@astrojs/starlight 0.38.5 → 0.39.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 +88 -0
- package/components/PageFrame.astro +1 -0
- package/package.json +2 -2
- package/schemas/sidebar.ts +53 -21
- package/style/markdown.css +23 -23
- package/style/reset.css +15 -0
- package/utils/createTranslationSystem.ts +5 -1
- package/utils/error-map.ts +6 -0
- package/utils/head.ts +8 -0
- package/utils/navigation.ts +91 -86
- package/utils/routing/types.ts +16 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# @astrojs/starlight
|
|
2
2
|
|
|
3
|
+
## 0.39.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#3618](https://github.com/withastro/starlight/pull/3618) [`dcf6d09`](https://github.com/withastro/starlight/commit/dcf6d094bbcfa1f83e45742901f4178df07c2156) Thanks [@HiDeoo](https://github.com/HiDeoo)! - **⚠️ BREAKING CHANGE:** This release changes how autogenerated links work in Starlight’s sidebar configuration.
|
|
8
|
+
|
|
9
|
+
If you have sidebar groups using the `autogenerate` key, you must now wrap that configuration in an `items` array:
|
|
10
|
+
|
|
11
|
+
```diff
|
|
12
|
+
{
|
|
13
|
+
label: 'My group',
|
|
14
|
+
- autogenerate: { directory: 'some-dir' },
|
|
15
|
+
+ items: [{ autogenerate: { directory: 'some-dir' } }],
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This change unlocks the possibility to mix autogenerated links and other links in a single group, for example:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
{
|
|
23
|
+
label: 'Mixed group',
|
|
24
|
+
items: [
|
|
25
|
+
'example-page',
|
|
26
|
+
{ autogenerate: { directory: 'examples' } },
|
|
27
|
+
{ label: 'More examples', link: 'https://example.com' },
|
|
28
|
+
],
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This release also updates the shape of autogenerated sidebar entries in route data. Autogenerated links and groups in `Astro.locals.starlightRoute.sidebar` now include an `autogenerate` object with the [configured `directory` value](https://starlight.astro.build/guides/sidebar/#autogenerated-groups):
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
{
|
|
36
|
+
type: 'link',
|
|
37
|
+
label: 'Example',
|
|
38
|
+
href: '/examples/example/',
|
|
39
|
+
isCurrent: false,
|
|
40
|
+
autogenerate: { directory: 'examples' }
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- [#3618](https://github.com/withastro/starlight/pull/3618) [`dcf6d09`](https://github.com/withastro/starlight/commit/dcf6d094bbcfa1f83e45742901f4178df07c2156) Thanks [@HiDeoo](https://github.com/HiDeoo)! - **⚠️ BREAKING CHANGE:** This release changes the default collapsed state of autogenerated sidebar subgroups.
|
|
45
|
+
|
|
46
|
+
Autogenerated subgroups no longer inherit the `collapsed` value from their parent group. They are now expanded by default unless explicitly configured with [`autogenerate.collapsed`](https://starlight.astro.build/reference/configuration/#collapsing-groups).
|
|
47
|
+
|
|
48
|
+
If your sidebar configuration relies on a collapsed parent group to also collapse its autogenerated subgroups, update your configuration to set `autogenerate.collapsed` to `true`:
|
|
49
|
+
|
|
50
|
+
```diff
|
|
51
|
+
{
|
|
52
|
+
label: 'Reference',
|
|
53
|
+
collapsed: true,
|
|
54
|
+
items: [
|
|
55
|
+
- { autogenerate: { directory: 'reference' } },
|
|
56
|
+
+ { autogenerate: { directory: 'reference', collapsed: true } },
|
|
57
|
+
],
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- [#3845](https://github.com/withastro/starlight/pull/3845) [`4d755f5`](https://github.com/withastro/starlight/commit/4d755f591bbca682d01a70162a10c12ab5187c61) Thanks [@delucis](https://github.com/delucis)! - Adds a `<link rel="alternate" hreflang="x-default" href="...">` tag pointing to the default locale in multilingual sites. The `x-default` alternate is used as a signal of which language to fall back to if no other is available. Learn more in Google’s [SEO localization docs](https://developers.google.com/search/docs/specialty/international/localized-versions#xdefault).
|
|
62
|
+
|
|
63
|
+
- [#3862](https://github.com/withastro/starlight/pull/3862) [`ec70630`](https://github.com/withastro/starlight/commit/ec70630cbbe43bb5b026ddabfe7224759c798a4b) Thanks [@itrew](https://github.com/itrew)! - Makes spacing of items in nested lists more consistent
|
|
64
|
+
|
|
65
|
+
- [#3872](https://github.com/withastro/starlight/pull/3872) [`417a66c`](https://github.com/withastro/starlight/commit/417a66c4acf3a7408fc0cea701aa5f143c540e5c) Thanks [@tats-u](https://github.com/tats-u)! - Enables [the CSS property `text-autospace`](https://developer.mozilla.org/docs/Web/CSS/Reference/Properties/text-autospace) in Chinese and Japanese documents.
|
|
66
|
+
|
|
67
|
+
If you would prefer to disable autospacing in Chinese and Japanese pages, you can add the following custom CSS to your site:
|
|
68
|
+
|
|
69
|
+
```css
|
|
70
|
+
[lang]:where(:lang(zh, ja)) {
|
|
71
|
+
text-autospace: initial;
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- [#3797](https://github.com/withastro/starlight/pull/3797) [`9764ebd`](https://github.com/withastro/starlight/commit/9764ebdab8d12983064d11515b6d92355a4d95eb) Thanks [@delucis](https://github.com/delucis)! - Avoids the risk of layout shift when users expand and collapse sidebar groups
|
|
76
|
+
|
|
77
|
+
This release can introduce additional padding to the site sidebar on certain devices to reserve space for scrollbars. You may wish to inspect your site sidebar visually when upgrading.
|
|
78
|
+
|
|
79
|
+
If you would prefer to keep the previous styling, you can add the following custom CSS to your site:
|
|
80
|
+
|
|
81
|
+
```css
|
|
82
|
+
.sidebar-pane {
|
|
83
|
+
scrollbar-gutter: auto;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- [#3858](https://github.com/withastro/starlight/pull/3858) [`6672c35`](https://github.com/withastro/starlight/commit/6672c35b914cd53f1641a43a7ea8e7ef08d9cc8f) Thanks [@delucis](https://github.com/delucis)! - Updates `i18next`, used for Starlight’s localization APIs, from v23 to v26
|
|
88
|
+
|
|
89
|
+
There should not be any user-facing changes from this update
|
|
90
|
+
|
|
3
91
|
## 0.38.5
|
|
4
92
|
|
|
5
93
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrojs/starlight",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "Build beautiful, high-performance documentation websites with Astro",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"hast-util-select": "^6.0.4",
|
|
67
67
|
"hast-util-to-string": "^3.0.1",
|
|
68
68
|
"hastscript": "^9.0.1",
|
|
69
|
-
"i18next": "^
|
|
69
|
+
"i18next": "^26.0.7",
|
|
70
70
|
"js-yaml": "^4.1.1",
|
|
71
71
|
"klona": "^2.0.6",
|
|
72
72
|
"magic-string": "^0.30.21",
|
package/schemas/sidebar.ts
CHANGED
|
@@ -50,31 +50,63 @@ const SidebarLinkItemSchema = z.strictObject({
|
|
|
50
50
|
});
|
|
51
51
|
export type SidebarLinkItem = z.infer<typeof SidebarLinkItemSchema>;
|
|
52
52
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
/** Enable autogenerating a sidebar category from a specific docs directory. */
|
|
56
|
-
autogenerate: z.object({
|
|
57
|
-
/** The directory to generate sidebar items for. */
|
|
58
|
-
directory: z.string().transform(stripLeadingAndTrailingSlashes),
|
|
53
|
+
const AutoSidebarEntriesSchema = z
|
|
54
|
+
.object({
|
|
59
55
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
56
|
+
* Explicitly prevent autogenerated groups which are no longer supported as the final type for
|
|
57
|
+
* supported sidebar item is a non-discriminated union where TypeScript will not perform excess
|
|
58
|
+
* property checks. This means that a user could define a sidebar group with an autogenerated
|
|
59
|
+
* property, not getting a TypeScript error, and only have it fail at runtime.
|
|
60
|
+
* @see https://github.com/microsoft/TypeScript/issues/20863
|
|
62
61
|
*/
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
label: z.custom<never>().optional(),
|
|
63
|
+
/** Enable autogenerating entries from a specific docs directory. */
|
|
64
|
+
autogenerate: z.object({
|
|
65
|
+
/** The directory to generate sidebar items for. */
|
|
66
|
+
directory: z.string().transform(stripLeadingAndTrailingSlashes),
|
|
67
|
+
/** Whether the autogenerated subgroups should be collapsed by default. Default: `false`. */
|
|
68
|
+
collapsed: z.boolean().optional(),
|
|
69
|
+
/** HTML attributes to add to the autogenerated link items. */
|
|
70
|
+
attrs: SidebarLinkItemHTMLAttributesSchema(),
|
|
71
|
+
// TODO: not supported by Docusaurus but would be good to have
|
|
72
|
+
/** How many directories deep to include from this directory in the sidebar. Default: `Infinity`. */
|
|
73
|
+
// depth: z.number().optional(),
|
|
74
|
+
}),
|
|
75
|
+
})
|
|
76
|
+
.strict()
|
|
77
|
+
.superRefine((config, ctx) => {
|
|
78
|
+
if (!('label' in config)) return;
|
|
79
|
+
|
|
80
|
+
// TODO: Remove this error message in a future release once most users have migrated
|
|
81
|
+
ctx.addIssue({
|
|
82
|
+
code: 'custom',
|
|
83
|
+
message:
|
|
84
|
+
`Found an \`autogenerate\` object with a \`label\`. Support for autogenerated sidebar groups was removed in Starlight v0.38.0.\n` +
|
|
85
|
+
`You should instead create a group with the desired \`label\` and an \`items\` array containing the autogenerate config:\n\n` +
|
|
86
|
+
`{\n` +
|
|
87
|
+
` label: '${config.label}',\n` +
|
|
88
|
+
` items: [{ autogenerate: ${JSON.stringify(
|
|
89
|
+
config.autogenerate,
|
|
90
|
+
// Hide empty attrs object that is automatically added by the schema default value.
|
|
91
|
+
(key, value: unknown) =>
|
|
92
|
+
key === 'attrs' &&
|
|
93
|
+
typeof value === 'object' &&
|
|
94
|
+
value !== null &&
|
|
95
|
+
Object.keys(value).length === 0
|
|
96
|
+
? undefined
|
|
97
|
+
: value,
|
|
98
|
+
' '
|
|
99
|
+
).replace(/\n\s*/g, ' ')} }]\n` +
|
|
100
|
+
`}`,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
export type AutoSidebarEntries = z.infer<typeof AutoSidebarEntriesSchema>;
|
|
72
104
|
|
|
73
105
|
type ManualSidebarGroupInput = z.input<typeof SidebarGroupSchema> & {
|
|
74
106
|
/** Array of links and subcategories to display in this category. */
|
|
75
107
|
items: Array<
|
|
76
108
|
| z.input<typeof SidebarLinkItemSchema>
|
|
77
|
-
| z.input<typeof
|
|
109
|
+
| z.input<typeof AutoSidebarEntriesSchema>
|
|
78
110
|
| z.input<typeof InternalSidebarLinkItemSchema>
|
|
79
111
|
| z.input<typeof InternalSidebarLinkItemShorthandSchema>
|
|
80
112
|
| ManualSidebarGroupInput
|
|
@@ -85,7 +117,7 @@ type ManualSidebarGroupOutput = z.output<typeof SidebarGroupSchema> & {
|
|
|
85
117
|
/** Array of links and subcategories to display in this category. */
|
|
86
118
|
items: Array<
|
|
87
119
|
| z.output<typeof SidebarLinkItemSchema>
|
|
88
|
-
| z.output<typeof
|
|
120
|
+
| z.output<typeof AutoSidebarEntriesSchema>
|
|
89
121
|
| z.output<typeof InternalSidebarLinkItemSchema>
|
|
90
122
|
| z.output<typeof InternalSidebarLinkItemShorthandSchema>
|
|
91
123
|
| ManualSidebarGroupOutput
|
|
@@ -101,7 +133,7 @@ const ManualSidebarGroupSchema: z.ZodType<ManualSidebarGroupOutput, ManualSideba
|
|
|
101
133
|
.union([
|
|
102
134
|
SidebarLinkItemSchema,
|
|
103
135
|
ManualSidebarGroupSchema,
|
|
104
|
-
|
|
136
|
+
AutoSidebarEntriesSchema,
|
|
105
137
|
InternalSidebarLinkItemSchema,
|
|
106
138
|
InternalSidebarLinkItemShorthandSchema,
|
|
107
139
|
])
|
|
@@ -124,7 +156,7 @@ export type InternalSidebarLinkItem = z.output<typeof InternalSidebarLinkItemSch
|
|
|
124
156
|
export const SidebarItemSchema = z.union([
|
|
125
157
|
SidebarLinkItemSchema,
|
|
126
158
|
ManualSidebarGroupSchema,
|
|
127
|
-
|
|
159
|
+
AutoSidebarEntriesSchema,
|
|
128
160
|
InternalSidebarLinkItemSchema,
|
|
129
161
|
InternalSidebarLinkItemShorthandSchema,
|
|
130
162
|
]);
|
package/style/markdown.css
CHANGED
|
@@ -31,31 +31,31 @@
|
|
|
31
31
|
overflow-wrap: anywhere;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/*
|
|
35
|
+
* This complex selector manages spacing inside lists.
|
|
36
|
+
*
|
|
37
|
+
* - Finds all lists containing items with non-inline direct children (e.g.
|
|
38
|
+
* ul > li > p)
|
|
39
|
+
* - Applies a bottom margin to the last non-inline child of each list item
|
|
40
|
+
* in this list
|
|
41
|
+
*
|
|
42
|
+
* This ensures that even if only one list item contains a block element
|
|
43
|
+
* (most commonly a paragraph), siblings of that list item are spaced
|
|
44
|
+
* consistently.
|
|
45
|
+
*/
|
|
34
46
|
.sl-markdown-content
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
del,
|
|
45
|
-
span,
|
|
46
|
-
input,
|
|
47
|
-
code,
|
|
48
|
-
br,
|
|
49
|
-
script,
|
|
50
|
-
:where(.not-content *)
|
|
51
|
-
),
|
|
52
|
-
/**
|
|
53
|
-
* For list items ending with 1 or multiple script elements (`:has(~ script:last-child)`), we
|
|
54
|
-
* need to style the last non-script element (`:not(script)`) that doesn't have a subsequent
|
|
55
|
-
* sibling that is not a script (`:not(:has(~ :not(script)))`).
|
|
47
|
+
:is(ol, ul):has(> li > :not(a, strong, em, del, span, input, code, br, script, ol, ul))
|
|
48
|
+
> li
|
|
49
|
+
> :is(
|
|
50
|
+
:last-child:not(a, strong, em, del, span, input, code, br, script, :where(.not-content *)),
|
|
51
|
+
/*
|
|
52
|
+
* For list items ending with 1 or multiple script elements (`:has(~
|
|
53
|
+
* script:last-child)`), we need to style the last non-script
|
|
54
|
+
* element (`:not(script)`) that doesn't have a subsequent sibling
|
|
55
|
+
* that is not a script (`:not(:has(~ :not(script)))`).
|
|
56
56
|
*/
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
:not(script):has(~ script:last-child):not(:has(~ :not(script)))
|
|
58
|
+
) {
|
|
59
59
|
margin-bottom: 1.25rem;
|
|
60
60
|
}
|
|
61
61
|
|
package/style/reset.css
CHANGED
|
@@ -28,6 +28,21 @@
|
|
|
28
28
|
background-color: var(--sl-color-bg);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
[lang]:where(:lang(zh, ja)) {
|
|
32
|
+
text-autospace: normal;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
[lang]:where(:not(:lang(zh, ja))) {
|
|
36
|
+
text-autospace: initial;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pre,
|
|
40
|
+
code,
|
|
41
|
+
samp,
|
|
42
|
+
kbd {
|
|
43
|
+
text-autospace: no-autospace;
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
input,
|
|
32
47
|
button,
|
|
33
48
|
textarea,
|
|
@@ -72,7 +72,11 @@ export async function createTranslationSystem<T extends i18nSchemaOutput>(
|
|
|
72
72
|
|
|
73
73
|
const t = i18n.getFixedT(lang, I18nextNamespace) as I18nT;
|
|
74
74
|
t.all = () => i18n.getResourceBundle(lang, I18nextNamespace) as ReturnType<I18nT['all']>;
|
|
75
|
-
|
|
75
|
+
// Since i18next 25.10.4 the `ExistsFunction` type has a signature including a predicate that
|
|
76
|
+
// TS cannot preserve when composing a new function to add default options.
|
|
77
|
+
// See: https://github.com/i18next/i18next/issues/2425
|
|
78
|
+
t.exists = ((key, options) =>
|
|
79
|
+
i18n.exists(key, { lng: lang, ns: I18nextNamespace, ...options })) as ExistsFunction;
|
|
76
80
|
t.dir = (dirLang = lang) => i18n.dir(dirLang);
|
|
77
81
|
|
|
78
82
|
return t;
|
package/utils/error-map.ts
CHANGED
|
@@ -107,6 +107,12 @@ const errorMap: z.core.$ZodErrorMap = (issue) => {
|
|
|
107
107
|
for (const unionError of issue.errors) {
|
|
108
108
|
const expectedShape: string[] = [];
|
|
109
109
|
for (const issue of unionError) {
|
|
110
|
+
// We sometimes use `z.NEVER` to explicitly error on certain property combinations in
|
|
111
|
+
// non-discriminated unions to make it easier for users to identify invalid config, e.g.
|
|
112
|
+
// autogenerated groups in the sidebar no longer being supported. Having these properties
|
|
113
|
+
// show up in the error message with an expected type of `never` is noisy and not helpful
|
|
114
|
+
// so we skip them here.
|
|
115
|
+
if (issue.code === 'invalid_type' && issue.expected === 'never') continue;
|
|
110
116
|
// If the issue is a nested union error, show the associated error message instead of the
|
|
111
117
|
// base error message.
|
|
112
118
|
if (issue.code === 'invalid_union') {
|
package/utils/head.ts
CHANGED
|
@@ -91,6 +91,14 @@ export function getHead(
|
|
|
91
91
|
},
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
|
+
headDefaults.push({
|
|
95
|
+
tag: 'link',
|
|
96
|
+
attrs: {
|
|
97
|
+
rel: 'alternate',
|
|
98
|
+
hreflang: 'x-default',
|
|
99
|
+
href: localizedUrl(canonical, config.defaultLocale.locale, project.trailingSlash).href,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
// Link to sitemap, but only when `site` is set.
|
package/utils/navigation.ts
CHANGED
|
@@ -4,7 +4,7 @@ import config from 'virtual:starlight/user-config';
|
|
|
4
4
|
import type { Badge, I18nBadge, I18nBadgeConfig } from '../schemas/badge';
|
|
5
5
|
import type { PrevNextLinkConfig } from '../schemas/prevNextLink';
|
|
6
6
|
import type {
|
|
7
|
-
|
|
7
|
+
AutoSidebarEntries,
|
|
8
8
|
InternalSidebarLinkItem,
|
|
9
9
|
LinkHTMLAttributes,
|
|
10
10
|
SidebarItem,
|
|
@@ -24,9 +24,13 @@ import { getLocaleRoutes, routes } from './routing';
|
|
|
24
24
|
import type {
|
|
25
25
|
SidebarGroup,
|
|
26
26
|
SidebarLink,
|
|
27
|
+
SidebarManualLink,
|
|
27
28
|
PaginationLinks,
|
|
28
29
|
Route,
|
|
29
30
|
SidebarEntry,
|
|
31
|
+
SidebarAutoLink,
|
|
32
|
+
SidebarAutoGroup,
|
|
33
|
+
SidebarAutogenerateRouteData,
|
|
30
34
|
} from './routing/types';
|
|
31
35
|
import { localeToLang, localizedFilePath, slugToPathname } from './slugs';
|
|
32
36
|
import { isAbsoluteUrl } from './url';
|
|
@@ -35,6 +39,8 @@ import type { StarlightConfig } from './user-config';
|
|
|
35
39
|
const DirKey = Symbol('DirKey');
|
|
36
40
|
const SlugKey = Symbol('SlugKey');
|
|
37
41
|
|
|
42
|
+
const rootAutogenerate: SidebarAutogenerateRouteData = { directory: '' };
|
|
43
|
+
|
|
38
44
|
const neverPathFormatter = createPathFormatter({ trailingSlash: 'never' });
|
|
39
45
|
|
|
40
46
|
const docsCollectionPathFromRoot = getCollectionPathFromRoot('docs', project);
|
|
@@ -68,14 +74,13 @@ function isDir(data: Record<string, unknown>): data is Dir {
|
|
|
68
74
|
/** Convert an item in a user’s sidebar config to a sidebar entry. */
|
|
69
75
|
function configItemToEntry(
|
|
70
76
|
item: SidebarItem,
|
|
71
|
-
currentPathname: string,
|
|
72
77
|
locale: string | undefined,
|
|
73
78
|
routes: Route[]
|
|
74
|
-
): SidebarEntry {
|
|
79
|
+
): SidebarEntry | SidebarEntry[] {
|
|
75
80
|
if ('link' in item) {
|
|
76
81
|
return linkFromSidebarLinkItem(item, locale);
|
|
77
82
|
} else if ('autogenerate' in item) {
|
|
78
|
-
return
|
|
83
|
+
return entriesFromAutogenerateConfig(item, locale, routes);
|
|
79
84
|
} else if ('slug' in item) {
|
|
80
85
|
return linkFromInternalSidebarLinkItem(item, locale);
|
|
81
86
|
} else {
|
|
@@ -83,22 +88,22 @@ function configItemToEntry(
|
|
|
83
88
|
return {
|
|
84
89
|
type: 'group',
|
|
85
90
|
label,
|
|
86
|
-
entries: item.items.
|
|
91
|
+
entries: item.items.flatMap((i) => configItemToEntry(i, locale, routes)),
|
|
87
92
|
collapsed: item.collapsed,
|
|
88
93
|
badge: getSidebarBadge(item.badge, locale, label),
|
|
89
94
|
};
|
|
90
95
|
}
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
/** Autogenerate
|
|
94
|
-
function
|
|
95
|
-
item:
|
|
98
|
+
/** Autogenerate links and groups from a user’s sidebar config. */
|
|
99
|
+
function entriesFromAutogenerateConfig(
|
|
100
|
+
item: AutoSidebarEntries,
|
|
96
101
|
locale: string | undefined,
|
|
97
|
-
routes: Route[]
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const { attrs, collapsed: subgroupCollapsed, directory } = item.autogenerate;
|
|
102
|
+
routes: Route[]
|
|
103
|
+
): (SidebarAutoLink | SidebarGroup)[] {
|
|
104
|
+
const { attrs, collapsed, directory } = item.autogenerate;
|
|
101
105
|
const localeDir = locale ? locale + '/' + directory : directory;
|
|
106
|
+
const autogenerate = { directory };
|
|
102
107
|
const dirDocs = routes.filter((doc) => {
|
|
103
108
|
const filePathFromContentDir = getRoutePathRelativeToCollectionRoot(doc, locale);
|
|
104
109
|
return (
|
|
@@ -109,20 +114,7 @@ function groupFromAutogenerateConfig(
|
|
|
109
114
|
);
|
|
110
115
|
});
|
|
111
116
|
const tree = treeify(dirDocs, locale, localeDir);
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
type: 'group',
|
|
115
|
-
label,
|
|
116
|
-
entries: sidebarFromDir(
|
|
117
|
-
tree,
|
|
118
|
-
currentPathname,
|
|
119
|
-
locale,
|
|
120
|
-
subgroupCollapsed ?? item.collapsed,
|
|
121
|
-
attrs
|
|
122
|
-
),
|
|
123
|
-
collapsed: item.collapsed,
|
|
124
|
-
badge: getSidebarBadge(item.badge, locale, label),
|
|
125
|
-
};
|
|
117
|
+
return sidebarFromDir(tree, { collapsed: collapsed ?? false, attrs }, autogenerate);
|
|
126
118
|
}
|
|
127
119
|
|
|
128
120
|
/** Create a link entry from a manual link item in user config. */
|
|
@@ -134,7 +126,12 @@ function linkFromSidebarLinkItem(item: SidebarLinkItem, locale: string | undefin
|
|
|
134
126
|
if (locale) href = '/' + locale + href;
|
|
135
127
|
}
|
|
136
128
|
const label = pickLang(item.translations, localeToLang(locale)) || item.label;
|
|
137
|
-
return makeSidebarLink(
|
|
129
|
+
return makeSidebarLink({
|
|
130
|
+
href,
|
|
131
|
+
label,
|
|
132
|
+
badge: getSidebarBadge(item.badge, locale, label),
|
|
133
|
+
attrs: item.attrs,
|
|
134
|
+
});
|
|
138
135
|
}
|
|
139
136
|
|
|
140
137
|
/** Create a link entry from an automatic internal link item in user config. */
|
|
@@ -169,39 +166,44 @@ function linkFromInternalSidebarLinkItem(
|
|
|
169
166
|
frontmatter.title;
|
|
170
167
|
const badge = item.badge ?? frontmatter.sidebar?.badge;
|
|
171
168
|
const attrs = { ...frontmatter.sidebar?.attrs, ...item.attrs };
|
|
172
|
-
return makeSidebarLink(
|
|
173
|
-
slugToPathname(route.id),
|
|
169
|
+
return makeSidebarLink({
|
|
170
|
+
href: slugToPathname(route.id),
|
|
174
171
|
label,
|
|
175
|
-
getSidebarBadge(badge, locale, label),
|
|
176
|
-
attrs
|
|
177
|
-
);
|
|
172
|
+
badge: getSidebarBadge(badge, locale, label),
|
|
173
|
+
attrs,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
interface MakeLinkOptions {
|
|
178
|
+
autogenerate?: SidebarAutogenerateRouteData | undefined;
|
|
179
|
+
href: string;
|
|
180
|
+
label: string;
|
|
181
|
+
badge?: Badge | undefined;
|
|
182
|
+
attrs?: LinkHTMLAttributes | undefined;
|
|
178
183
|
}
|
|
179
184
|
|
|
180
185
|
/** Process sidebar link options to create a link entry. */
|
|
186
|
+
function makeSidebarLink(opts: MakeLinkOptions & { autogenerate?: undefined }): SidebarManualLink;
|
|
181
187
|
function makeSidebarLink(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
attrs?: LinkHTMLAttributes
|
|
186
|
-
): SidebarLink {
|
|
188
|
+
opts: MakeLinkOptions & { autogenerate: SidebarAutogenerateRouteData }
|
|
189
|
+
): SidebarAutoLink;
|
|
190
|
+
function makeSidebarLink({ attrs, badge, href, label, autogenerate }: MakeLinkOptions) {
|
|
187
191
|
if (!isAbsoluteUrl(href)) {
|
|
188
192
|
href = formatPath(href);
|
|
189
193
|
}
|
|
190
|
-
return makeLink({ label, href, badge, attrs });
|
|
194
|
+
return makeLink({ label, href, badge, attrs, autogenerate });
|
|
191
195
|
}
|
|
192
196
|
|
|
193
197
|
/** Create a link entry */
|
|
194
|
-
function makeLink({
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}): SidebarLink {
|
|
204
|
-
return { type: 'link', ...opts, badge, isCurrent: false, attrs };
|
|
198
|
+
function makeLink({ attrs = {}, badge, autogenerate, ...opts }: MakeLinkOptions): SidebarLink {
|
|
199
|
+
return {
|
|
200
|
+
type: 'link',
|
|
201
|
+
...opts,
|
|
202
|
+
badge,
|
|
203
|
+
isCurrent: false,
|
|
204
|
+
attrs,
|
|
205
|
+
...(autogenerate ? { autogenerate } : {}),
|
|
206
|
+
};
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
/** Test if two paths are equivalent even if formatted differently. */
|
|
@@ -273,13 +275,18 @@ function treeify(routes: Route[], locale: string | undefined, baseDir: string):
|
|
|
273
275
|
}
|
|
274
276
|
|
|
275
277
|
/** Create a link entry for a given content collection entry. */
|
|
276
|
-
function linkFromRoute(
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
278
|
+
function linkFromRoute(
|
|
279
|
+
route: Route,
|
|
280
|
+
attrs: LinkHTMLAttributes | undefined,
|
|
281
|
+
autogenerate: SidebarAutogenerateRouteData
|
|
282
|
+
): SidebarAutoLink {
|
|
283
|
+
return makeSidebarLink({
|
|
284
|
+
href: slugToPathname(route.id),
|
|
285
|
+
label: route.entry.data.sidebar.label || route.entry.data.title,
|
|
286
|
+
badge: route.entry.data.sidebar.badge,
|
|
287
|
+
attrs: { ...attrs, ...route.entry.data.sidebar.attrs },
|
|
288
|
+
autogenerate,
|
|
289
|
+
});
|
|
283
290
|
}
|
|
284
291
|
|
|
285
292
|
/**
|
|
@@ -305,18 +312,22 @@ function sortDirEntries(dir: [string, Dir | Route][]): [string, Dir | Route][] {
|
|
|
305
312
|
});
|
|
306
313
|
}
|
|
307
314
|
|
|
315
|
+
interface SidebarDirOptions {
|
|
316
|
+
collapsed: boolean;
|
|
317
|
+
attrs: LinkHTMLAttributes | undefined;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
interface SidebarDirContext extends SidebarDirOptions {
|
|
321
|
+
fullPath: string;
|
|
322
|
+
dirName: string;
|
|
323
|
+
autogenerate: SidebarAutogenerateRouteData;
|
|
324
|
+
}
|
|
325
|
+
|
|
308
326
|
/** Create a group entry for a given content collection directory. */
|
|
309
|
-
function groupFromDir(
|
|
310
|
-
|
|
311
|
-
fullPath: string,
|
|
312
|
-
dirName: string,
|
|
313
|
-
currentPathname: string,
|
|
314
|
-
locale: string | undefined,
|
|
315
|
-
collapsed: boolean,
|
|
316
|
-
attrs?: LinkHTMLAttributes
|
|
317
|
-
): SidebarGroup {
|
|
327
|
+
function groupFromDir(dir: Dir, context: SidebarDirContext): SidebarAutoGroup {
|
|
328
|
+
const { fullPath, dirName, collapsed, autogenerate } = context;
|
|
318
329
|
const entries = sortDirEntries(Object.entries(dir)).map(([key, dirOrRoute]) =>
|
|
319
|
-
dirToItem(dirOrRoute, `${fullPath}/${key}`, key
|
|
330
|
+
dirToItem(dirOrRoute, { ...context, fullPath: `${fullPath}/${key}`, dirName: key })
|
|
320
331
|
);
|
|
321
332
|
return {
|
|
322
333
|
type: 'group',
|
|
@@ -324,34 +335,29 @@ function groupFromDir(
|
|
|
324
335
|
entries,
|
|
325
336
|
collapsed,
|
|
326
337
|
badge: undefined,
|
|
338
|
+
autogenerate,
|
|
327
339
|
};
|
|
328
340
|
}
|
|
329
341
|
|
|
330
342
|
/** Create a sidebar entry for a directory or content entry. */
|
|
331
343
|
function dirToItem(
|
|
332
344
|
dirOrRoute: Dir[string],
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
locale: string | undefined,
|
|
337
|
-
collapsed: boolean,
|
|
338
|
-
attrs?: LinkHTMLAttributes
|
|
339
|
-
): SidebarEntry {
|
|
345
|
+
context: SidebarDirContext
|
|
346
|
+
): SidebarAutoGroup | SidebarAutoLink {
|
|
347
|
+
const { attrs, autogenerate } = context;
|
|
340
348
|
return isDir(dirOrRoute)
|
|
341
|
-
? groupFromDir(dirOrRoute,
|
|
342
|
-
: linkFromRoute(dirOrRoute, attrs);
|
|
349
|
+
? groupFromDir(dirOrRoute, context)
|
|
350
|
+
: linkFromRoute(dirOrRoute, attrs, autogenerate);
|
|
343
351
|
}
|
|
344
352
|
|
|
345
353
|
/** Create a sidebar entry for a given content directory. */
|
|
346
354
|
function sidebarFromDir(
|
|
347
355
|
tree: Dir,
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
collapsed: boolean,
|
|
351
|
-
attrs?: LinkHTMLAttributes
|
|
356
|
+
options: SidebarDirOptions,
|
|
357
|
+
autogenerate: SidebarAutogenerateRouteData = rootAutogenerate
|
|
352
358
|
) {
|
|
353
359
|
return sortDirEntries(Object.entries(tree)).map(([key, dirOrRoute]) =>
|
|
354
|
-
dirToItem(dirOrRoute,
|
|
360
|
+
dirToItem(dirOrRoute, { ...options, fullPath: key, dirName: key, autogenerate })
|
|
355
361
|
);
|
|
356
362
|
}
|
|
357
363
|
|
|
@@ -374,7 +380,7 @@ const lastCurrentEntryByLocale = new Map<string | undefined, SidebarLink>();
|
|
|
374
380
|
export function getSidebar(pathname: string, locale: string | undefined): SidebarEntry[] {
|
|
375
381
|
let intermediateSidebar = intermediateSidebars.get(locale);
|
|
376
382
|
if (!intermediateSidebar) {
|
|
377
|
-
intermediateSidebar = getIntermediateSidebarFromConfig(config.sidebar,
|
|
383
|
+
intermediateSidebar = getIntermediateSidebarFromConfig(config.sidebar, locale);
|
|
378
384
|
intermediateSidebars.set(locale, intermediateSidebar);
|
|
379
385
|
}
|
|
380
386
|
setIntermediateSidebarCurrentEntry(intermediateSidebar, pathname, locale);
|
|
@@ -387,24 +393,23 @@ export function getSidebarFromConfig(
|
|
|
387
393
|
pathname: string,
|
|
388
394
|
locale: string | undefined
|
|
389
395
|
): SidebarEntry[] {
|
|
390
|
-
const sidebar = getIntermediateSidebarFromConfig(sidebarConfig,
|
|
396
|
+
const sidebar = getIntermediateSidebarFromConfig(sidebarConfig, locale);
|
|
391
397
|
const currentEntry = getSidebarCurrentEntry(sidebar, pathname);
|
|
392
398
|
if (currentEntry) currentEntry.isCurrent = true;
|
|
393
399
|
return sidebar;
|
|
394
400
|
}
|
|
395
401
|
|
|
396
|
-
/** Get the intermediate sidebar for
|
|
402
|
+
/** Get the intermediate sidebar for a locale using the specified sidebar config. */
|
|
397
403
|
function getIntermediateSidebarFromConfig(
|
|
398
404
|
sidebarConfig: StarlightConfig['sidebar'],
|
|
399
|
-
pathname: string,
|
|
400
405
|
locale: string | undefined
|
|
401
406
|
): SidebarEntry[] {
|
|
402
407
|
const routes = getLocaleRoutes(locale);
|
|
403
408
|
if (sidebarConfig) {
|
|
404
|
-
return sidebarConfig.
|
|
409
|
+
return sidebarConfig.flatMap((group) => configItemToEntry(group, locale, routes));
|
|
405
410
|
} else {
|
|
406
411
|
const tree = treeify(routes, locale, locale || '');
|
|
407
|
-
return sidebarFromDir(tree,
|
|
412
|
+
return sidebarFromDir(tree, { collapsed: false, attrs: undefined });
|
|
408
413
|
}
|
|
409
414
|
}
|
|
410
415
|
|
package/utils/routing/types.ts
CHANGED
|
@@ -14,7 +14,11 @@ export interface LocaleData {
|
|
|
14
14
|
locale: string | undefined;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export interface
|
|
17
|
+
export interface SidebarAutogenerateRouteData {
|
|
18
|
+
directory: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SidebarManualLink {
|
|
18
22
|
type: 'link';
|
|
19
23
|
label: string;
|
|
20
24
|
href: string;
|
|
@@ -23,7 +27,11 @@ export interface SidebarLink {
|
|
|
23
27
|
attrs: LinkHTMLAttributes;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
export interface
|
|
30
|
+
export interface SidebarAutoLink extends SidebarManualLink {
|
|
31
|
+
autogenerate: SidebarAutogenerateRouteData;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SidebarManualGroup {
|
|
27
35
|
type: 'group';
|
|
28
36
|
label: string;
|
|
29
37
|
entries: (SidebarLink | SidebarGroup)[];
|
|
@@ -31,6 +39,12 @@ export interface SidebarGroup {
|
|
|
31
39
|
badge: Badge | undefined;
|
|
32
40
|
}
|
|
33
41
|
|
|
42
|
+
export interface SidebarAutoGroup extends SidebarManualGroup {
|
|
43
|
+
autogenerate: SidebarAutogenerateRouteData;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type SidebarLink = SidebarManualLink | SidebarAutoLink;
|
|
47
|
+
export type SidebarGroup = SidebarManualGroup | SidebarAutoGroup;
|
|
34
48
|
export type SidebarEntry = SidebarLink | SidebarGroup;
|
|
35
49
|
|
|
36
50
|
export interface PaginationLinks {
|