Vite + TypeScript ohne UI-Framework in web/, exakte Versionen, History-Router (matchRoute, navigate, startRouter mit Cleanup, a[data-link], popstate), Grundlayout mit Kopfzeile, Fußzeile und Platzhalterseiten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
2.0 KiB
TypeScript
65 lines
2.0 KiB
TypeScript
export type RouteId = 'editor' | 'cards' | 'reset';
|
|
|
|
type Render = (route: RouteId, root: HTMLElement) => void | (() => void);
|
|
|
|
export function matchRoute(pathname: string): RouteId {
|
|
const path = pathname.replace(/\/+$/, '') || '/';
|
|
if (path === '/karten') return 'cards';
|
|
if (path === '/passwort-neu') return 'reset';
|
|
return 'editor';
|
|
}
|
|
|
|
// Beendet den laufenden Router (Seiten-Cleanup, Listener); null, solange keiner läuft.
|
|
let stopCurrent: (() => void) | null = null;
|
|
let renderCurrent: (() => void) | null = null;
|
|
|
|
export function navigate(path: string): void {
|
|
const url = new URL(path, location.href);
|
|
const target = url.pathname + url.search + url.hash;
|
|
if (target !== location.pathname + location.search + location.hash) {
|
|
history.pushState(null, '', target);
|
|
}
|
|
renderCurrent?.();
|
|
}
|
|
|
|
export function startRouter(render: Render): void {
|
|
stopCurrent?.();
|
|
|
|
const root = document.getElementById('app-root');
|
|
if (!root) throw new Error('#app-root fehlt');
|
|
|
|
let cleanup: void | (() => void);
|
|
|
|
const renderPage = () => {
|
|
cleanup?.();
|
|
cleanup = undefined;
|
|
cleanup = render(matchRoute(location.pathname), root);
|
|
};
|
|
|
|
const onClick = (event: MouseEvent) => {
|
|
if (event.defaultPrevented || event.button !== 0) return;
|
|
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
const anchor = (event.target as Element | null)?.closest<HTMLAnchorElement>('a[data-link]');
|
|
if (!anchor || (anchor.target && anchor.target !== '_self')) return;
|
|
const url = new URL(anchor.href, location.href);
|
|
if (url.origin !== location.origin) return;
|
|
event.preventDefault();
|
|
navigate(url.pathname + url.search + url.hash);
|
|
};
|
|
|
|
document.addEventListener('click', onClick);
|
|
window.addEventListener('popstate', renderPage);
|
|
|
|
renderCurrent = renderPage;
|
|
stopCurrent = () => {
|
|
document.removeEventListener('click', onClick);
|
|
window.removeEventListener('popstate', renderPage);
|
|
cleanup?.();
|
|
cleanup = undefined;
|
|
renderCurrent = null;
|
|
stopCurrent = null;
|
|
};
|
|
|
|
renderPage();
|
|
}
|