import { escapeHtml } from "./format.js"; const listeners = new Set(); let rootElement = null; let routeElement = null; let current = null; let routeTable = []; export function parseLocation() { const raw = window.location.hash.replace(/^#/, "") || "/home"; const [pathPart, queryPart = ""] = raw.split("?"); const path = pathPart.startsWith("/") ? pathPart : `/${pathPart}`; return { path, query: new URLSearchParams(queryPart) }; } export function navigate(path, query) { const params = new URLSearchParams(); if (query) { for (const [key, value] of Object.entries(query)) { if (value === null || value === undefined || value === "") continue; params.set(key, String(value)); } } const text = params.toString(); window.location.hash = `${path}${text ? `?${text}` : ""}`; } export function replaceQuery(changes) { const { path, query } = parseLocation(); for (const [key, value] of Object.entries(changes)) { if (value === null || value === undefined || value === "") query.delete(key); else query.set(key, String(value)); } const text = query.toString(); window.location.hash = `${path}${text ? `?${text}` : ""}`; } export function onRoute(listener) { listeners.add(listener); } export function startRouter({ root, outlet, routes }) { rootElement = root; routeElement = outlet; routeTable = routes; window.addEventListener("hashchange", render); render(); } export function currentRoute() { return current; } function matchRoute(path, routes) { for (const route of routes) { const match = route.pattern.exec(path); if (match) return { route, match }; } return null; } async function render() { const { path, query } = parseLocation(); if (!path || path === "/") { navigate("/home"); return; } const matched = matchRoute(path, routeTable); if (!matched) { routeElement.innerHTML = `
页面不存在

${escapeHtml(path)}

回到首页
`; return; } current = { path, query, route: matched.route }; for (const listener of listeners) { try { listener(current); } catch { // 导航状态更新不应阻断页面渲染 } } window.scrollTo({ top: 0, behavior: "instant" }); routeElement.innerHTML = `
`; try { await matched.route.view(routeElement, { params: matched.match.slice(1), query, path, }); } catch (error) { routeElement.innerHTML = `
加载失败

${escapeHtml(error.message || "未知错误")}

`; routeElement.querySelector("#retry-button")?.addEventListener("click", render); } rootElement?.classList.remove("is-loading"); }