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)}
${escapeHtml(error.message || "未知错误")}