mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-25 05:09:03 -05:00
The 404 page opens on Start here instead of tabs numbered 0 to 4. A group label is a group, or in Explore one link to its overview, never a link to its first page, and a crumb names the section that introduces the page. Emulators sit one level deep by initial. Previous and next run within a tab (Start here into Your setup) and skip system, emulator and platform pages, and follow a page's Next steps list. Reference pages group under Checking files and Data, a wiki page joining by its group: front matter. redirects.json maps alternative core names to their profile.
113 lines
4.9 KiB
TypeScript
113 lines
4.9 KiB
TypeScript
// What each page knows of its place in the site, computed once per page
|
|
// from what scripts/sitestarlight.py writes beside the sidebar:
|
|
// - breadcrumbs: the tab and the groups above the page, each linked to the
|
|
// page or the section that introduces it, never to a sibling page;
|
|
// - previous and next: the reading order of `reading`. Start here runs on
|
|
// into Your setup, every other tab is read on its own, and a system,
|
|
// emulator or platform page has none;
|
|
// - a pruned sidebar: in the tabs listing every system and emulator, a
|
|
// group that does not hold the page becomes one link to its overview, so
|
|
// an emulator page does not carry the links of every other emulator. The
|
|
// groups of the other tabs stay groups.
|
|
import { defineRouteMiddleware, type StarlightRouteData } from '@astrojs/starlight/route-data';
|
|
import sidebar from './generated/sidebar.json' with { type: 'json' };
|
|
|
|
type Entry = StarlightRouteData['sidebar'][number];
|
|
type Link = Extract<Entry, { type: 'link' }>;
|
|
type Group = { href: string | null; prune: boolean };
|
|
export type Crumb = { label: string; href?: string };
|
|
|
|
const BASE = import.meta.env.BASE_URL.replace(/\/?$/, '/');
|
|
const withBase = (link: string) => (link.startsWith(BASE) ? link : BASE + link.replace(/^\//, ''));
|
|
|
|
const GROUPS = sidebar.groups as Record<string, Group>;
|
|
const RUNS = sidebar.reading as { label: string; link: string }[][];
|
|
|
|
const groupOf = (trail: string[]): Group | undefined => GROUPS[trail.join('/')];
|
|
|
|
const holdsCurrent = (entry: Entry): boolean =>
|
|
entry.type === 'link' ? entry.isCurrent : entry.entries.some(holdsCurrent);
|
|
|
|
const prune = (entries: Entry[], trail: string[]): Entry[] =>
|
|
entries.map((entry) => {
|
|
if (entry.type === 'link') return entry;
|
|
const here = [...trail, entry.label];
|
|
if (holdsCurrent(entry)) return { ...entry, collapsed: false, entries: prune(entry.entries, here) };
|
|
const group = groupOf(here);
|
|
if (group?.prune && group.href) {
|
|
return { type: 'link', label: entry.label, href: withBase(group.href), isCurrent: false, badge: entry.badge, attrs: {} };
|
|
}
|
|
return { ...entry, entries: prune(entry.entries, here) };
|
|
});
|
|
|
|
// The groups above the current page, outermost first.
|
|
const trail = (entries: Entry[], labels: string[]): Crumb[] | undefined => {
|
|
for (const entry of entries) {
|
|
if (entry.type === 'link') {
|
|
if (entry.isCurrent) return [];
|
|
continue;
|
|
}
|
|
const here = [...labels, entry.label];
|
|
const below = trail(entry.entries, here);
|
|
if (below) {
|
|
const href = groupOf(here)?.href;
|
|
return [{ label: entry.label, href: href ? withBase(href) : undefined }, ...below];
|
|
}
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
// A page the navigation does not list (part of a split page) sits under the
|
|
// group holding the pages of its folder.
|
|
const folderTrail = (entries: Entry[], labels: string[], folder: string): Crumb[] => {
|
|
const links = (entry: Entry): string[] => (entry.type === 'link' ? [entry.href] : entry.entries.flatMap(links));
|
|
for (const entry of entries) {
|
|
if (entry.type !== 'group' || !links(entry).some((href) => href.startsWith(folder))) continue;
|
|
const href = groupOf([...labels, entry.label])?.href;
|
|
return [{ label: entry.label, href: href ? withBase(href) : undefined }];
|
|
}
|
|
return [];
|
|
};
|
|
|
|
const asLink = (entry: { label: string; link: string }): Link => ({
|
|
type: 'link',
|
|
label: entry.label,
|
|
href: withBase(entry.link),
|
|
isCurrent: false,
|
|
badge: undefined,
|
|
attrs: {},
|
|
});
|
|
|
|
export const onRequest = defineRouteMiddleware(async (context, next) => {
|
|
await next();
|
|
const route = context.locals.starlightRoute;
|
|
const path = context.url.pathname;
|
|
|
|
const topic = context.locals.starlightSidebarTopics?.topics.find((t) => t.isCurrent);
|
|
const crumbs: Crumb[] = [];
|
|
if (topic && withBase(topic.link) !== path && route.id !== '404') {
|
|
crumbs.push({ label: topic.label, href: withBase(topic.link) });
|
|
const segments = path.slice(BASE.length).split('/').filter(Boolean);
|
|
const groups =
|
|
trail(route.sidebar, [topic.label]) ??
|
|
(segments.length > 1 ? folderTrail(route.sidebar, [topic.label], `${BASE}${segments[0]}/`) : []);
|
|
for (const crumb of groups) {
|
|
// A group introduced by the page itself is the page; one with no
|
|
// section of its own (the initial D of the emulators) adds nothing
|
|
// to the crumb before it.
|
|
if (crumb.href === path) continue;
|
|
if (crumb.href && crumb.href === crumbs.at(-1)?.href) continue;
|
|
crumbs.push(crumb);
|
|
}
|
|
}
|
|
context.locals.rbBreadcrumbs = crumbs;
|
|
|
|
const { prev, next: following } = route.entry.data;
|
|
const run = RUNS.find((pages) => pages.some((entry) => withBase(entry.link) === path));
|
|
const position = run ? run.findIndex((entry) => withBase(entry.link) === path) : -1;
|
|
if (prev === undefined) route.pagination.prev = run && position > 0 ? asLink(run[position - 1]) : undefined;
|
|
if (following === undefined) route.pagination.next = run && position < run.length - 1 ? asLink(run[position + 1]) : undefined;
|
|
|
|
route.sidebar = prune(route.sidebar, [topic?.label ?? '']);
|
|
});
|