import { Component, Suspense, useEffect, type ReactNode } from 'react'; import { useLocation, type RouteObject } from 'react-router'; import { config } from 'virtual:seemore/config'; import { decodePath } from '../shared/base.js'; import type { RouteEntry } from '../shared/types.js'; import { DocPage, DocsLayout, NotFound, Overview, PageError } from './layout/DocsLayout.js'; import { useRouteEntry } from './lib/pages.js'; /** The current route URL: React Router has already removed the basename. */ export function useRouteUrl(): string { const { pathname } = useLocation(); const trimmed = decodePath(pathname).replace(/\/+$/, ''); return trimmed === '' ? '/' : trimmed; } function Page() { const url = useRouteUrl(); const entry = useRouteEntry(url); useEffect(() => { document.title = entry === undefined || url === '/' ? config.title : `${entry.title} · ${config.title}`; }, [url, entry?.title]); // Keyed by address, so navigating away from a page that threw starts clean rather than // carrying its error to every page after it. Reset by content version, so in dev an edit // that fixes the file gets to render — a boundary in its error state has unmounted the // children, and nothing else is left in the tree to try again. return ( ); } function PageContent({ url, entry }: { url: string; entry: RouteEntry | undefined }) { if (entry !== undefined) return ; // A folder with no `index.md` or root `README.md` still gets a home address: a generated // list of every page, not an apology. return url === '/' ? : ; } /** * The only error boundary in the app. Render errors come from page content — the rest of the * tree is seemore's own — so this sits around the page and nothing else. */ class PageErrorBoundary extends Component< { children: ReactNode; resetKey: string | undefined }, { message: string | undefined } > { override state: { message: string | undefined } = { message: undefined }; static getDerivedStateFromError(error: unknown): { message: string } { return { message: error instanceof Error ? error.message : String(error) }; } override componentDidUpdate(previous: { resetKey: string | undefined }): void { if (this.state.message !== undefined && previous.resetKey !== this.props.resetKey) { this.setState({ message: undefined }); } } override render() { if (this.state.message !== undefined) return ; return this.props.children; } } /** * One catch-all route, matched against `virtual:seemore/routes` at render time. * * Our URLs are exact strings, so there is no pattern matching for a router to do — and a * route table that never changes shape means creating or deleting a page needs no new * router, which is what keeps the dev-mode sidebar refresh a re-render rather than a reload. * * A data router, because fumadocs' React Router integration uses its hooks — but **no route * carries a loader**, which is the actual guarantee. With nothing to fetch, * there is no code path in which server-side data can fail to prerender. */ export function createRouteObjects(): RouteObject[] { return [ { path: '*', element: ( }> ), }, ]; }