feat: crumb to the parent and read forward

A part page's parent is its last crumb and is marked in the sidebar.
A Next steps link pointing back in the reading run gives way to the run
order, so Start here reads every page once.
This commit is contained in:
Abdessamad Derraz committed 2026-10-23 10:36:09 +02:00
1 parent 5b1120aeb5
commit fc33779c6f
2 files changed
+33 -2

No files matched your search

+3
View File
@@ -19,6 +19,9 @@ export const collections = {
// A mark drawn beside the title (a platform's logo), a path under
// the site root such as assets/logos/batocera.svg.
logo: z.string().optional(),
// The page a part page belongs to (a console of a setup's list),
// read by routeData.ts: the last crumb, marked in the sidebar.
parent: z.object({ label: z.string(), link: z.string() }).optional(),
}),
}),
}),
+30 -2
View File
@@ -8,7 +8,12 @@
// - 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.
// groups of the other tabs stay groups;
// - a part page (one console of a setup's list) names the page it belongs
// to in its `parent:` front matter: that page is its last crumb, and its
// sidebar entry is marked when the part page has none of its own;
// - a page's "Next steps" link is its next only when it reads forward: one
// pointing back to an earlier page of the run made Start here loop.
import { defineRouteMiddleware, type StarlightRouteData } from '@astrojs/starlight/route-data';
import sidebar from './generated/sidebar.json' with { type: 'json' };
@@ -69,6 +74,18 @@ const folderTrail = (entries: Entry[], labels: string[], folder: string): Crumb[
return [];
};
// The parent's entry marked current, for a page the sidebar does not list.
const markCurrent = (entries: Entry[], href: string): Entry[] =>
entries.map((entry) =>
entry.type === 'link'
? entry.href === href
? { ...entry, isCurrent: true }
: entry
: { ...entry, entries: markCurrent(entry.entries, href) },
);
const bare = (link: string) => withBase(link).replace(/[?#].*$/, '');
const asLink = (entry: { label: string; link: string }): Link => ({
type: 'link',
label: entry.label,
@@ -84,6 +101,8 @@ export const onRequest = defineRouteMiddleware(async (context, next) => {
const path = context.url.pathname;
const topic = context.locals.starlightSidebarTopics?.topics.find((t) => t.isCurrent);
const parent = route.entry.data.parent;
if (parent && !route.sidebar.some(holdsCurrent)) route.sidebar = markCurrent(route.sidebar, bare(parent.link));
const crumbs: Crumb[] = [];
if (topic && withBase(topic.link) !== path && route.id !== '404') {
crumbs.push({ label: topic.label, href: withBase(topic.link) });
@@ -98,6 +117,10 @@ export const onRequest = defineRouteMiddleware(async (context, next) => {
if (crumb.href && crumb.href === crumbs.at(-1)?.href) continue;
crumbs.push(crumb);
}
const parentHref = parent ? withBase(parent.link) : undefined;
if (parent && parentHref !== path && parentHref !== crumbs.at(-1)?.href) {
crumbs.push({ label: parent.label, href: parentHref });
}
}
context.locals.rbBreadcrumbs = crumbs;
@@ -105,7 +128,12 @@ export const onRequest = defineRouteMiddleware(async (context, next) => {
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;
const inOrder = run && position < run.length - 1 ? asLink(run[position + 1]) : undefined;
if (following === undefined) route.pagination.next = inOrder;
else if (run && typeof following === 'object' && following.link) {
const target = run.findIndex((entry) => bare(entry.link) === bare(following.link));
if (target !== -1 && target <= position) route.pagination.next = inOrder;
}
route.sidebar = prune(route.sidebar, [topic?.label ?? '']);
});