/* Route-shape helpers for the reader experience. An article route is a page of the open book drawn by ArticleBookStack — pages laid out side by side, older ones parked with their spines showing — while the article edit page and the new-article page remain plain, full-height pages. Every language version of an article has an address of its own — /wiki//, English no different from the rest, so /wiki/en/bayes-theorem and /wiki/fr/bayes-theorem sit side by side. The one short form, /wiki/ (what a wiki link to [[Bayes Theorem]] writes, and what an old bookmark holds), names no language: the open-article middleware reads which document the slug speaks and turns the address into that version's own. */ const READER_PATH = /^\/wiki\/([^/]+?)\/?$/ const VERSION_READER_PATH = /^\/wiki\/([a-z]{2})\/([^/]+?)\/?$/ const WIKI_SUB_PATH = /^\/wiki\/[^/]+\/[^/]+/ /** What a reader address names: the article's identity, and which language * version the address asks for (null for the short form, which asks for * whichever version the slug itself speaks). */ export interface ReaderLocation { identity: string lang: string | null } export function parseReaderPath(path: string): ReaderLocation | null { const version = VERSION_READER_PATH.exec(path) // /wiki//edit is the editor whatever is — never a version address if (version && version[2]!.toLowerCase() !== 'edit') { return { identity: decodeURIComponent(version[2]!), lang: version[1]! } } const plain = READER_PATH.exec(path) if (plain) return { identity: decodeURIComponent(plain[1]!), lang: null } return null } export function isReaderPath(path: string): boolean { return parseReaderPath(path) !== null } export function wikiSlugFromPath(path: string): string | null { return parseReaderPath(path)?.identity ?? null } /** * The address one language version lives at: /wiki//, the * article's identity being the canonical version's slug — so every version of * one article shares the last part of its address and differs only by the * language in front. Documents carry their own stored slug, which no address * needs: the wiki finds a version by (identity, language). */ export function versionHref(identity: string, language?: string): string { const code = language?.trim().toLowerCase() || DEFAULT_LANGUAGE return `/wiki/${encodeURIComponent(code)}/${encodeURIComponent(identity)}` } /** * The "reading surface": the reader itself plus the edit/new-article pages — * the pages that belong to the open book. The stack of open pages survives the * whole session either way; this marks the pages outside it (the list, the * graph, home), which are the places worth returning to once the last page is * closed. */ export function isReadingSurface(path: string): boolean { return isReaderPath(path) || WIKI_SUB_PATH.test(path) || path === '/new' } /* ---------- the wiki's own pages ---------- The link map and the graphing calculator are pages the shelf holds no copies of — but the book can read them too. Each has one plain address of its own, and useArticleTabs can lay it on the row beside the open articles: the page then draws the live surface instead of an article, for as long as its page stays open. */ export type ToolPage = 'graph' | 'calculator' const TOOL_PAGE_PATHS: Record = { graph: '/graph', calculator: '/calculator', } /** The address a tool page lives at. */ export function toolHref(page: ToolPage): string { return TOOL_PAGE_PATHS[page] } /** Which tool page an address names, if any. */ export function toolFromPath(path: string): ToolPage | null { const clean = path.replace(/\/+$/, '') for (const page of Object.keys(TOOL_PAGE_PATHS) as ToolPage[]) if (clean === TOOL_PAGE_PATHS[page]) return page return null } export function isToolPath(path: string): boolean { return toolFromPath(path) !== null }