feat(web): Projektgerüst, Router, Grundlayout
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>
This commit is contained in:
1 parent
ba93d8f8c5
commit
05a735993b
13 files changed
+1664
No files matched your search
@@ -0,0 +1,64 @@
|
||||
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();
|
||||
}
|
||||
Reference in new issue
Block a user