@astrojs/starlight 0.10.4 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/404.astro +9 -4
  2. package/CHANGELOG.md +59 -0
  3. package/components/Badge.astro +1 -0
  4. package/components/Banner.astro +4 -4
  5. package/components/ContentPanel.astro +4 -0
  6. package/components/EditLink.astro +5 -24
  7. package/components/FallbackContentNotice.astro +2 -5
  8. package/components/Footer.astro +5 -32
  9. package/components/{HeadSEO.astro → Head.astro} +10 -14
  10. package/components/Header.astro +26 -16
  11. package/components/Hero.astro +6 -9
  12. package/components/Icons.ts +4 -0
  13. package/components/LanguageSelect.astro +1 -4
  14. package/components/LastUpdated.astro +5 -24
  15. package/components/MarkdownContent.astro +4 -0
  16. package/components/MobileMenuFooter.astro +17 -0
  17. package/components/MobileMenuToggle.astro +2 -4
  18. package/components/{TableOfContents/MobileTableOfContents.astro → MobileTableOfContents.astro} +26 -29
  19. package/components/Page.astro +120 -0
  20. package/{layout → components}/PageFrame.astro +16 -8
  21. package/components/{RightSidebarPanel.astro → PageSidebar.astro} +21 -9
  22. package/components/PageTitle.astro +16 -0
  23. package/components/{PrevNextLinks.astro → Pagination.astro} +3 -9
  24. package/components/Search.astro +5 -5
  25. package/components/Sidebar.astro +7 -36
  26. package/components/SidebarSublist.astro +8 -2
  27. package/components/SiteTitle.astro +1 -20
  28. package/components/SkipLink.astro +3 -5
  29. package/components/SocialIcons.astro +5 -34
  30. package/components/TableOfContents/TableOfContentsList.astro +1 -1
  31. package/components/TableOfContents/starlight-toc.ts +3 -1
  32. package/components/TableOfContents.astro +12 -15
  33. package/components/ThemeProvider.astro +1 -0
  34. package/components/ThemeSelect.astro +1 -4
  35. package/{layout → components}/TwoColumnContent.astro +2 -4
  36. package/constants.ts +4 -0
  37. package/index.astro +4 -2
  38. package/index.ts +10 -15
  39. package/integrations/virtual-user-config.ts +3 -0
  40. package/package.json +132 -3
  41. package/props.ts +1 -0
  42. package/schema.ts +3 -0
  43. package/schemas/components.ts +256 -0
  44. package/schemas/sidebar.ts +89 -0
  45. package/schemas/social.ts +65 -0
  46. package/style/reset.css +1 -1
  47. package/translations/index.ts +2 -0
  48. package/translations/vi.json +22 -0
  49. package/{components/TableOfContents → utils}/generateToC.ts +2 -1
  50. package/utils/navigation.ts +22 -5
  51. package/utils/route-data.ts +97 -0
  52. package/utils/routing.ts +10 -0
  53. package/utils/user-config.ts +15 -102
  54. package/utils/validateLogoImports.ts +21 -0
  55. package/virtual.d.ts +37 -0
  56. package/components/RightSidebar.astro +0 -36
  57. package/layout/Page.astro +0 -132
@@ -5,9 +5,14 @@ import { pathWithBase } from './base';
5
5
  import { pickLang } from './i18n';
6
6
  import { getLocaleRoutes, type Route } from './routing';
7
7
  import { localeToLang, slugToPathname } from './slugs';
8
- import type { AutoSidebarGroup, SidebarItem, SidebarLinkItem } from './user-config';
9
8
  import { ensureLeadingAndTrailingSlashes, ensureTrailingSlash } from './path';
10
9
  import type { Badge } from '../schemas/badge';
10
+ import type {
11
+ AutoSidebarGroup,
12
+ LinkHTMLAttributes,
13
+ SidebarItem,
14
+ SidebarLinkItem,
15
+ } from '../schemas/sidebar';
11
16
 
12
17
  const DirKey = Symbol('DirKey');
13
18
 
@@ -17,6 +22,7 @@ export interface Link {
17
22
  href: string;
18
23
  isCurrent: boolean;
19
24
  badge: Badge | undefined;
25
+ attrs: LinkHTMLAttributes;
20
26
  }
