import {html, TemplateResult, render} from "@benev/slate" type RouteHandler = (...params: string[]) => TemplateResult interface Routes { [path: string]: RouteHandler } export class HashRouter { routes: Routes element: HTMLDivElement constructor(routes: Routes) { this.routes = routes this.element = document.createElement("div") this.onHashChange = this.onHashChange.bind(this) window.addEventListener("hashchange", this.onHashChange) this.onHashChange() } getCurrentPath() { return window.location.hash.slice(1) || "/" } matchRoute(path: string): { handler: RouteHandler; params: string[] } | null { for (const route in this.routes) { const routeParts = route.split("/") const pathParts = path.split("/") if (routeParts.includes("*")) { const isMatch = routeParts.every((part, index) => { return part === pathParts[index] || part === "*" }) if (isMatch) { const wildcardIndex = routeParts.indexOf("*") const wildcardValue = pathParts[wildcardIndex] return { handler: this.routes[route], params: [wildcardValue] } } } if (route === path) { return { handler: this.routes[route], params: [] } } } return null } onHashChange() { const currentPath = this.getCurrentPath() const matchedRoute = this.matchRoute(currentPath) if (matchedRoute) { const { handler, params } = matchedRoute this.render(handler, ...params) } else if (currentPath.startsWith("editor")) { this.render(() => html`404 Not Found`) } else { this.render(this.routes["/"]) // Default to landing page } } render(handler: RouteHandler, ...params: string[]) { render(handler(...params), this.element) } }