@astrojs/starlight 0.34.7 → 0.35.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.
- package/CHANGELOG.md +40 -0
- package/components/Banner.astro +1 -1
- package/index.ts +1 -1
- package/integrations/asides.ts +48 -3
- package/integrations/code-rtl-support.ts +13 -2
- package/integrations/heading-links.ts +3 -14
- package/integrations/remark-rehype-utils.ts +38 -0
- package/loaders.ts +28 -5
- package/package.json +1 -1
- package/schemas/sidebar.ts +11 -1
- package/user-components/Aside.astro +7 -2
- package/utils/i18n.ts +4 -1
- package/utils/navigation.ts +20 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# @astrojs/starlight
|
|
2
2
|
|
|
3
|
+
## 0.35.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#2261](https://github.com/withastro/starlight/pull/2261) [`778b743`](https://github.com/withastro/starlight/commit/778b743cdb832551ed576c745728358d8bbf9d7a) Thanks [@shubham-padia](https://github.com/shubham-padia)! - Adds support for using any of Starlight’s built-in icons in asides.
|
|
8
|
+
|
|
9
|
+
- [#3272](https://github.com/withastro/starlight/pull/3272) [`e7fe267`](https://github.com/withastro/starlight/commit/e7fe26720b7527c39e3aab42ea9fd0468f92167c) Thanks [@delucis](https://github.com/delucis)! - Adds a new `generateId` option to Starlight’s `docsLoader()`
|
|
10
|
+
|
|
11
|
+
This enables overriding the default sluggifier used to convert content filenames to URLs.
|
|
12
|
+
|
|
13
|
+
- [#3276](https://github.com/withastro/starlight/pull/3276) [`3917b20`](https://github.com/withastro/starlight/commit/3917b206da26522f73bbe0c1120de9acae5972c5) Thanks [@delucis](https://github.com/delucis)! - Excludes banner content from search results
|
|
14
|
+
|
|
15
|
+
Previously, content set in [`banner`](https://starlight.astro.build/reference/frontmatter/#banner) in page frontmatter was indexed by Starlight’s default search provider Pagefind. This could cause unexpected search results, especially for sites setting a common banner content on multiple pages. Starlight’s default `Banner` component is now excluded from search indexing.
|
|
16
|
+
|
|
17
|
+
This change does not impact `Banner` overrides using custom components.
|
|
18
|
+
|
|
19
|
+
- [#3266](https://github.com/withastro/starlight/pull/3266) [`1161af0`](https://github.com/withastro/starlight/commit/1161af0c2fe26485da6123f8fd7205c53b0e45e5) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Adds support for custom HTML attributes on autogenerated sidebar links using the [`autogenerate.attrs`](https://starlight.astro.build/guides/sidebar/#custom-html-attributes-for-autogenerated-links) option.
|
|
20
|
+
|
|
21
|
+
- [#3274](https://github.com/withastro/starlight/pull/3274) [`80ccff7`](https://github.com/withastro/starlight/commit/80ccff7c542794e04a4d2abb17227a076fa57c5d) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes an issue where some Starlight remark and rehype plugins were transforming Markdown and MDX content in non-Starlight pages.
|
|
22
|
+
|
|
23
|
+
⚠️ **BREAKING CHANGE:**
|
|
24
|
+
|
|
25
|
+
Previously, some of Starlight’s remark and rehype plugins, most notably the plugin transforming Starlight's custom Markdown syntax for [rendering asides](https://starlight.astro.build/guides/authoring-content/#asides), were applied to all Markdown and MDX content. This included content from [individual Markdown pages](https://docs.astro.build/en/guides/markdown-content/#individual-markdown-pages) and content from [content collections](https://docs.astro.build/en/guides/content-collections/) other than the `docs` collection used by Starlight.
|
|
26
|
+
|
|
27
|
+
This change restricts the application of Starlight’s remark and rehype plugins to only Markdown and MDX content loaded using Starlight's [`docsLoader()`](https://starlight.astro.build/reference/configuration/#docsloader). If you were relying on this behavior, please let us know about your use case in the dedicated `#starlight` channel in the [Astro Discord](https://astro.build/chat/) or by [opening an issue](https://github.com/withastro/starlight/issues/new?template=---01-bug-report.yml).
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- [#3266](https://github.com/withastro/starlight/pull/3266) [`1161af0`](https://github.com/withastro/starlight/commit/1161af0c2fe26485da6123f8fd7205c53b0e45e5) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Ensures invalid sidebar group configurations using the `attrs` option are properly reported as a type error.
|
|
32
|
+
|
|
33
|
+
Previously, invalid sidebar group configurations using the `attrs` option were not reported as a type error but only surfaced at runtime. This change is only a type-level change and does not affect the runtime behavior of Starlight which does not support the `attrs` option for sidebar groups.
|
|
34
|
+
|
|
35
|
+
- [#3274](https://github.com/withastro/starlight/pull/3274) [`80ccff7`](https://github.com/withastro/starlight/commit/80ccff7c542794e04a4d2abb17227a076fa57c5d) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Prevents Starlight remark and rehype plugins from transforming Markdown and MDX content when using the Astro [`renderMarkdown()`](https://docs.astro.build/en/reference/content-loader-reference/#rendermarkdown) content loader API.
|
|
36
|
+
|
|
37
|
+
## 0.34.8
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- [#3306](https://github.com/withastro/starlight/pull/3306) [`21fcd94`](https://github.com/withastro/starlight/commit/21fcd944d528557b89fc8b351579beabdcc06ff6) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes a regression in Starlight version `0.34.5` that caused multilingual sites with a default locale explicitly set to `root` to report a configuration error.
|
|
42
|
+
|
|
3
43
|
## 0.34.7
|
|
4
44
|
|
|
5
45
|
### Patch Changes
|
package/components/Banner.astro
CHANGED
package/index.ts
CHANGED
|
@@ -129,7 +129,7 @@ export default function StarlightIntegration(
|
|
|
129
129
|
}),
|
|
130
130
|
],
|
|
131
131
|
rehypePlugins: [
|
|
132
|
-
rehypeRtlCodeSupport(),
|
|
132
|
+
rehypeRtlCodeSupport({ astroConfig: config }),
|
|
133
133
|
// Process headings and add anchor links.
|
|
134
134
|
...starlightAutolinkHeadings({
|
|
135
135
|
starlightConfig,
|
package/integrations/asides.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="mdast-util-directive" />
|
|
2
2
|
|
|
3
3
|
import type { AstroConfig, AstroIntegration, AstroUserConfig } from 'astro';
|
|
4
|
-
import { h as _h, s as _s, type Properties } from 'hastscript';
|
|
4
|
+
import { h as _h, s as _s, type Properties, type Result } from 'hastscript';
|
|
5
5
|
import type { Node, Paragraph as P, Parent, PhrasingContent, Root } from 'mdast';
|
|
6
6
|
import {
|
|
7
7
|
type Directives,
|
|
@@ -14,7 +14,12 @@ import { toString } from 'mdast-util-to-string';
|
|
|
14
14
|
import remarkDirective from 'remark-directive';
|
|
15
15
|
import type { Plugin, Transformer } from 'unified';
|
|
16
16
|
import { visit } from 'unist-util-visit';
|
|
17
|
-
import type { HookParameters, StarlightConfig } from '../types';
|
|
17
|
+
import type { HookParameters, StarlightConfig, StarlightIcon } from '../types';
|
|
18
|
+
import { getRemarkRehypeDocsCollectionPath, shouldTransformFile } from './remark-rehype-utils';
|
|
19
|
+
import { Icons } from '../components/Icons';
|
|
20
|
+
import { fromHtml } from 'hast-util-from-html';
|
|
21
|
+
import type { Element } from 'hast';
|
|
22
|
+
import { AstroError } from 'astro/errors';
|
|
18
23
|
|
|
19
24
|
interface AsidesOptions {
|
|
20
25
|
starlightConfig: Pick<StarlightConfig, 'defaultLocale' | 'locales'>;
|
|
@@ -87,6 +92,20 @@ function transformUnhandledDirective(
|
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
/** Hacky function that generates the children of an mdast SVG tree. */
|
|
96
|
+
function makeSvgChildNodes(children: Result['children']): any[] {
|
|
97
|
+
const nodes: P[] = [];
|
|
98
|
+
for (const child of children) {
|
|
99
|
+
if (child.type !== 'element') continue;
|
|
100
|
+
nodes.push({
|
|
101
|
+
type: 'paragraph',
|
|
102
|
+
data: { hName: child.tagName, hProperties: child.properties },
|
|
103
|
+
children: makeSvgChildNodes(child.children),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return nodes;
|
|
107
|
+
}
|
|
108
|
+
|
|
90
109
|
/**
|
|
91
110
|
* remark plugin that converts blocks delimited with `:::` into styled
|
|
92
111
|
* asides (a.k.a. “callouts”, “admonitions”, etc.). Depends on the
|
|
@@ -148,7 +167,11 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
|
|
|
148
167
|
],
|
|
149
168
|
};
|
|
150
169
|
|
|
170
|
+
const docsCollectionPath = getRemarkRehypeDocsCollectionPath(options.astroConfig.srcDir);
|
|
171
|
+
|
|
151
172
|
const transformer: Transformer<Root> = (tree, file) => {
|
|
173
|
+
if (!shouldTransformFile(file, docsCollectionPath)) return;
|
|
174
|
+
|
|
152
175
|
const lang = options.absolutePathToLang(file.path);
|
|
153
176
|
const t = options.useTranslations(lang);
|
|
154
177
|
visit(tree, (node, index, parent) => {
|
|
@@ -159,6 +182,7 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
|
|
|
159
182
|
return;
|
|
160
183
|
}
|
|
161
184
|
const variant = node.name;
|
|
185
|
+
const attributes = node.attributes;
|
|
162
186
|
if (!isAsideVariant(variant)) return;
|
|
163
187
|
|
|
164
188
|
// remark-directive converts a container’s “label” to a paragraph added as the head of its
|
|
@@ -180,6 +204,19 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
|
|
|
180
204
|
node.children.splice(0, 1);
|
|
181
205
|
}
|
|
182
206
|
|
|
207
|
+
let iconPath = iconPaths[variant];
|
|
208
|
+
|
|
209
|
+
if (attributes?.['icon']) {
|
|
210
|
+
const iconName = attributes['icon'] as StarlightIcon;
|
|
211
|
+
const icon = Icons[iconName];
|
|
212
|
+
if (!icon) throwInvalidAsideIconError(iconName);
|
|
213
|
+
// Omit the root node and return only the first child which is the SVG element.
|
|
214
|
+
const iconHastTree = fromHtml(`<svg>${icon}</svg>`, { fragment: true, space: 'svg' })
|
|
215
|
+
.children[0] as Element;
|
|
216
|
+
// Render all SVG child nodes.
|
|
217
|
+
iconPath = makeSvgChildNodes(iconHastTree.children);
|
|
218
|
+
}
|
|
219
|
+
|
|
183
220
|
const aside = h(
|
|
184
221
|
'aside',
|
|
185
222
|
{
|
|
@@ -197,7 +234,7 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
|
|
|
197
234
|
fill: 'currentColor',
|
|
198
235
|
class: 'starlight-aside__icon',
|
|
199
236
|
},
|
|
200
|
-
|
|
237
|
+
iconPath
|
|
201
238
|
),
|
|
202
239
|
...titleNode,
|
|
203
240
|
]),
|
|
@@ -216,6 +253,14 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
|
|
|
216
253
|
|
|
217
254
|
type RemarkPlugins = NonNullable<NonNullable<AstroUserConfig['markdown']>['remarkPlugins']>;
|
|
218
255
|
|
|
256
|
+
export function throwInvalidAsideIconError(icon: string) {
|
|
257
|
+
throw new AstroError(
|
|
258
|
+
'Invalid aside icon',
|
|
259
|
+
`An aside custom icon must be set to the name of one of Starlight\’s built-in icons, but received \`${icon}\`.\n\n` +
|
|
260
|
+
'See https://starlight.astro.build/reference/icons/#all-icons for a list of available icons.'
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
219
264
|
export function starlightAsides(options: AsidesOptions): RemarkPlugins {
|
|
220
265
|
return [remarkDirective, remarkAsides(options)];
|
|
221
266
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import type { AstroConfig } from 'astro';
|
|
1
2
|
import type { Root } from 'hast';
|
|
2
3
|
import { CONTINUE, SKIP, visit } from 'unist-util-visit';
|
|
4
|
+
import type { VFile } from 'vfile';
|
|
5
|
+
import { getRemarkRehypeDocsCollectionPath, shouldTransformFile } from './remark-rehype-utils';
|
|
6
|
+
|
|
7
|
+
interface RtlCodeSupportOptions {
|
|
8
|
+
astroConfig: Pick<AstroConfig, 'srcDir'>;
|
|
9
|
+
}
|
|
3
10
|
|
|
4
11
|
/**
|
|
5
12
|
* rehype plugin that adds `dir` attributes to `<code>` and `<pre>`
|
|
@@ -15,8 +22,12 @@ import { CONTINUE, SKIP, visit } from 'unist-util-visit';
|
|
|
15
22
|
* - `<code>` is often LTR, but could also be RTL. `dir="auto"` ensures the bidirectional
|
|
16
23
|
* algorithm treats the contents of `<code>` in isolation and gives its best guess.
|
|
17
24
|
*/
|
|
18
|
-
export function rehypeRtlCodeSupport() {
|
|
19
|
-
|
|
25
|
+
export function rehypeRtlCodeSupport({ astroConfig }: RtlCodeSupportOptions) {
|
|
26
|
+
const docsCollectionPath = getRemarkRehypeDocsCollectionPath(astroConfig.srcDir);
|
|
27
|
+
|
|
28
|
+
return () => (root: Root, file: VFile) => {
|
|
29
|
+
if (!shouldTransformFile(file, docsCollectionPath)) return;
|
|
30
|
+
|
|
20
31
|
visit(root, 'element', (el) => {
|
|
21
32
|
if (el.tagName === 'pre' || el.tagName === 'code') {
|
|
22
33
|
el.properties ||= {};
|
|
@@ -6,7 +6,7 @@ import { h } from 'hastscript';
|
|
|
6
6
|
import type { Transformer } from 'unified';
|
|
7
7
|
import { SKIP, visit } from 'unist-util-visit';
|
|
8
8
|
import type { HookParameters, StarlightConfig } from '../types';
|
|
9
|
-
import {
|
|
9
|
+
import { getRemarkRehypeDocsCollectionPath, shouldTransformFile } from './remark-rehype-utils';
|
|
10
10
|
|
|
11
11
|
const AnchorLinkIcon = h(
|
|
12
12
|
'span',
|
|
@@ -30,8 +30,7 @@ export default function rehypeAutolinkHeadings(
|
|
|
30
30
|
absolutePathToLang: AutolinkHeadingsOptions['absolutePathToLang']
|
|
31
31
|
) {
|
|
32
32
|
const transformer: Transformer<Root> = (tree, file) => {
|
|
33
|
-
|
|
34
|
-
if (!normalizePath(file.path).startsWith(docsCollectionPath)) return;
|
|
33
|
+
if (!shouldTransformFile(file, docsCollectionPath)) return;
|
|
35
34
|
|
|
36
35
|
const pageLang = absolutePathToLang(file.path);
|
|
37
36
|
const t = useTranslationsForLang(pageLang);
|
|
@@ -93,23 +92,13 @@ export const starlightAutolinkHeadings = ({
|
|
|
93
92
|
{ experimentalHeadingIdCompat: astroConfig.experimental?.headingIdCompat },
|
|
94
93
|
],
|
|
95
94
|
rehypeAutolinkHeadings(
|
|
96
|
-
|
|
95
|
+
getRemarkRehypeDocsCollectionPath(astroConfig.srcDir),
|
|
97
96
|
useTranslations,
|
|
98
97
|
absolutePathToLang
|
|
99
98
|
),
|
|
100
99
|
]
|
|
101
100
|
: [];
|
|
102
101
|
|
|
103
|
-
/**
|
|
104
|
-
* File path separators seems to be inconsistent on Windows when the rehype plugin is used on
|
|
105
|
-
* Markdown vs MDX files.
|
|
106
|
-
* For the time being, we normalize the path to unix style path.
|
|
107
|
-
*/
|
|
108
|
-
const backSlashRegex = /\\/g;
|
|
109
|
-
function normalizePath(path: string) {
|
|
110
|
-
return path.replace(backSlashRegex, '/');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
102
|
// This utility is inlined from https://github.com/syntax-tree/hast-util-heading-rank
|
|
114
103
|
// Copyright (c) 2020 Titus Wormer <tituswormer@gmail.com>
|
|
115
104
|
// MIT License: https://github.com/syntax-tree/hast-util-heading-rank/blob/main/license
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AstroConfig } from 'astro';
|
|
2
|
+
import type { VFile } from 'vfile';
|
|
3
|
+
import { resolveCollectionPath } from '../utils/collection';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns the path to the Starlight docs collection ready to be used in remark/rehype plugins,
|
|
7
|
+
* e.g. with the `shouldTransformFile()` utility to determine if a file should be transformed
|
|
8
|
+
* by a plugin or not.
|
|
9
|
+
*/
|
|
10
|
+
export function getRemarkRehypeDocsCollectionPath(srcDir: AstroConfig['srcDir']) {
|
|
11
|
+
return normalizePath(resolveCollectionPath('docs', srcDir));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Determines if a file should be transformed by a remark/rehype plugin, e.g. files without a known
|
|
16
|
+
* path or files that are not part of the Starlight docs collection should be skipped.
|
|
17
|
+
*/
|
|
18
|
+
export function shouldTransformFile(file: VFile, docsCollectionPath: string) {
|
|
19
|
+
// If the content is rendered using the content loader `renderMarkdown()` API, a file path
|
|
20
|
+
// is not provided.
|
|
21
|
+
// In that case, we skip the file.
|
|
22
|
+
if (!file?.path) return false;
|
|
23
|
+
|
|
24
|
+
// If the document is not part of the Starlight docs collection, skip it.
|
|
25
|
+
if (!normalizePath(file.path).startsWith(docsCollectionPath)) return false;
|
|
26
|
+
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* File path separators seems to be inconsistent on Windows between remark/rehype plugins used on
|
|
32
|
+
* Markdown vs MDX files.
|
|
33
|
+
* For the time being, we normalize all paths to unix style paths.
|
|
34
|
+
*/
|
|
35
|
+
const backSlashRegex = /\\/g;
|
|
36
|
+
function normalizePath(path: string) {
|
|
37
|
+
return path.replace(backSlashRegex, '/');
|
|
38
|
+
}
|
package/loaders.ts
CHANGED
|
@@ -6,13 +6,30 @@ import { getCollectionPathFromRoot, type StarlightCollection } from './utils/col
|
|
|
6
6
|
const docsExtensions = ['markdown', 'mdown', 'mkdn', 'mkd', 'mdwn', 'md', 'mdx'];
|
|
7
7
|
const i18nExtensions = ['json', 'yml', 'yaml'];
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
type GlobOptions = Parameters<typeof glob>[0];
|
|
10
|
+
type GenerateIdFunction = NonNullable<GlobOptions['generateId']>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Loads content files from the `src/content/docs/` directory, ignoring filenames starting with `_`.
|
|
14
|
+
*/
|
|
15
|
+
export function docsLoader({
|
|
16
|
+
generateId,
|
|
17
|
+
}: {
|
|
18
|
+
/**
|
|
19
|
+
* Function that generates an ID for an entry. Default implementation generates a slug from the entry path.
|
|
20
|
+
* @returns The ID of the entry. Must be unique per collection.
|
|
21
|
+
**/
|
|
22
|
+
generateId?: GenerateIdFunction;
|
|
23
|
+
} = {}): Loader {
|
|
10
24
|
return {
|
|
11
25
|
name: 'starlight-docs-loader',
|
|
12
|
-
load: createGlobLoadFn('docs'),
|
|
26
|
+
load: createGlobLoadFn('docs', generateId),
|
|
13
27
|
};
|
|
14
28
|
}
|
|
15
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Loads data files from the `src/content/i18n/` directory, ignoring filenames starting with `_`.
|
|
32
|
+
*/
|
|
16
33
|
export function i18nLoader(): Loader {
|
|
17
34
|
return {
|
|
18
35
|
name: 'starlight-i18n-loader',
|
|
@@ -20,7 +37,10 @@ export function i18nLoader(): Loader {
|
|
|
20
37
|
};
|
|
21
38
|
}
|
|
22
39
|
|
|
23
|
-
function createGlobLoadFn(
|
|
40
|
+
function createGlobLoadFn(
|
|
41
|
+
collection: StarlightCollection,
|
|
42
|
+
generateId?: GenerateIdFunction
|
|
43
|
+
): Loader['load'] {
|
|
24
44
|
return (context: LoaderContext) => {
|
|
25
45
|
const extensions = collection === 'docs' ? docsExtensions : i18nExtensions;
|
|
26
46
|
|
|
@@ -32,9 +52,12 @@ function createGlobLoadFn(collection: StarlightCollection): Loader['load'] {
|
|
|
32
52
|
extensions.push('mdoc');
|
|
33
53
|
}
|
|
34
54
|
|
|
35
|
-
|
|
55
|
+
const options: GlobOptions = {
|
|
36
56
|
base: getCollectionPathFromRoot(collection, context.config),
|
|
37
57
|
pattern: `**/[^_]*.{${extensions.join(',')}}`,
|
|
38
|
-
}
|
|
58
|
+
};
|
|
59
|
+
if (generateId) options.generateId = generateId;
|
|
60
|
+
|
|
61
|
+
return glob(options).load(context);
|
|
39
62
|
};
|
|
40
63
|
}
|
package/package.json
CHANGED
package/schemas/sidebar.ts
CHANGED
|
@@ -14,6 +14,14 @@ const SidebarBaseSchema = z.object({
|
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
const SidebarGroupSchema = SidebarBaseSchema.extend({
|
|
17
|
+
/**
|
|
18
|
+
* Explicitly prevent custom attributes on groups as the final type for supported sidebar item
|
|
19
|
+
* is a non-discriminated union where TypeScript will not perform excess property checks.
|
|
20
|
+
* This means that a user could define a sidebar group with custom attributes, not getting a
|
|
21
|
+
* TypeScript error, and only have it fail at runtime.
|
|
22
|
+
* @see https://github.com/microsoft/TypeScript/issues/20863
|
|
23
|
+
*/
|
|
24
|
+
attrs: z.never().optional(),
|
|
17
25
|
/** Whether this item should be collapsed by default. */
|
|
18
26
|
collapsed: z.boolean().default(false),
|
|
19
27
|
});
|
|
@@ -22,7 +30,7 @@ const SidebarGroupSchema = SidebarBaseSchema.extend({
|
|
|
22
30
|
// `Record<string, string | number | boolean | undefined>` but typed as `HTMLAttributes<'a'>`
|
|
23
31
|
// for user convenience.
|
|
24
32
|
const linkHTMLAttributesSchema = z.record(
|
|
25
|
-
z.union([z.string(), z.number(), z.boolean(), z.undefined()])
|
|
33
|
+
z.union([z.string(), z.number(), z.boolean(), z.undefined(), z.null()])
|
|
26
34
|
) as z.Schema<Omit<HTMLAttributes<'a'>, keyof AstroBuiltinAttributes | 'children'>>;
|
|
27
35
|
export type LinkHTMLAttributes = z.infer<typeof linkHTMLAttributesSchema>;
|
|
28
36
|
|
|
@@ -46,6 +54,8 @@ const AutoSidebarGroupSchema = SidebarGroupSchema.extend({
|
|
|
46
54
|
* Defaults to the `AutoSidebarGroup` `collapsed` value.
|
|
47
55
|
*/
|
|
48
56
|
collapsed: z.boolean().optional(),
|
|
57
|
+
/** HTML attributes to add to the autogenerated link items. */
|
|
58
|
+
attrs: SidebarLinkItemHTMLAttributesSchema(),
|
|
49
59
|
// TODO: not supported by Docusaurus but would be good to have
|
|
50
60
|
/** How many directories deep to include from this directory in the sidebar. Default: `Infinity`. */
|
|
51
61
|
// depth: z.number().optional(),
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { AstroError } from 'astro/errors';
|
|
3
3
|
import Icon from './Icon.astro';
|
|
4
|
+
import { Icons, type StarlightIcon } from '../components/Icons';
|
|
5
|
+
import { throwInvalidAsideIconError } from '../integrations/asides';
|
|
4
6
|
|
|
5
7
|
const asideVariants = ['note', 'tip', 'caution', 'danger'] as const;
|
|
6
8
|
const icons = { note: 'information', tip: 'rocket', caution: 'warning', danger: 'error' } as const;
|
|
@@ -8,9 +10,10 @@ const icons = { note: 'information', tip: 'rocket', caution: 'warning', danger:
|
|
|
8
10
|
interface Props {
|
|
9
11
|
type?: (typeof asideVariants)[number];
|
|
10
12
|
title?: string;
|
|
13
|
+
icon?: StarlightIcon;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
let { type = 'note', title } = Astro.props;
|
|
16
|
+
let { type = 'note', title, icon } = Astro.props;
|
|
14
17
|
|
|
15
18
|
if (!asideVariants.includes(type)) {
|
|
16
19
|
throw new AstroError(
|
|
@@ -20,6 +23,8 @@ if (!asideVariants.includes(type)) {
|
|
|
20
23
|
);
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
if (icon && !Icons[icon]) throwInvalidAsideIconError(icon);
|
|
27
|
+
|
|
23
28
|
if (!title) {
|
|
24
29
|
title = Astro.locals.t(`aside.${type}`);
|
|
25
30
|
}
|
|
@@ -27,7 +32,7 @@ if (!title) {
|
|
|
27
32
|
|
|
28
33
|
<aside aria-label={title} class={`starlight-aside starlight-aside--${type}`}>
|
|
29
34
|
<p class="starlight-aside__title" aria-hidden="true">
|
|
30
|
-
<Icon name={icons[type]} class="starlight-aside__icon" />{title}
|
|
35
|
+
<Icon name={icon || icons[type]} class="starlight-aside__icon" />{title}
|
|
31
36
|
</p>
|
|
32
37
|
<div class="starlight-aside__content">
|
|
33
38
|
<slot />
|
package/utils/i18n.ts
CHANGED
|
@@ -57,7 +57,10 @@ function getAstroI18nConfig(config: StarlightConfig): NonNullable<AstroConfig['i
|
|
|
57
57
|
// In Starlight, this matches the `locale` property if defined, and we fallback to the `lang`
|
|
58
58
|
// property if not (which would be set to the language’s directory name by default).
|
|
59
59
|
defaultLocale:
|
|
60
|
-
|
|
60
|
+
// If the default locale is explicitly set to `root`, we use the `lang` property instead.
|
|
61
|
+
(config.defaultLocale.locale === 'root'
|
|
62
|
+
? config.defaultLocale.lang
|
|
63
|
+
: (config.defaultLocale.locale ?? config.defaultLocale.lang)) ?? BuiltInDefaultLocale.lang,
|
|
61
64
|
locales: config.locales
|
|
62
65
|
? Object.entries(config.locales).map(([locale, localeConfig]) => {
|
|
63
66
|
return {
|
package/utils/navigation.ts
CHANGED
|
@@ -96,7 +96,7 @@ function groupFromAutogenerateConfig(
|
|
|
96
96
|
routes: Route[],
|
|
97
97
|
currentPathname: string
|
|
98
98
|
): SidebarGroup {
|
|
99
|
-
const { collapsed: subgroupCollapsed, directory } = item.autogenerate;
|
|
99
|
+
const { attrs, collapsed: subgroupCollapsed, directory } = item.autogenerate;
|
|
100
100
|
const localeDir = locale ? locale + '/' + directory : directory;
|
|
101
101
|
const dirDocs = routes.filter((doc) => {
|
|
102
102
|
const filePathFromContentDir = getRoutePathRelativeToCollectionRoot(doc, locale);
|
|
@@ -112,7 +112,13 @@ function groupFromAutogenerateConfig(
|
|
|
112
112
|
return {
|
|
113
113
|
type: 'group',
|
|
114
114
|
label,
|
|
115
|
-
entries: sidebarFromDir(
|
|
115
|
+
entries: sidebarFromDir(
|
|
116
|
+
tree,
|
|
117
|
+
currentPathname,
|
|
118
|
+
locale,
|
|
119
|
+
subgroupCollapsed ?? item.collapsed,
|
|
120
|
+
attrs
|
|
121
|
+
),
|
|
116
122
|
collapsed: item.collapsed,
|
|
117
123
|
badge: getSidebarBadge(item.badge, locale, label),
|
|
118
124
|
};
|
|
@@ -268,12 +274,12 @@ function treeify(routes: Route[], locale: string | undefined, baseDir: string):
|
|
|
268
274
|
}
|
|
269
275
|
|
|
270
276
|
/** Create a link entry for a given content collection entry. */
|
|
271
|
-
function linkFromRoute(route: Route): SidebarLink {
|
|
277
|
+
function linkFromRoute(route: Route, attrs?: LinkHTMLAttributes): SidebarLink {
|
|
272
278
|
return makeSidebarLink(
|
|
273
279
|
slugToPathname(route.slug),
|
|
274
280
|
route.entry.data.sidebar.label || route.entry.data.title,
|
|
275
281
|
route.entry.data.sidebar.badge,
|
|
276
|
-
route.entry.data.sidebar.attrs
|
|
282
|
+
{ ...attrs, ...route.entry.data.sidebar.attrs }
|
|
277
283
|
);
|
|
278
284
|
}
|
|
279
285
|
|
|
@@ -307,10 +313,11 @@ function groupFromDir(
|
|
|
307
313
|
dirName: string,
|
|
308
314
|
currentPathname: string,
|
|
309
315
|
locale: string | undefined,
|
|
310
|
-
collapsed: boolean
|
|
316
|
+
collapsed: boolean,
|
|
317
|
+
attrs?: LinkHTMLAttributes
|
|
311
318
|
): SidebarGroup {
|
|
312
319
|
const entries = sortDirEntries(Object.entries(dir)).map(([key, dirOrRoute]) =>
|
|
313
|
-
dirToItem(dirOrRoute, `${fullPath}/${key}`, key, currentPathname, locale, collapsed)
|
|
320
|
+
dirToItem(dirOrRoute, `${fullPath}/${key}`, key, currentPathname, locale, collapsed, attrs)
|
|
314
321
|
);
|
|
315
322
|
return {
|
|
316
323
|
type: 'group',
|
|
@@ -328,11 +335,12 @@ function dirToItem(
|
|
|
328
335
|
dirName: string,
|
|
329
336
|
currentPathname: string,
|
|
330
337
|
locale: string | undefined,
|
|
331
|
-
collapsed: boolean
|
|
338
|
+
collapsed: boolean,
|
|
339
|
+
attrs?: LinkHTMLAttributes
|
|
332
340
|
): SidebarEntry {
|
|
333
341
|
return isDir(dirOrRoute)
|
|
334
|
-
? groupFromDir(dirOrRoute, fullPath, dirName, currentPathname, locale, collapsed)
|
|
335
|
-
: linkFromRoute(dirOrRoute);
|
|
342
|
+
? groupFromDir(dirOrRoute, fullPath, dirName, currentPathname, locale, collapsed, attrs)
|
|
343
|
+
: linkFromRoute(dirOrRoute, attrs);
|
|
336
344
|
}
|
|
337
345
|
|
|
338
346
|
/** Create a sidebar entry for a given content directory. */
|
|
@@ -340,10 +348,11 @@ function sidebarFromDir(
|
|
|
340
348
|
tree: Dir,
|
|
341
349
|
currentPathname: string,
|
|
342
350
|
locale: string | undefined,
|
|
343
|
-
collapsed: boolean
|
|
351
|
+
collapsed: boolean,
|
|
352
|
+
attrs?: LinkHTMLAttributes
|
|
344
353
|
) {
|
|
345
354
|
return sortDirEntries(Object.entries(tree)).map(([key, dirOrRoute]) =>
|
|
346
|
-
dirToItem(dirOrRoute, key, key, currentPathname, locale, collapsed)
|
|
355
|
+
dirToItem(dirOrRoute, key, key, currentPathname, locale, collapsed, attrs)
|
|
347
356
|
);
|
|
348
357
|
}
|
|
349
358
|
|