21
27
 
22
28
  interface Group {
@@ -114,14 +120,20 @@ function linkFromConfig(
114
120
  if (locale) href = '/' + locale + href;
115
121
  }
116
122
  const label = pickLang(item.translations, localeToLang(locale)) || item.label;
117
- return makeLink(href, label, currentPathname, item.badge);
123
+ return makeLink(href, label, currentPathname, item.badge, item.attrs);
118
124
  }
119
125
 
120
126
  /** Create a link entry. */
121
- function makeLink(href: string, label: string, currentPathname: string, badge?: Badge): Link {
127
+ function makeLink(
128
+ href: string,
129
+ label: string,
130
+ currentPathname: string,
131
+ badge?: Badge,
132
+ attrs?: LinkHTMLAttributes
133
+ ): Link {
122
134
  if (!isAbsolute(href)) href = pathWithBase(href);
123
135
  const isCurrent = href === ensureTrailingSlash(currentPathname);
124
- return { type: 'link', label, href, isCurrent, badge };
136
+ return { type: 'link', label, href, isCurrent, badge, attrs: attrs ?? {} };
125
137
  }
126
138
 
127
139
  /** Get the segments leading to a page. */
@@ -171,7 +183,8 @@ function linkFromRoute(route: Route, currentPathname: string): Link {
171
183
  slugToPathname(route.slug),
172
184
  route.entry.data.sidebar.label || route.entry.data.title,
173
185
  currentPathname,
174
- route.entry.data.sidebar.badge
186
+ route.entry.data.sidebar.badge,
187
+ route.entry.data.sidebar.attrs
175
188
  );
176
189
  }
177
190
 
@@ -274,7 +287,9 @@ export function getPrevNextLinks(
274
287
  next?: PrevNextLinkConfig;
275
288
  }
