@astrojs/starlight 0.28.6 → 0.29.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # @astrojs/starlight
2
2
 
3
+ ## 0.29.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#2611](https://github.com/withastro/starlight/pull/2611) [`6059d96`](https://github.com/withastro/starlight/commit/6059d961a6b31fd7848b0c59411fc6370e62abab) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes a UI string type issue in projects with multiple data content collections.
8
+
9
+ - [#2606](https://github.com/withastro/starlight/pull/2606) [`10b15a7`](https://github.com/withastro/starlight/commit/10b15a7fcda4805dda622f893b6671b96a349161) Thanks [@delucis](https://github.com/delucis)! - Makes `<CardGrid>` more resilient to complex child content on smaller viewports
10
+
11
+ - [#2605](https://github.com/withastro/starlight/pull/2605) [`ec7ab4f`](https://github.com/withastro/starlight/commit/ec7ab4f1df83052aa00c5f560adf7e7b8d61c9db) Thanks [@brianzelip](https://github.com/brianzelip)! - Exposes `SidebarPersister` component in package exports for use in custom overrides
12
+
13
+ - [#2614](https://github.com/withastro/starlight/pull/2614) [`9a31980`](https://github.com/withastro/starlight/commit/9a319807c698f65b461f456c54c413081ab551f0) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes an issue with custom pages using the `<StarlightPage />` component and a custom sidebar missing highlighting for the active page and navigation links.
14
+
15
+ - [#2600](https://github.com/withastro/starlight/pull/2600) [`49aef17`](https://github.com/withastro/starlight/commit/49aef173811d05ae132729c92c3920a142ceeb7d) Thanks [@jdevega](https://github.com/jdevega)! - Adds Backstage social icon
16
+
17
+ - [#2613](https://github.com/withastro/starlight/pull/2613) [`a73780f`](https://github.com/withastro/starlight/commit/a73780fb280feb5b96ffa94539633b2930bd8e76) Thanks [@delucis](https://github.com/delucis)! - Fixes support for `sidebar` frontmatter options in sidebar entries using `slug` or the string shorthand for internal links
18
+
19
+ ## 0.29.0
20
+
21
+ ### Minor Changes
22
+
23
+ - [#2551](https://github.com/withastro/starlight/pull/2551) [`154c8e3`](https://github.com/withastro/starlight/commit/154c8e337819c7d7ebf4ff4dd55db0d10607620b) Thanks [@hippotastic](https://github.com/hippotastic)! - Updates the `astro-expressive-code` dependency to the latest version (0.38).
24
+
25
+ The new version allows using `ec.config.mjs` to selectively override individual Expressive Code styles and settings provided by Starlight themes and plugins, speeds up Shiki language loading, and adds the config option `expressiveCode.shiki.injectLangsIntoNestedCodeBlocks`. See the [Expressive Code release notes](https://expressive-code.com/releases/#0380) for full details.
26
+
27
+ - [#2252](https://github.com/withastro/starlight/pull/2252) [`6116db0`](https://github.com/withastro/starlight/commit/6116db03a4157c0f0caa210690ef0dcdd001a287) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Improves build performance for sites with large sidebars
28
+
29
+ This release adds a caching layer to Starlight’s sidebar generation logic, reducing the number of times sidebars need to be regenerated while building a site. Some benchmarks for projects with a complex sidebar saw builds complete more than 35% faster with this change.
30
+
31
+ - [#2503](https://github.com/withastro/starlight/pull/2503) [`a4c8edd`](https://github.com/withastro/starlight/commit/a4c8eddc53993068c0f60159fecc123013827ef0) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Improves the accessibility of asides and tabs by removing some unnecessary HTML landmarks.
32
+
33
+ ### Patch Changes
34
+
35
+ - [#2579](https://github.com/withastro/starlight/pull/2579) [`241966b`](https://github.com/withastro/starlight/commit/241966bd0523a2e82891d622b2a97b2f3646bc41) Thanks [@RafidMuhymin](https://github.com/RafidMuhymin)! - Adds social link icon for Nostr
36
+
3
37
  ## 0.28.6
4
38
 
5
39
  ### Patch Changes
@@ -158,6 +158,10 @@ export const BuiltInIcons = {
158
158
  '<path d="M6.49 19.04h-.23L5.13 17.9v-.23l1.73-1.71h1.2l.15.15v1.2L6.5 19.04ZM5.13 6.31V6.1l1.13-1.13h.23L8.2 6.68v1.2l-.15.15h-1.2zm9.96 9.09h-1.65l-.14-.13v-3.83c0-.68-.27-1.2-1.1-1.23c-.42 0-.9 0-1.43.02l-.07.08v4.96l-.14.14H8.9l-.13-.14V8.73l.13-.14h3.7a2.6 2.6 0 0 1 2.61 2.6v4.08l-.13.14Zm-8.37-2.44H.14L0 12.82v-1.64l.14-.14h6.58l.14.14v1.64zm17.14 0h-6.58l-.14-.14v-1.64l.14-.14h6.58l.14.14v1.64zM11.05 6.55V1.64l.14-.14h1.65l.14.14v4.9l-.14.14h-1.65zm0 15.81v-4.9l.14-.14h1.65l.14.13v4.91l-.14.14h-1.65z"/>',
159
159
  deno: '<path d="M12 0a12 12 0 1 1 0 24 12 12 0 0 1 0-24m-.47 6.8c-3.49 0-6.2 2.19-6.2 4.92 0 2.58 2.5 4.23 6.37 4.15h.12l.42-.02-.1.28v.03l.09.22v.03l.02.04.02.07.02.04.01.05.02.05.02.07.02.08.02.06.02.08.02.09.02.09.03.1.01.06.03.1.02.1.03.15.02.07.02.11.03.12.02.12.04.17.02.15.04.2.02.1.03.15.03.15.04.22.04.23.04.23.04.24.04.24.04.26.04.26.04.2.05.34.02.14.06.36.04.3.04.22.04.31.03.16a10.76 10.76 0 0 0 6.53-3.41l.05-.06-.24-.89-.64-2.37-.39-1.47-.35-1.3-.21-.78-.14-.5-.08-.3-.07-.26-.03-.11-.02-.07-.01-.03v-.03a6.04 6.04 0 0 0-2.05-2.97 6.75 6.75 0 0 0-4.25-1.35M8.47 19.3a.59.59 0 0 0-.72.4v.01l-.53 1.96q.5.24 1.01.43l.08.03.57-2.11V20a.59.59 0 0 0-.41-.7m3.26-1.43a.59.59 0 0 0-.71.4v.01l-.8 2.96v.01a.59.59 0 0 0 1.12.3l.8-2.96v-.02l.02-.06v-.02l-.02-.1-.02-.14-.02-.08a.58.58 0 0 0-.37-.3Zm-5.55-3.04a1 1 0 0 0-.04.09v.02l-.8 2.95v.02a.59.59 0 0 0 1.13.3v-.01l.72-2.68a5.3 5.3 0 0 1-1.01-.7Zm-1.9-3.4a.59.59 0 0 0-.72.4v.02l-.8 2.95v.01a.59.59 0 0 0 1.13.3l.8-2.96v-.01a.59.59 0 0 0-.41-.7m17.87-.68a.59.59 0 0 0-.72.4v.02l-.8 2.95v.01a.59.59 0 0 0 1.13.3l.8-2.96v-.01a.59.59 0 0 0-.41-.7M2.55 6.81a10.7 10.7 0 0 0-1.26 3.93.59.59 0 0 0 1-.22v-.02l.8-2.95v-.01a.59.59 0 0 0-.55-.73m17.59.02a.59.59 0 0 0-.72.4v.01l-.8 2.96v.01a.59.59 0 0 0 1.13.3l.8-2.96v-.01a.59.59 0 0 0-.41-.7Zm-7.85 1.93a.75.75 0 1 1 0 1.5.75.75 0 0 1 0-1.5M6.01 4.03a.59.59 0 0 0-.71.4v.02L4.5 7.4v.01a.59.59 0 0 0 1.12.3l.8-2.96v-.01a.59.59 0 0 0-.41-.7Zm10.24.56a.59.59 0 0 0-.71.4V5L15 7q.52.26.99.6l.05.04.62-2.32V5.3a.59.59 0 0 0-.41-.7m-5.21-3.34a11 11 0 0 0-1.12.16l-.07.01L9.1 4.2v.01a.59.59 0 0 0 1.13.3l.8-2.96v-.01a.6.6 0 0 0 0-.27m7.34 2.04-.16.58v.02a.59.59 0 0 0 1.13.3V4.2l.02-.07a11 11 0 0 0-.92-.77zm-4.64-1.94-.28 1.05a.59.59 0 0 0 1.13.31v-.01l.3-1.1q-.52-.15-1.06-.24z"/>',
160
160
  jsr: '<path d="M3.7 5.54v3.7H0v7.38h7.38v1.84h12.93v-3.7H24V7.39h-7.38V5.54Zm1.84 1.85h1.85v7.38H1.84v-3.7h1.84v1.85h1.85zm3.7 0h5.53v1.84h-3.7v1.85h3.7v5.53H9.23v-1.84h3.7v-1.85h-3.7Zm7.38 1.84h5.53v3.7h-1.84v-1.85h-1.85v5.54h-1.84z"/>',
161
+ nostr:
162
+ '<path d="M21.6 10.6v9.7a.7.7 0 0 1-.6.6h-8a.7.7 0 0 1-.6-.6v-1.8c0-2.2.3-4.4.8-5.3a2 2 0 0 1 1.3-1c1-.4 2.9-.2 3.7-.2 0 0 2.3 0 2.3-1.3 0-1-1-1-1-1-1.2 0-2 0-2.6-.2-1-.4-1-1.1-1-1.3 0-2.7-4-3-7.5-2.4-3.9.7 0 6.2 0 13.5v1a.7.7 0 0 1-.7.6H3.8a.7.7 0 0 1-.6-.6V3.5a.7.7 0 0 1 .6-.6h3.7a.7.7 0 0 1 .7.6c0 .6.6.9 1 .6 1.3-1 3-1.5 5-1.5 4.2 0 7.4 2.5 7.4 8zm-7-2a1.4 1.4 0 1 0-2.9 0 1.4 1.4 0 0 0 2.9 0z"/>',
163
+ backstage:
164
+ '<path d="M18.4 9.3a4.4 4.4 0 0 0 .7-.5l.1-.1a4.5 4.5 0 0 0 .8-1.1l.3-1c0-1-.6-2-2-3L12.7.4 6 6.6l-4.3 4 6 3.7a6.1 6.1 0 0 0 3 .9c1.5 0 2.8-.5 3.7-1.4 1-1 1.4-2.3.8-3.4a2.8 2.8 0 0 0-.4-.5l1 .1a4.6 4.6 0 0 0 1.8-.3 4.5 4.5 0 0 0 .7-.3zm-5.5 3.3c-1 1-2.8 1.2-4.2.4l-4.1-2.5 3.7-3.6 4.2 2.6c1.5.9 1.4 2.1.4 3.1zm.5-4.5-4-2.3L13 2.4l3.8 2.3c1.4.9 1.6 2 .6 3a3.3 3.3 0 0 1-4 .4zM15 18.5c-1 1-2.5 1.6-4.1 1.6a6.8 6.8 0 0 1-3.5-1l-5.6-3.4v1.4l6 3.6a6.1 6.1 0 0 0 3 .9c1.5 0 2.8-.5 3.7-1.4.7-.7 1.1-1.5 1.1-2.3l-.6.6zm0-2.1c-1 1-2.5 1.6-4.1 1.6a6.8 6.8 0 0 1-3.5-1l-5.6-3.4V15l6 3.6a6.1 6.1 0 0 0 3 .9c1.5 0 2.8-.5 3.7-1.4.7-.7 1.1-1.5 1.1-2.3v-.1l-.6.7zm0-2.1c-1 1-2.5 1.6-4.1 1.6a6.8 6.8 0 0 1-3.5-1l-5.6-3.4v1.3l6 3.6a6.1 6.1 0 0 0 3 1c1.5 0 2.8-.6 3.7-1.5.7-.7 1.1-1.5 1.1-2.3l-.6.7zm4.6 1.4a5.2 5.2 0 0 1-3.3 1.4v1.5a4.5 4.5 0 0 0 2.8-1.3c.8-.7 1.2-1.5 1.2-2.3v-.1l-.7.8zm-4.6 5c-1 1-2.5 1.6-4.1 1.6a6.8 6.8 0 0 1-3.5-1l-5.6-3.4v1.3l6 3.6a6.1 6.1 0 0 0 3 1c1.5 0 2.8-.5 3.7-1.5.7-.6 1.1-1.5 1.1-2.2V20l-.6.7zM19.7 9l-.1.2-1.2.8a5.2 5.2 0 0 1-1.5.5 5.2 5.2 0 0 1-.8 0l.1.4V12a4.6 4.6 0 0 0 1.5-.3A4.4 4.4 0 0 0 19 11l.1-.1a4.5 4.5 0 0 0 .8-1.1 2.6 2.6 0 0 0 .3-1.1v-.1l-.2.1-.4.6zm0 4.3a6 6 0 0 1-.1.1 5.2 5.2 0 0 1-3.3 1.5v1.4a4.5 4.5 0 0 0 2.8-1.2l1-1.2.2-1v-.1l-.2.2a4.8 4.8 0 0 1-.4.5zm0-2.1-.1.1a4.8 4.8 0 0 1-.6.5 5.2 5.2 0 0 1-2.7 1v1.4A4.5 4.5 0 0 0 19 13h.1a4.2 4.2 0 0 0 .8-1.2l.3-.9v-.3a4 4 0 0 1-.2.2l-.4.5z"/>',
161
165
  };
162
166
 
163
167
  export const Icons = {
@@ -107,10 +107,10 @@ function transformUnhandledDirective(
107
107
  * ```astro
108
108
  * <aside class="starlight-aside starlight-aside--tip" aria-label="Did you know?">
109
109
  * <p class="starlight-aside__title" aria-hidden="true">Did you know?</p>
110
- * <section class="starlight-aside__content">
110
+ * <div class="starlight-aside__content">
111
111
  * <p>Astro helps you build faster websites with “Islands Architecture”.</p>
112
- * </section>
113
- * </Aside>
112
+ * </div>
113
+ * </aside>
114
114
  * ```
115
115
  */
116
116
  function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
@@ -204,7 +204,7 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
204
204
  ),
205
205
  ...titleNode,
206
206
  ]),
207
- h('section', { class: 'starlight-aside__content' }, node.children),
207
+ h('div', { class: 'starlight-aside__content' }, node.children),
208
208
  ]
209
209
  );
210
210
 
@@ -8,6 +8,7 @@ import type { AstroIntegration } from 'astro';
8
8
  import type { StarlightConfig } from '../../types';
9
9
  import type { createTranslationSystemFromFs } from '../../utils/translations-fs';
10
10
  import { pathToLocale } from '../shared/pathToLocale';
11
+ import { slugToLocale } from '../shared/slugToLocale';
11
12
  import {
12
13
  applyStarlightUiThemeColors,
13
14
  preprocessThemes,
@@ -152,7 +153,10 @@ export function getStarlightEcConfigPreprocessor({
152
153
  },
153
154
  ...otherStyleOverrides,
154
155
  },
155
- getBlockLocale: ({ file }) => pathToLocale(file.path, { starlightConfig, astroConfig }),
156
+ getBlockLocale: ({ file }) =>
157
+ file.url
158
+ ? slugToLocale(file.url.pathname.slice(1), starlightConfig)
159
+ : pathToLocale(file.path, { starlightConfig, astroConfig }),
156
160
  plugins,
157
161
  ...rest,
158
162
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/starlight",
3
- "version": "0.28.6",
3
+ "version": "0.29.1",
4
4
  "description": "Build beautiful, high-performance documentation websites with Astro",
5
5
  "keywords": [
6
6
  "docs",
@@ -47,6 +47,10 @@
47
47
  "types": "./components/MarkdownContent.astro.tsx",
48
48
  "import": "./components/MarkdownContent.astro"
49
49
  },
50
+ "./components/SidebarPersister.astro": {
51
+ "types": "./components/SidebarPersister.astro.tsx",
52
+ "import": "./components/SidebarPersister.astro"
53
+ },
50
54
  "./components/SidebarSublist.astro": {
51
55
  "types": "./components/SidebarSublist.astro.tsx",
52
56
  "import": "./components/SidebarSublist.astro"
@@ -170,14 +174,14 @@
170
174
  "astro": "^4.14.0"
171
175
  },
172
176
  "devDependencies": {
173
- "@astrojs/markdown-remark": "^5.1.0",
177
+ "@astrojs/markdown-remark": "^5.3.0",
174
178
  "@playwright/test": "^1.45.0",
175
179
  "@types/js-yaml": "^4.0.9",
176
180
  "@types/node": "^18.16.19",
177
181
  "@vitest/coverage-v8": "^1.6.0",
178
- "astro": "^4.15.3",
179
- "vitest": "^1.6.0",
180
- "linkedom": "^0.18.4"
182
+ "astro": "^4.16.10",
183
+ "linkedom": "^0.18.4",
184
+ "vitest": "^1.6.0"
181
185
  },
182
186
  "dependencies": {
183
187
  "@astrojs/mdx": "^3.1.3",
@@ -185,7 +189,7 @@
185
189
  "@pagefind/default-ui": "^1.0.3",
186
190
  "@types/hast": "^3.0.4",
187
191
  "@types/mdast": "^4.0.4",
188
- "astro-expressive-code": "^0.35.6",
192
+ "astro-expressive-code": "^0.38.3",
189
193
  "bcp-47": "^2.1.0",
190
194
  "hast-util-from-html": "^2.0.1",
191
195
  "hast-util-select": "^6.0.2",
package/schemas/social.ts CHANGED
@@ -35,6 +35,8 @@ export const socialLinks = [
35
35
  'zulip',
36
36
  'pinterest',
37
37
  'tiktok',
38
+ 'nostr',
39
+ 'backstage',
38
40
  ] as const;
39
41
 
40
42
  export const SocialLinksSchema = () =>
@@ -85,6 +87,8 @@ export const SocialLinksSchema = () =>
85
87
  zulip: 'Zulip',
86
88
  pinterest: 'Pinterest',
87
89
  tiktok: 'TikTok',
90
+ nostr: 'Nostr',
91
+ backstage: 'Backstage',
88
92
  }[key];
89
93
  labelledLinks[key] = { label, url };
90
94
  }
@@ -29,7 +29,7 @@ if (!title) {
29
29
  <p class="starlight-aside__title" aria-hidden="true">
30
30
  <Icon name={icons[type]} class="starlight-aside__icon" />{title}
31
31
  </p>
32
- <section class="starlight-aside__content">
32
+ <div class="starlight-aside__content">
33
33
  <slot />
34
- </section>
34
+ </div>
35
35
  </aside>
@@ -11,6 +11,7 @@ const { stagger = false } = Astro.props;
11
11
  <style>
12
12
  .card-grid {
13
13
  display: grid;
14
+ grid-template-columns: 100%;
14
15
  gap: 1rem;
15
16
  }
16
17
 
@@ -47,7 +47,7 @@ const getIDs = () => {
47
47
 
48
48
  /**
49
49
  * Rehype processor to extract tab panel data and turn each
50
- * `<starlight-tab-item>` into a `<section>` with the necessary
50
+ * `<starlight-tab-item>` into a `<div>` with the necessary
51
51
  * attributes.
52
52
  */
53
53
  const tabsProcessor = rehype()
@@ -73,8 +73,8 @@ const tabsProcessor = rehype()
73
73
  // Remove `<TabItem>` props
74
74
  delete node.properties.dataLabel;
75
75
  delete node.properties.dataIcon;
76
- // Turn into `<section>` with required attributes
77
- node.tagName = 'section';
76
+ // Turn into `<div>` with required attributes
77
+ node.tagName = 'div';
78
78
  node.properties.id = ids.panelId;
79
79
  node.properties['aria-labelledby'] = ids.tabId;
80
80
  node.properties.role = 'tabpanel';
@@ -121,7 +121,11 @@ function buildResources<T extends Record<string, string | undefined>>(
121
121
  return { [I18nextNamespace]: dictionary as BuiltInStrings & T };
122
122
  }
123
123
 
124
- export type I18nKeys = UserI18nKeys | keyof StarlightApp.I18n;
124
+ // `keyof BuiltInStrings` and `UserI18nKeys` may contain some identical keys, e.g. the built-in UI
125
+ // strings. We let TypeScript merge them into a single union type so that plugins with a TypeScript
126
+ // configuration preventing `UserI18nKeys` to be properly inferred can still get auto-completion
127
+ // for built-in UI strings.
128
+ export type I18nKeys = keyof BuiltInStrings | UserI18nKeys | keyof StarlightApp.I18n;
125
129
 
126
130
  export type I18nT = TFunction<'starlight', undefined> & {
127
131
  all: () => UserI18nSchema;
package/utils/i18n.ts CHANGED
@@ -79,7 +79,7 @@ function getAstroI18nConfig(config: StarlightConfig): NonNullable<AstroConfig['i
79
79
  ? Object.entries(config.locales).map(([locale, localeConfig]) => {
80
80
  return {
81
81
  codes: [localeConfig?.lang ?? locale],
82
- path: locale === 'root' ? localeConfig?.lang ?? BuiltInDefaultLocale.lang : locale,
82
+ path: locale === 'root' ? (localeConfig?.lang ?? BuiltInDefaultLocale.lang) : locale,
83
83
  };
84
84
  })
85
85
  : [config.defaultLocale.lang],
@@ -20,6 +20,8 @@ import type { StarlightConfig } from './user-config';
20
20
  const DirKey = Symbol('DirKey');
21
21
  const SlugKey = Symbol('SlugKey');
22
22
 
23
+ const neverPathFormatter = createPathFormatter({ trailingSlash: 'never' });
24
+
23
25
  export interface Link {
24
26
  type: 'link';
25
27
  label: string;
@@ -73,11 +75,11 @@ function configItemToEntry(
73
75
  routes: Route[]
74
76
  ): SidebarEntry {
75
77
  if ('link' in item) {
76
- return linkFromSidebarLinkItem(item, locale, currentPathname);
78
+ return linkFromSidebarLinkItem(item, locale);
77
79
  } else if ('autogenerate' in item) {
78
80
  return groupFromAutogenerateConfig(item, locale, routes, currentPathname);
79
81
  } else if ('slug' in item) {
80
- return linkFromInternalSidebarLinkItem(item, locale, currentPathname);
82
+ return linkFromInternalSidebarLinkItem(item, locale);
81
83
  } else {
82
84
  const label = pickLang(item.translations, localeToLang(locale)) || item.label;
83
85
  return {
@@ -121,11 +123,7 @@ function groupFromAutogenerateConfig(
121
123
  const isAbsolute = (link: string) => /^https?:\/\//.test(link);
122
124
 
123
125
  /** Create a link entry from a manual link item in user config. */
124
- function linkFromSidebarLinkItem(
125
- item: SidebarLinkItem,
126
- locale: string | undefined,
127
- currentPathname: string
128
- ) {
126
+ function linkFromSidebarLinkItem(item: SidebarLinkItem, locale: string | undefined) {
129
127
  let href = item.link;
130
128
  if (!isAbsolute(href)) {
131
129
  href = ensureLeadingSlash(href);
@@ -133,26 +131,19 @@ function linkFromSidebarLinkItem(
133
131
  if (locale) href = '/' + locale + href;
134
132
  }
135
133
  const label = pickLang(item.translations, localeToLang(locale)) || item.label;
136
- return makeSidebarLink(
137
- href,
138
- label,
139
- currentPathname,
140
- getSidebarBadge(item.badge, locale, label),
141
- item.attrs
142
- );
134
+ return makeSidebarLink(href, label, getSidebarBadge(item.badge, locale, label), item.attrs);
143
135
  }
144
136
 
145
137
  /** Create a link entry from an automatic internal link item in user config. */
146
138
  function linkFromInternalSidebarLinkItem(
147
139
  item: InternalSidebarLinkItem,
148
- locale: string | undefined,
149
- currentPathname: string
140
+ locale: string | undefined
150
141
  ) {
151
142
  // Astro passes root `index.[md|mdx]` entries with a slug of `index`
152
143
  const slug = item.slug === 'index' ? '' : item.slug;
153
144
  const localizedSlug = locale ? (slug ? locale + '/' + slug : locale) : slug;
154
- const entry = routes.find((entry) => localizedSlug === entry.slug);
155
- if (!entry) {
145
+ const route = routes.find((entry) => localizedSlug === entry.slug);
146
+ if (!route) {
156
147
  const hasExternalSlashes = item.slug.at(0) === '/' || item.slug.at(-1) === '/';
157
148
  if (hasExternalSlashes) {
158
149
  throw new AstroError(
@@ -167,52 +158,47 @@ function linkFromInternalSidebarLinkItem(
167
158
  );
168
159
  }
169
160
  }
161
+ const frontmatter = route.entry.data;
170
162
  const label =
171
- pickLang(item.translations, localeToLang(locale)) || item.label || entry.entry.data.title;
172
- return makeSidebarLink(
173
- entry.slug,
174
- label,
175
- currentPathname,
176
- getSidebarBadge(item.badge, locale, label),
177
- item.attrs
178
- );
163
+ pickLang(item.translations, localeToLang(locale)) ||
164
+ item.label ||
165
+ frontmatter.sidebar?.label ||
166
+ frontmatter.title;
167
+ const badge = item.badge ?? frontmatter.sidebar?.badge;
168
+ const attrs = { ...frontmatter.sidebar?.attrs, ...item.attrs };
169
+ return makeSidebarLink(route.slug, label, getSidebarBadge(badge, locale, label), attrs);
179
170
  }
180
171
 
181
172
  /** Process sidebar link options to create a link entry. */
182
173
  function makeSidebarLink(
183
174
  href: string,
184
175
  label: string,
185
- currentPathname: string,
186
176
  badge?: Badge,
187
177
  attrs?: LinkHTMLAttributes
188
178
  ): Link {
189
179
  if (!isAbsolute(href)) {
190
180
  href = formatPath(href);
191
181
  }
192
- const isCurrent = pathsMatch(encodeURI(href), currentPathname);
193
- return makeLink({ label, href, isCurrent, badge, attrs });
182
+ return makeLink({ label, href, badge, attrs });
194
183
  }
195
184
 
196
185
  /** Create a link entry */
197
186
  function makeLink({
198
- isCurrent = false,
199
187
  attrs = {},
200
188
  badge = undefined,
201
189
  ...opts
202
190
  }: {
203
191
  label: string;
204
192
  href: string;
205
- isCurrent?: boolean;
206
193
  badge?: Badge | undefined;
207
194
  attrs?: LinkHTMLAttributes | undefined;
208
195
  }): Link {
209
- return { type: 'link', ...opts, badge, isCurrent, attrs };
196
+ return { type: 'link', ...opts, badge, isCurrent: false, attrs };
210
197
  }
211
198
 
212
199
  /** Test if two paths are equivalent even if formatted differently. */
213
200
  function pathsMatch(pathA: string, pathB: string) {
214
- const format = createPathFormatter({ trailingSlash: 'never' });
215
- return format(pathA) === format(pathB);
201
+ return neverPathFormatter(pathA) === neverPathFormatter(pathB);
216
202
  }
217
203
 
218
204
  /** Get the segments leading to a page. */
@@ -268,11 +254,10 @@ function treeify(routes: Route[], baseDir: string): Dir {
268
254
  }
269
255
 
270
256
  /** Create a link entry for a given content collection entry. */
271
- function linkFromRoute(route: Route, currentPathname: string): Link {
257
+ function linkFromRoute(route: Route): Link {
272
258
  return makeSidebarLink(
273
259
  slugToPathname(route.slug),
274
260
  route.entry.data.sidebar.label || route.entry.data.title,
275
- currentPathname,
276
261
  route.entry.data.sidebar.badge,
277
262
  route.entry.data.sidebar.attrs
278
263
  );
@@ -286,7 +271,7 @@ function getOrder(routeOrDir: Route | Dir): number {
286
271
  return isDir(routeOrDir)
287
272
  ? Math.min(...Object.values(routeOrDir).flatMap(getOrder))
288
273
  : // If no order value is found, set it to the largest number possible.
289
- routeOrDir.entry.data.sidebar.order ?? Number.MAX_VALUE;
274
+ (routeOrDir.entry.data.sidebar.order ?? Number.MAX_VALUE);
290
275
  }
291
276
 
292
277
  /** Sort a directory’s entries by user-specified order or alphabetically if no order specified. */
@@ -333,7 +318,7 @@ function dirToItem(
333
318
  ): SidebarEntry {
334
319
  return isDir(dirOrRoute)
335
320
  ? groupFromDir(dirOrRoute, fullPath, dirName, currentPathname, locale, collapsed)
336
- : linkFromRoute(dirOrRoute, currentPathname);
321
+ : linkFromRoute(dirOrRoute);
337
322
  }
338
323
 
339
324
  /** Create a sidebar entry for a given content directory. */
@@ -348,9 +333,25 @@ function sidebarFromDir(
348
333
  );
349
334
  }
350
335
 
336
+ /**
337
+ * Intermediate sidebar represents sidebar entries generated from the user config for a specific
338
+ * locale and do not contain any information about the current page.
339
+ * These representations are cached per locale to avoid regenerating them for each page.
340
+ * When generating the final sidebar for a page, the intermediate sidebar is cloned and the current
341
+ * page is marked as such.
342
+ *
343
+ * @see getSidebarFromIntermediateSidebar
344
+ */
345
+ const intermediateSidebars = new Map<string | undefined, SidebarEntry[]>();
346
+
351
347
  /** Get the sidebar for the current page using the global config. */
352
348
  export function getSidebar(pathname: string, locale: string | undefined): SidebarEntry[] {
353
- return getSidebarFromConfig(config.sidebar, pathname, locale);
349
+ let intermediateSidebar = intermediateSidebars.get(locale);
350
+ if (!intermediateSidebar) {
351
+ intermediateSidebar = getIntermediateSidebarFromConfig(config.sidebar, pathname, locale);
352
+ intermediateSidebars.set(locale, intermediateSidebar);
353
+ }
354
+ return getSidebarFromIntermediateSidebar(intermediateSidebar, pathname);
354
355
  }
355
356
 
356
357
  /** Get the sidebar for the current page using the specified sidebar config. */
@@ -358,6 +359,16 @@ export function getSidebarFromConfig(
358
359
  sidebarConfig: StarlightConfig['sidebar'],
359
360
  pathname: string,
360
361
  locale: string | undefined
362
+ ): SidebarEntry[] {
363
+ const intermediateSidebar = getIntermediateSidebarFromConfig(sidebarConfig, pathname, locale);
364
+ return getSidebarFromIntermediateSidebar(intermediateSidebar, pathname);
365
+ }
366
+
367
+ /** Get the intermediate sidebar for the current page using the specified sidebar config. */
368
+ function getIntermediateSidebarFromConfig(
369
+ sidebarConfig: StarlightConfig['sidebar'],
370
+ pathname: string,
371
+ locale: string | undefined
361
372
  ): SidebarEntry[] {
362
373
  const routes = getLocaleRoutes(locale);
363
374
  if (sidebarConfig) {
@@ -368,6 +379,34 @@ export function getSidebarFromConfig(
368
379
  }
369
380
  }
370
381
 
382
+ /** Transform an intermediate sidebar into a sidebar for the current page. */
383
+ function getSidebarFromIntermediateSidebar(
384
+ intermediateSidebar: SidebarEntry[],
385
+ pathname: string
386
+ ): SidebarEntry[] {
387
+ const sidebar = structuredClone(intermediateSidebar);
388
+ setIntermediateSidebarCurrentEntry(sidebar, pathname);
389
+ return sidebar;
390
+ }
391
+
392
+ /** Marks the current page as such in an intermediate sidebar. */
393
+ function setIntermediateSidebarCurrentEntry(
394
+ intermediateSidebar: SidebarEntry[],
395
+ pathname: string
396
+ ): boolean {
397
+ for (const entry of intermediateSidebar) {
398
+ if (entry.type === 'link' && pathsMatch(encodeURI(entry.href), pathname)) {
399
+ entry.isCurrent = true;
400
+ return true;
401
+ }
402
+
403
+ if (entry.type === 'group' && setIntermediateSidebarCurrentEntry(entry.entries, pathname)) {
404
+ return true;
405
+ }
406
+ }
407
+ return false;
408
+ }
409
+
371
410
  /** Generates a deterministic string based on the content of the passed sidebar. */
372
411
  export function getSidebarHash(sidebar: SidebarEntry[]): string {
373
412
  let hash = 0;
@@ -12,7 +12,7 @@ import {
12
12
  } from './route-data';
13
13
  import type { StarlightDocsEntry } from './routing';
14
14
  import { slugToLocaleData, urlToSlug } from './slugs';
15
- import { getPrevNextLinks, getSidebarFromConfig } from './navigation';
15
+ import { getPrevNextLinks, getSidebar, getSidebarFromConfig } from './navigation';
16
16
  import { docsSchema } from '../schema';
17
17
  import type { Prettify, RemoveIndexSignature } from './types';
18
18
  import { DeprecatedLabelsPropProxy } from './i18n';
@@ -115,11 +115,9 @@ export async function generateStarlightPageRouteData({
115
115
  const pageFrontmatter = await getStarlightPageFrontmatter(frontmatter);
116
116
  const id = `${stripLeadingAndTrailingSlashes(slug)}.md`;
117
117
  const localeData = slugToLocaleData(slug);
118
- const sidebar = getSidebarFromConfig(
119
- props.sidebar ? validateSidebarProp(props.sidebar) : config.sidebar,
120
- url.pathname,
121
- localeData.locale
122
- );
118
+ const sidebar = props.sidebar
119
+ ? getSidebarFromConfig(validateSidebarProp(props.sidebar), url.pathname, localeData.locale)
120
+ : getSidebar(url.pathname, localeData.locale);
123
121
  const headings = props.headings ?? [];
124
122
  const pageDocsEntry: StarlightPageDocsEntry = {
125
123
  id,
@@ -5,8 +5,13 @@ import type { i18nSchemaOutput } from '../schemas/i18n';
5
5
  import { createTranslationSystem } from './createTranslationSystem';
6
6
  import type { RemoveIndexSignature } from './types';
7
7
 
8
+ // @ts-ignore - This may be a type error in projects without an i18n collection and running
9
+ // `tsc --noEmit` in their project. Note that it is not possible to inline this type in
10
+ // `UserI18nSchema` because this would break types for users having multiple data collections.
11
+ type i18nCollection = CollectionEntry<'i18n'>;
12
+
8
13
  export type UserI18nSchema = 'i18n' extends DataCollectionKey
9
- ? CollectionEntry<'i18n'> extends { data: infer T }
14
+ ? i18nCollection extends { data: infer T }
10
15
  ? i18nSchemaOutput & T
11
16
  : i18nSchemaOutput
12
17
  : i18nSchemaOutput;