mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 12:49:03 -05:00
The decoration pass now forwards the logo front matter, pointed at the local copy. The emulators index lists every profile under the initial the sidebar draws, so each letter link lands on its section.
112 lines
4.8 KiB
TypeScript
112 lines
4.8 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, and one with
|
|
// no section of its own 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 ?? '']);
|
|
});
|