276
289
  ): {
290
+ /** Link to previous page in the sidebar. */
277
291
  prev: Link | undefined;
292
+ /** Link to next page in the sidebar. */
278
293
  next: Link | undefined;
279
294
  } {
280
295
  const entries = flattenSidebar(sidebar);
@@ -308,6 +323,8 @@ function applyPrevNextLinkConfig(
308
323
  ...link,
309
324
  label: config.label ?? link.label,
310
325
  href: config.link ?? link.href,
326
+ // Explicitly remove sidebar link attributes for prev/next links.
327
+ attrs: {},
311
328
  };
312
329
  } else if (config.link && config.label) {
313
330
  // If there is no link and the frontmatter contains both a URL and a label,
@@ -0,0 +1,97 @@
1
+ import type { MarkdownHeading } from 'astro';
2
+ import { fileURLToPath } from 'node:url';
3
+ import project from 'virtual:starlight/project-context';
4
+ import config from 'virtual:starlight/user-config';
5
+ import { generateToC, type TocItem } from './generateToC';
6
+ import { getFileCommitDate } from './git';
7
+ import { getPrevNextLinks, getSidebar, type SidebarEntry } from './navigation';
8
+ import type { Route } from './routing';
9
+ import { useTranslations } from './translations';
10
+ import { ensureTrailingSlash } from './path';
11
+
12
+ interface PageProps extends Route {
13
+ headings: MarkdownHeading[];
14
+ }
15
+
16
+ export interface StarlightRouteData extends Route {
17
+ /** Array of Markdown headings extracted from the current page. */
18
+ headings: MarkdownHeading[];
19
+ /** Site navigation sidebar entries for this page. */
20
+ sidebar: SidebarEntry[];
21
+ /** Whether or not the sidebar should be displayed on this page. */
22
+ hasSidebar: boolean;
23
+ /** Links to the previous and next page in the sidebar if enabled. */
24
+ pagination: ReturnType<typeof getPrevNextLinks>;
25
+ /** Table of contents for this page if enabled. */
26
+ toc: { minHeadingLevel: number; maxHeadingLevel: number; items: TocItem[] } | undefined;
27
+ /** JS Date object representing when this page was last updated if enabled. */
28
+ lastUpdated: Date | undefined;
29
+ /** URL object for the address where this page can be edited if enabled. */
30
+ editUrl: URL | undefined;
31
+ }
32
+
33
+ export function generateRouteData({
34
+ props,
35
+ url,
36
+ }: {
37
+ props: PageProps;
38
+ url: URL;
39
+ }): StarlightRouteData {
40
+ const { entry, locale } = props;
41
+ const sidebar = getSidebar(url.pathname, locale);
42
+ return {
43
+ ...props,
44
+ sidebar,
45
+ hasSidebar: entry.data.template !== 'splash',
46
+ pagination: getPrevNextLinks(sidebar, config.pagination, entry.data),
47
+ toc: getToC(props),
48
+ lastUpdated: getLastUpdated(props),
49
+ editUrl: getEditUrl(props),
50
+ };
51
+ }
52
+
53
+ function getToC({ entry, locale, headings }: PageProps) {
54
+ const tocConfig =
55
+ entry.data.template === 'splash'
56
+ ? false
57
+ : entry.data.tableOfContents !== undefined
58
+ ? entry.data.tableOfContents
59
+ : config.tableOfContents;
60
+ if (!tocConfig) return;
61
+ const t = useTranslations(locale);
62
+ return {
63
+ ...tocConfig,
64
+ items: generateToC(headings, { ...tocConfig, title: t('tableOfContents.overview') }),
65
+ };
66
+ }
67
+
68
+ function getLastUpdated({ entry, id }: PageProps): Date | undefined {
69
+ if (entry.data.lastUpdated ?? config.lastUpdated) {
70
+ const currentFilePath = fileURLToPath(new URL('src/content/docs/' + id, project.root));
71
+ let date = typeof entry.data.lastUpdated !== 'boolean' ? entry.data.lastUpdated : undefined;
72
+ if (!date) {
73
+ try {
74
+ ({ date } = getFileCommitDate(currentFilePath, 'newest'));
75
+ } catch {}
76
+ }
77
+ return date;
78
+ }
79
+ return;
80
+ }
81
+
82
+ function getEditUrl({ entry, id }: PageProps): URL | undefined {
83
+ const { editUrl } = entry.data;
84
+ // If frontmatter value is false, editing is disabled for this page.
85
+ if (editUrl === false) return;
86
+
87
+ let url: string | undefined;
88
+ if (typeof editUrl === 'string') {
89
+ // If a URL was provided in frontmatter, use that.
90
+ url = editUrl;
91
+ } else if (config.editLink.baseUrl) {
92
+ const srcPath = project.srcDir.replace(project.root, '');
93
+ // If a base URL was added in Starlight config, synthesize the edit URL from it.
94
+ url = ensureTrailingSlash(config.editLink.baseUrl) + srcPath + 'content/docs/' + id;
95
+ }
96
+ return url ? new URL(url) : undefined;
97
+ }
package/utils/routing.ts CHANGED
@@ -8,16 +8,26 @@ import {
8
8
  slugToLocaleData,
9
9
  slugToParam,
10
10
  } from './slugs';
11
+ import { validateLogoImports } from './validateLogoImports';
12
+
13
+ // Validate any user-provided logos imported correctly.
14
+ // We do this here so all pages trigger it and at the top level so it runs just once.
15
+ validateLogoImports();
11
16
 
12
17
  export type StarlightDocsEntry = Omit<CollectionEntry<'docs'>, 'slug'> & {
13
18
  slug: string;
14
19
  };
15
20
 
16
21
  export interface Route extends LocaleData {
22
+ /** Content collection entry for the current page. Includes frontmatter at `data`. */
17
23
  entry: StarlightDocsEntry;
24
+ /** Locale metadata for the page content. Can be different from top-level locale values when a page is using fallback content. */
18
25
  entryMeta: LocaleData;
26
+ /** The slug, a.k.a. permalink, for this page. */
19
27
  slug: string;
28
+ /** The unique ID for this page. */
20
29
  id: string;
30
+ /** True if this page is untranslated in the current language and using fallback content from the default locale. */
21
31
  isFallback?: true;
22
32
  [key: string]: unknown;
23
33
  }
@@ -1,10 +1,13 @@
1
1
  import { z } from 'astro/zod';
2
2
  import { parse as bcpParse, stringify as bcpStringify } from 'bcp-47';
3
+ import { BadgeConfigSchema } from '../schemas/badge';
4
+ import { ComponentConfigSchema } from '../schemas/components';
5
+ import { FaviconSchema } from '../schemas/favicon';
3
6
  import { HeadConfigSchema } from '../schemas/head';
4
7
  import { LogoConfigSchema } from '../schemas/logo';
8
+ import { SidebarItemSchema } from '../schemas/sidebar';
9
+ import { SocialLinksSchema } from '../schemas/social';
5
10
  import { TableOfContentsSchema } from '../schemas/tableOfContents';
6
- import { FaviconSchema } from '../schemas/favicon';
7
- import { BadgeConfigSchema } from '../schemas/badge';
8
11
 
9
12
  const LocaleSchema = z.object({
10
13
  /** The label for this language to show in UI, e.g. `"English"`, `"العربية"`, or `"简体中文"`. */
@@ -28,79 +31,6 @@ const LocaleSchema = z.object({
28
31
  ),
29
32
  });
30
33
 
31
- const SidebarBaseSchema = z.object({
32
- /** The visible label for this item in the sidebar. */
33
- label: z.string(),
34
- /** Translations of the `label` for each supported language. */
35
- translations: z.record(z.string()).default({}),
36
- });
37
-
38
- const SidebarGroupSchema = SidebarBaseSchema.extend({
39
- /** Whether this item should be collapsed by default. */
40
- collapsed: z.boolean().default(false),
41
- });
42
-
43
- const SidebarLinkItemSchema = SidebarBaseSchema.extend({
44
- /** The link to this item’s content. Can be a relative link to local files or the full URL of an external page. */
45
- link: z.string(),
46
- /** Adds a badge to the link item */
47
- badge: BadgeConfigSchema(),
48
- });
49
- export type SidebarLinkItem = z.infer<typeof SidebarLinkItemSchema>;
50
-
51
- const AutoSidebarGroupSchema = SidebarGroupSchema.extend({
52
- /** Enable autogenerating a sidebar category from a specific docs directory. */
53
- autogenerate: z.object({
54
- /** The directory to generate sidebar items for. */
55
- directory: z.string(),
56
- /**
57
- * Whether the autogenerated subgroups should be collapsed by default.
58
- * Defaults to the `AutoSidebarGroup` `collapsed` value.
59
- */
60
- collapsed: z.boolean().optional(),
61
- // TODO: not supported by Docusaurus but would be good to have
62
- /** How many directories deep to include from this directory in the sidebar. Default: `Infinity`. */
63
- // depth: z.number().optional(),
64
- }),
65
- });
66
- export type AutoSidebarGroup = z.infer<typeof AutoSidebarGroupSchema>;
67
-
68
- type ManualSidebarGroupInput = z.input<typeof SidebarGroupSchema> & {
69
- /** Array of links and subcategories to display in this category. */
70
- items: Array<
71
- | z.input<typeof SidebarLinkItemSchema>
72
- | z.input<typeof AutoSidebarGroupSchema>
73
- | ManualSidebarGroupInput
74
- >;
75
- };
76
-
77
- type ManualSidebarGroupOutput = z.output<typeof SidebarGroupSchema> & {
78
- /** Array of links and subcategories to display in this category. */
79
- items: Array<
80
- | z.output<typeof SidebarLinkItemSchema>
81
- | z.output<typeof AutoSidebarGroupSchema>
82
- | ManualSidebarGroupOutput
83
- >;
84
- };
85
-
86
- const ManualSidebarGroupSchema: z.ZodType<
87
- ManualSidebarGroupOutput,
88
- z.ZodTypeDef,
89
- ManualSidebarGroupInput
90
- > = SidebarGroupSchema.extend({
91
- /** Array of links and subcategories to display in this category. */
92
- items: z.lazy(() =>
93
- z.union([SidebarLinkItemSchema, ManualSidebarGroupSchema, AutoSidebarGroupSchema]).array()
94
- ),
95
- });
96
-
97
- const SidebarItemSchema = z.union([
98
- SidebarLinkItemSchema,
99
- ManualSidebarGroupSchema,
100
- AutoSidebarGroupSchema,
101
- ]);
102
- export type SidebarItem = z.infer<typeof SidebarItemSchema>;
103
-
104
34
  const UserConfigSchema = z.object({
105
35
  /** Title for your website. Will be used in metadata and as browser tab title. */
106
36
  title: z
@@ -133,33 +63,7 @@ const UserConfigSchema = z.object({
133
63
  * youtube: 'https://youtube.com/@astrodotbuild',
134
64
  * }
135
65
  */
136
- social: z
137
- .record(
138
- z.enum([
139
- 'twitter',
140
- 'mastodon',
141
- 'github',
142
- 'gitlab',
143
- 'bitbucket',
144
- 'discord',
145
- 'gitter',
146
- 'codeberg',
147
- 'codePen',
148
- 'youtube',
149
- 'threads',
150
- 'linkedin',
151
- 'twitch',
152
- 'microsoftTeams',
153
- 'instagram',
154
- 'stackOverflow',
155
- 'x.com',
156
- 'telegram',
157
- 'rss',
158
- ]),
159
- // Link to the respective social profile for this site
160
- z.string().url()
161
- )
162
- .optional(),
66
+ social: SocialLinksSchema(),
163
67
 
164
68
  /** The tagline for your website. */
165
69
  tagline: z.string().optional().describe('The tagline for your website.'),
@@ -279,6 +183,15 @@ const UserConfigSchema = z.object({
279
183
 
280
184
  /** The default favicon for your site which should be a path to an image in the `public/` directory. */
281
185
  favicon: FaviconSchema(),
186
+
187
+ /** Specify paths to components that should override Starlight’s default components */
188
+ components: ComponentConfigSchema(),
189
+
190
+ /** Will be used as title delimiter in the generated `<title>` tag. */
191
+ titleDelimiter: z
192
+ .string()
193
+ .default('|')
194
+ .describe('Will be used as title delimiter in the generated `<title>` tag.'),
282
195
  });
283
196
 
284
197
  export const StarlightConfigSchema = UserConfigSchema.strict().transform(
@@ -0,0 +1,21 @@
1
+ import config from 'virtual:starlight/user-config';
2
+ import { logos } from 'virtual:starlight/user-images';
3
+
4
+ /** Check user-imported logo images have resolved correctly. */
5
+ export function validateLogoImports(): void {
6
+ if (config.logo) {
7
+ let err: string | undefined;
8
+ if ('src' in config.logo) {
9
+ if (!logos.dark || !logos.light) {
10
+ err = `Could not resolve logo import for "${config.logo.src}" (logo.src)`;
11
+ }
12
+ } else {
13
+ if (!logos.dark) {
14
+ err = `Could not resolve logo import for "${config.logo.dark}" (logo.dark)`;
15
+ } else if (!logos.light) {
16
+ err = `Could not resolve logo import for "${config.logo.light}" (logo.light)`;
17
+ }
18
+ }
19
+ if (err) throw new Error(err);
20
+ }
21
+ }
package/virtual.d.ts CHANGED
@@ -15,3 +15,40 @@ declare module 'virtual:starlight/user-images' {
15
15
  light?: ImageMetadata;
16
16
  };
17
17
  }
18
+
19
+ declare module 'virtual:starlight/components' {
20
+ export const Banner: typeof import('./components/Banner.astro').default;
21
+ export const ContentPanel: typeof import('./components/ContentPanel.astro').default;
22
+ export const PageTitle: typeof import('./components/PageTitle.astro').default;
23
+ export const FallbackContentNotice: typeof import('./components/FallbackContentNotice.astro').default;
24
+
25
+ export const Footer: typeof import('./components/Footer.astro').default;
26
+ export const LastUpdated: typeof import('./components/LastUpdated.astro').default;
27
+ export const Pagination: typeof import('./components/Pagination.astro').default;
28
+ export const EditLink: typeof import('./components/EditLink.astro').default;
29
+
30
+ export const Header: typeof import('./components/Header.astro').default;
31
+ export const LanguageSelect: typeof import('./components/LanguageSelect.astro').default;
32
+ export const Search: typeof import('./components/Search.astro').default;
33
+ export const SiteTitle: typeof import('./components/SiteTitle.astro').default;
34
+ export const SocialIcons: typeof import('./components/SocialIcons.astro').default;
35
+ export const ThemeSelect: typeof import('./components/ThemeSelect.astro').default;
36
+
37
+ export const Head: typeof import('./components/Head.astro').default;
38
+ export const Hero: typeof import('./components/Hero.astro').default;
39
+ export const MarkdownContent: typeof import('./components/MarkdownContent.astro').default;
40
+
41
+ export const PageSidebar: typeof import('./components/PageSidebar.astro').default;
42
+ export const TableOfContents: typeof import('./components/TableOfContents.astro').default;
43
+ export const MobileTableOfContents: typeof import('./components/MobileTableOfContents.astro').default;
44
+
45
+ export const Sidebar: typeof import('./components/Sidebar.astro').default;
46
+ export const SkipLink: typeof import('./components/SkipLink.astro').default;
47
+ export const ThemeProvider: typeof import('./components/ThemeProvider.astro').default;
48
+
49
+ export const PageFrame: typeof import('./components/PageFrame.astro').default;
50
+ export const MobileMenuToggle: typeof import('./components/MobileMenuToggle.astro').default;
51
+ export const MobileMenuFooter: typeof import('./components/MobileMenuFooter.astro').default;
52
+
53
+ export const TwoColumnContent: typeof import('./components/TwoColumnContent.astro').default;
54
+ }
@@ -1,36 +0,0 @@
1
- ---
2
- import type { MarkdownHeading } from 'astro';
3
- import RightSidebarPanel from './RightSidebarPanel.astro';
4
- import MobileTableOfContents from './TableOfContents/MobileTableOfContents.astro';
5
- import TableOfContents from './TableOfContents.astro';
6
- import { generateToC } from './TableOfContents/generateToC';
7
- import { useTranslations } from '../utils/translations';
8
-
9
- interface Props {
10
- headings: MarkdownHeading[];
11
- locale: string | undefined;
12
- tocConfig: { maxHeadingLevel: number; minHeadingLevel: number } | false;
13
- }
14
-
15
- const { headings, locale, tocConfig } = Astro.props;
16
- const t = useTranslations(locale);
17
- const tocProps = tocConfig && {
18
- ...tocConfig,
19
- locale,
20
- toc: generateToC(headings, {
21
- ...tocConfig,
22
- title: t('tableOfContents.overview'),
23
- }),
24
- };
25
- ---
26
-
27
- {
28
- tocProps && (
29
- <>
30
- <MobileTableOfContents {...tocProps} />
31
- <RightSidebarPanel>
32
- <TableOfContents {...tocProps} />
33
- </RightSidebarPanel>
34
- </>
35
- )
36
- }
package/layout/Page.astro DELETED
@@ -1,132 +0,0 @@
1
- ---
2
- import config from 'virtual:starlight/user-config';
3
- import type { MarkdownHeading } from 'astro';
4
- import { getSidebar } from '../utils/navigation';
5
- import type { Route } from '../utils/routing';
6
-
7
- // Built-in CSS styles.
8
- import '../style/props.css';
9
- import '../style/reset.css';
10
- import '../style/shiki.css';
11
- import '../style/util.css';
12
-
13
- // Components — can override built-in CSS, but not user CSS.
14
- import ContentPanel from '../components/ContentPanel.astro';
15
- import FallbackContentNotice from '../components/FallbackContentNotice.astro';
16
- import Footer from '../components/Footer.astro';
17
- import HeadSEO from '../components/HeadSEO.astro';
18
- import Header from '../components/Header.astro';
19
- import Hero from '../components/Hero.astro';
20
- import MarkdownContent from '../components/MarkdownContent.astro';
21
- import RightSidebar from '../components/RightSidebar.astro';
22
- import Sidebar from '../components/Sidebar.astro';
23
- import SkipLink from '../components/SkipLink.astro';
24
- import ThemeProvider from '../components/ThemeProvider.astro';
25
- import PageFrame from '../layout/PageFrame.astro';
26
- import TwoColumnContent from '../layout/TwoColumnContent.astro';
27
- import Banner from '../components/Banner.astro';
28
-
29
- // Remark component CSS (needs to override `MarkdownContent.astro`)
30
- import '../style/asides.css';
31
-
32
- // Important that this is the last import so it can override built-in styles.
33
- import 'virtual:starlight/user-css';
34
-
35
- type Props = Route & { headings: MarkdownHeading[] };
36
-
37
- const { dir, entry, entryMeta, headings, isFallback, lang, locale } = Astro.props;
38
- const sidebar = getSidebar(Astro.url.pathname, locale);
39
-
40
- const hasSidebar = entry.data.template !== 'splash';
41
- const tocConfig = !hasSidebar
42
- ? false
43
- : entry.data.tableOfContents !== undefined
44
- ? entry.data.tableOfContents
45
- : config.tableOfContents;
46
- const hasToC = Boolean(tocConfig);
47
- const hasHero = Boolean(entry.data.hero);
48
- const pagefindEnabled =
49
- entry.slug !== '404' && !entry.slug.endsWith('/404') && entry.data.pagefind !== false;
50
- ---
51
-
52
- <html
53
- lang={lang}
54
- dir={dir}
55
- data-has-toc={hasToC}
56
- data-has-sidebar={hasSidebar}
57
- data-has-hero={hasHero}
58
- >
59
- <head>
60
- <HeadSEO data={entry.data} lang={lang} />
61
- <style>
62
- html:not([data-has-toc]) {
63
- --sl-mobile-toc-height: 0rem;
64
- }
65
- html:not([data-has-sidebar]) {
66
- --sl-content-width: 67.5rem;
67
- }
68
- /* Add scroll padding to ensure anchor headings aren't obscured by nav */
69
- html {
70
- /* Additional padding is needed to account for the mobile TOC */
71
- scroll-padding-top: calc(1.5rem + var(--sl-nav-height) + var(--sl-mobile-toc-height));
72
- }
73
- main {
74
- padding-bottom: 3vh;
75
- }
76
- @media (min-width: 50em) {
77
- [data-has-sidebar] {
78
- --sl-content-inline-start: var(--sl-sidebar-width);
79
- }
80
- }
81
- @media (min-width: 72em) {
82
- html {
83
- scroll-padding-top: calc(1.5rem + var(--sl-nav-height));
84
- }
85
- }
86
- </style>
87
- </head>
88
- <body>
89
- <ThemeProvider />
90
- <SkipLink {locale} />
91
- <PageFrame {locale} {hasSidebar}>
92
- <Header slot="header" {locale} />
93
- {hasSidebar && <Sidebar slot="sidebar" {sidebar} {locale} />}
94
- <TwoColumnContent {hasToC}>
95
- <RightSidebar slot="right-sidebar" {headings} {locale} {tocConfig} />
96
- <main data-pagefind-body={pagefindEnabled} lang={entryMeta.lang} dir={entryMeta.dir}>
97
- {/* TODO: Revisit how this logic flows. */}
98
- {entry.data.banner && <Banner {...entry.data.banner} />}
99
- {
100
- entry.data.hero ? (
101
- <ContentPanel>
102
- <Hero hero={entry.data.hero} fallbackTitle={entry.data.title} />
103
- <MarkdownContent>
104
- <slot />
105
- </MarkdownContent>
106
- </ContentPanel>
107
- ) : (
108
- <>
109
- <ContentPanel>
110
- <h1
111
- id="_top"
112
- data-page-title
113
- style="font-size: var(--sl-text-h1); line-height: var(--sl-line-height-headings); font-weight: 600; color: var(--sl-color-white); margin-top: 1rem;"
114
- >
115
- {entry.data.title}
116
- </h1>
117
- {isFallback && <FallbackContentNotice {locale} />}
118
- </ContentPanel>
119
- <ContentPanel>
120
- <MarkdownContent>
121
- <slot />
122
- </MarkdownContent>
123
- <Footer {...{ entry, dir, lang, locale, sidebar }} />
124
- </ContentPanel>
125
- </>
126
- )
127
- }
128
- </main>
129
- </TwoColumnContent>
130
- </PageFrame>
131
- </body>
132
- </html>