Skip to content
81 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

The runner base keeps its old comment, so the pushed base image still matches its folder1import { defineRouteMiddleware } from '@astrojs/starlight/route-data';
2import { ogVersion } from '@g1t/contracts/og';
3
4import { tabFor } from './tabs';
5
6type Entry = { type: string; label?: string; href?: string; isCurrent?: boolean; entries?: Entry[] };
7
8/** The social cards' service; see services/og. */
9const OG = 'https://og.g1t.sh';
10
11/** The slug of a group's first page on this site, such as `reference/api`. */
12function firstSlug(entries: Entry[]): string | undefined {
13 for (const entry of entries) {
14 if (entry.type === 'group') {
15 const slug = firstSlug(entry.entries ?? []);
16 if (slug !== undefined) return slug;
17 } else if (entry.href?.startsWith('/') && !entry.href.startsWith('//')) {
18 return entry.href.replace(/^\/|\/$/g, '');
19 }
20 }
21 return undefined;
22}
23
24/** The label of the sidebar group holding the current page, the innermost if they nest. */
25function sectionOf(entries: Entry[], group?: string): string | undefined {
26 for (const entry of entries) {
27 if (entry.type === 'group') {
28 const found = sectionOf(entry.entries ?? [], entry.label);
29 if (found !== undefined) return found;
30 } else if (entry.isCurrent) {
31 return group;
32 }
33 }
34 return undefined;
35}
36
37/**
38 * Each page's sidebar holds only its tab's groups: those whose pages
39 * belong to the same tab. Groups are told apart by their pages, not their
40 * labels, since a guide group and an API group can share a name.
41 *
42 * Each page also gets its own social card: its title, its section and its
43 * description, drawn by the og service.
44 */
45export const onRequest = defineRouteMiddleware((context) => {
46 const route = context.locals.starlightRoute;
47 const section = sectionOf(route.sidebar as Entry[]);
48 const tab = tabFor(route.id);
49 route.sidebar = route.sidebar.filter((entry) => {
50 if (entry.type !== 'group') return false;
51 const slug = firstSlug(entry.entries as Entry[]);
52 return slug !== undefined && tabFor(slug) === tab;
53 });
54
55 const { title, description } = route.entry.data;
56 const card = new URL('/docs', OG);
57 card.searchParams.set('title', title);
58 if (section) card.searchParams.set('section', section);
59 if (description) card.searchParams.set('description', description);
60 // The render version, so a new card design is a new address for every cache.
61 card.searchParams.set('v', ogVersion());
62 const image = card.toString();
63 const alt = `${title} · g1t docs`;
64 const meta = (key: 'property' | 'name', name: string, content: string) => ({
65 tag: 'meta' as const,
66 attrs: { [key]: name, content },
67 });
68 // Starlight already writes og:title, og:description, og:url and
69 // twitter:card; these finish the card.
70 route.head.push(
71 meta('property', 'og:image', image),
72 meta('property', 'og:image:type', 'image/png'),
73 meta('property', 'og:image:width', '1200'),
74 meta('property', 'og:image:height', '630'),
75 meta('property', 'og:image:alt', alt),
76 meta('name', 'twitter:title', title),
77 meta('name', 'twitter:image', image),
78 meta('name', 'twitter:image:alt', alt),
79 );
80 if (description) route.head.push(meta('name', 'twitter:description', description));
81});

This file's history is long; its oldest lines are credited to the oldest commit read.