@astrojs/starlight 0.35.1 → 0.35.3
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 +20 -0
- package/components/LanguageSelect.astro +0 -1
- package/components/MobileTableOfContents.astro +2 -2
- package/components/Select.astro +1 -2
- package/components/SidebarPersistState.ts +2 -2
- package/components/SidebarSublist.astro +2 -2
- package/components/ThemeSelect.astro +0 -1
- package/expressive-code.mjs +3 -1
- package/index.ts +3 -1
- package/integrations/asides-error.ts +9 -0
- package/integrations/asides.ts +9 -15
- package/integrations/expressive-code/index.ts +12 -120
- package/integrations/expressive-code/preprocessor.ts +121 -0
- package/integrations/expressive-code/translations.ts +4 -5
- package/integrations/heading-links.ts +1 -1
- package/integrations/remark-rehype-utils.ts +1 -1
- package/integrations/shared/absolutePathToLang.ts +4 -8
- package/integrations/sitemap.ts +1 -1
- package/integrations/virtual-user-config.ts +1 -1
- package/internal.ts +1 -1
- package/package.json +1 -1
- package/routes/common.astro +1 -1
- package/schema.ts +2 -2
- package/schemas/expressiveCode.ts +3 -1
- package/schemas/i18n.ts +2 -2
- package/translations/de.json +1 -1
- package/user-components/Aside.astro +1 -1
- package/utils/collection-fs.ts +21 -0
- package/utils/collection.ts +3 -11
- package/utils/createTranslationSystem.ts +3 -3
- package/utils/error-map.ts +9 -6
- package/utils/i18n.ts +3 -1
- package/utils/navigation.ts +1 -1
- package/utils/plugins.ts +11 -5
- package/utils/routing/data.ts +3 -3
- package/utils/slugs.ts +9 -3
- package/utils/starlight-page.ts +6 -6
- package/utils/translations-fs.ts +6 -5
- package/utils/translations.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @astrojs/starlight
|
|
2
2
|
|
|
3
|
+
## 0.35.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#3416](https://github.com/withastro/starlight/pull/3416) [`fcc0633`](https://github.com/withastro/starlight/commit/fcc0633ab14fc602f5c540972446e22c79746042) Thanks [@randomguy-2650](https://github.com/randomguy-2650)! - Updates German UI translations to be more idiomatic.
|
|
8
|
+
|
|
9
|
+
- [#1640](https://github.com/withastro/starlight/pull/1640) [`d1b3828`](https://github.com/withastro/starlight/commit/d1b3828cdfe8114884ab65049581e37624422ac4) Thanks [@hippotastic](https://github.com/hippotastic)! - Refactors various internal systems, improving code quality and maintainability.
|
|
10
|
+
|
|
11
|
+
- [#3421](https://github.com/withastro/starlight/pull/3421) [`97e8103`](https://github.com/withastro/starlight/commit/97e8103c5793d583931575dbe17924ef88a5b6fd) Thanks [@andersk](https://github.com/andersk)! - Removes an invalid `value` attribute from the language and theme selectors
|
|
12
|
+
|
|
13
|
+
- [#3422](https://github.com/withastro/starlight/pull/3422) [`9200fac`](https://github.com/withastro/starlight/commit/9200fac71ebe6e7bbbecee3d1893e744db01dc6e) Thanks [@andersk](https://github.com/andersk)! - Refactors collapsible sidebar sections and “on this page” dropdown to use `<span>` instead of `<div>`
|
|
14
|
+
|
|
15
|
+
## 0.35.2
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [#3341](https://github.com/withastro/starlight/pull/3341) [`10f6fe2`](https://github.com/withastro/starlight/commit/10f6fe22a981247293ee4de106736f1a6ae24b6a) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Prevents potential build issues with the Astro Cloudflare adapter due to the dependency on Node.js builtins.
|
|
20
|
+
|
|
21
|
+
- [#3327](https://github.com/withastro/starlight/pull/3327) [`bf58c60`](https://github.com/withastro/starlight/commit/bf58c60b9c3d5f5efdafbdba83cefa0566a367dc) Thanks [@delucis](https://github.com/delucis)! - Fixes a routing bug for docs pages with a slug authored with non-normalized composition. This could occur for filenames containing diacritics in some circumstances, causing 404s.
|
|
22
|
+
|
|
3
23
|
## 0.35.1
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
|
@@ -18,7 +18,6 @@ function localizedPathname(locale: string | undefined): string {
|
|
|
18
18
|
<Select
|
|
19
19
|
icon="translate"
|
|
20
20
|
label={Astro.locals.t('languageSelect.accessibleLabel')}
|
|
21
|
-
value={localizedPathname(Astro.locals.starlightRoute.locale)}
|
|
22
21
|
options={Object.entries(config.locales).map(([code, locale]) => ({
|
|
23
22
|
value: localizedPathname(code),
|
|
24
23
|
selected: code === Astro.locals.starlightRoute.locale,
|
|
@@ -11,10 +11,10 @@ const { toc } = Astro.locals.starlightRoute;
|
|
|
11
11
|
<nav aria-labelledby="starlight__on-this-page--mobile">
|
|
12
12
|
<details id="starlight__mobile-toc">
|
|
13
13
|
<summary id="starlight__on-this-page--mobile" class="sl-flex">
|
|
14
|
-
<
|
|
14
|
+
<span class="toggle sl-flex">
|
|
15
15
|
{Astro.locals.t('tableOfContents.onThisPage')}
|
|
16
16
|
<Icon name={'right-caret'} class="caret" size="1rem" />
|
|
17
|
-
</
|
|
17
|
+
</span>
|
|
18
18
|
<span class="display-current" />
|
|
19
19
|
</summary>
|
|
20
20
|
<div class="dropdown">
|
package/components/Select.astro
CHANGED
|
@@ -3,7 +3,6 @@ import Icon from '../user-components/Icon.astro';
|
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
5
|
label: string;
|
|
6
|
-
value: string;
|
|
7
6
|
icon: Parameters<typeof Icon>[0]['name'];
|
|
8
7
|
width?: string;
|
|
9
8
|
options: Array<{
|
|
@@ -26,7 +25,7 @@ interface Props {
|
|
|
26
25
|
<label style={`--sl-select-width: ${Astro.props.width}`}>
|
|
27
26
|
<span class="sr-only">{Astro.props.label}</span>
|
|
28
27
|
<Icon name={Astro.props.icon} class="icon label-icon" />
|
|
29
|
-
<select
|
|
28
|
+
<select autocomplete="off">
|
|
30
29
|
{
|
|
31
30
|
Astro.props.options.map(({ value, selected, label }) => (
|
|
32
31
|
<option value={value} selected={selected} set:html={label} />
|
|
@@ -19,11 +19,11 @@ interface SidebarState {
|
|
|
19
19
|
* and `hash` are read from the current page.
|
|
20
20
|
*/
|
|
21
21
|
const getState = (): SidebarState => {
|
|
22
|
-
let open = [];
|
|
22
|
+
let open: SidebarState['open'] = [];
|
|
23
23
|
const hash = target?.dataset.hash || '';
|
|
24
24
|
try {
|
|
25
25
|
const rawStoredState = sessionStorage.getItem(storageKey);
|
|
26
|
-
const storedState = JSON.parse(rawStoredState || '{}');
|
|
26
|
+
const storedState = JSON.parse(rawStoredState || '{}') as SidebarState;
|
|
27
27
|
if (Array.isArray(storedState.open) && storedState.hash === hash) open = storedState.open;
|
|
28
28
|
} catch {}
|
|
29
29
|
return {
|
|
@@ -39,7 +39,7 @@ const { sublist, nested } = Astro.props;
|
|
|
39
39
|
>
|
|
40
40
|
<SidebarRestorePoint />
|
|
41
41
|
<summary>
|
|
42
|
-
<
|
|
42
|
+
<span class="group-label">
|
|
43
43
|
<span class="large">{entry.label}</span>
|
|
44
44
|
{entry.badge && (
|
|
45
45
|
<Badge
|
|
@@ -48,7 +48,7 @@ const { sublist, nested } = Astro.props;
|
|
|
48
48
|
text={entry.badge.text}
|
|
49
49
|
/>
|
|
50
50
|
)}
|
|
51
|
-
</
|
|
51
|
+
</span>
|
|
52
52
|
<Icon name="right-caret" class="caret" size="1.25rem" />
|
|
53
53
|
</summary>
|
|
54
54
|
<Astro.self sublist={entry.entries} nested />
|
|
@@ -7,7 +7,6 @@ import Select from './Select.astro';
|
|
|
7
7
|
<Select
|
|
8
8
|
icon="laptop"
|
|
9
9
|
label={Astro.locals.t('themeSelect.accessibleLabel')}
|
|
10
|
-
value="auto"
|
|
11
10
|
options={[
|
|
12
11
|
{ label: Astro.locals.t('themeSelect.dark'), selected: false, value: 'dark' },
|
|
13
12
|
{ label: Astro.locals.t('themeSelect.light'), selected: false, value: 'light' },
|
package/expressive-code.mjs
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
export * from 'astro-expressive-code';
|
|
17
17
|
|
|
18
|
-
//
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
19
|
+
// @ts-ignore - Types are provided by the separate `expressive-code.d.ts` file and this may not be
|
|
20
|
+
// a type error for some users running `tsc --noEmit` depending on their `tsconfig.json` settings.
|
|
19
21
|
export function defineEcConfig(config) {
|
|
20
22
|
return config;
|
|
21
23
|
}
|
package/index.ts
CHANGED
|
@@ -92,7 +92,9 @@ export default function StarlightIntegration(
|
|
|
92
92
|
// config or by a plugin.
|
|
93
93
|
const allIntegrations = [...config.integrations, ...integrations];
|
|
94
94
|
if (!allIntegrations.find(({ name }) => name === 'astro-expressive-code')) {
|
|
95
|
-
integrations.push(
|
|
95
|
+
integrations.push(
|
|
96
|
+
...starlightExpressiveCode({ astroConfig: config, starlightConfig, useTranslations })
|
|
97
|
+
);
|
|
96
98
|
}
|
|
97
99
|
if (!allIntegrations.find(({ name }) => name === '@astrojs/sitemap')) {
|
|
98
100
|
integrations.push(starlightSitemap(starlightConfig));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AstroError } from 'astro/errors';
|
|
2
|
+
|
|
3
|
+
export function throwInvalidAsideIconError(icon: string) {
|
|
4
|
+
throw new AstroError(
|
|
5
|
+
'Invalid aside icon',
|
|
6
|
+
`An aside custom icon must be set to the name of one of Starlight’s built-in icons, but received \`${icon}\`.\n\n` +
|
|
7
|
+
'See https://starlight.astro.build/reference/icons/#all-icons for a list of available icons.'
|
|
8
|
+
);
|
|
9
|
+
}
|
package/integrations/asides.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { getRemarkRehypeDocsCollectionPath, shouldTransformFile } from './remark
|
|
|
19
19
|
import { Icons } from '../components/Icons';
|
|
20
20
|
import { fromHtml } from 'hast-util-from-html';
|
|
21
21
|
import type { Element } from 'hast';
|
|
22
|
-
import {
|
|
22
|
+
import { throwInvalidAsideIconError } from './asides-error';
|
|
23
23
|
|
|
24
24
|
interface AsidesOptions {
|
|
25
25
|
starlightConfig: Pick<StarlightConfig, 'defaultLocale' | 'locales'>;
|
|
@@ -29,22 +29,22 @@ interface AsidesOptions {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Hacky function that generates an mdast HTML tree ready for conversion to HTML by rehype. */
|
|
32
|
-
function h(el: string, attrs: Properties = {}, children:
|
|
32
|
+
function h(el: string, attrs: Properties = {}, children: unknown[] = []): P {
|
|
33
33
|
const { tagName, properties } = _h(el, attrs);
|
|
34
34
|
return {
|
|
35
35
|
type: 'paragraph',
|
|
36
36
|
data: { hName: tagName, hProperties: properties },
|
|
37
|
-
children,
|
|
37
|
+
children: children as P['children'],
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/** Hacky function that generates an mdast SVG tree ready for conversion to HTML by rehype. */
|
|
42
|
-
function s(el: string, attrs: Properties = {}, children:
|
|
42
|
+
function s(el: string, attrs: Properties = {}, children: unknown[] = []): P {
|
|
43
43
|
const { tagName, properties } = _s(el, attrs);
|
|
44
44
|
return {
|
|
45
45
|
type: 'paragraph',
|
|
46
46
|
data: { hName: tagName, hProperties: properties },
|
|
47
|
-
children,
|
|
47
|
+
children: children as P['children'],
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -93,14 +93,16 @@ function transformUnhandledDirective(
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/** Hacky function that generates the children of an mdast SVG tree. */
|
|
96
|
-
function makeSvgChildNodes(children: Result['children']):
|
|
96
|
+
function makeSvgChildNodes(children: Result['children']): P[] {
|
|
97
97
|
const nodes: P[] = [];
|
|
98
98
|
for (const child of children) {
|
|
99
99
|
if (child.type !== 'element') continue;
|
|
100
100
|
nodes.push({
|
|
101
101
|
type: 'paragraph',
|
|
102
102
|
data: { hName: child.tagName, hProperties: child.properties },
|
|
103
|
-
|
|
103
|
+
// We are explicitly casting to the expected type here due to the hacky nature of this
|
|
104
|
+
// function which only works with SVG elements.
|
|
105
|
+
children: makeSvgChildNodes(child.children) as unknown as P['children'],
|
|
104
106
|
});
|
|
105
107
|
}
|
|
106
108
|
return nodes;
|
|
@@ -253,14 +255,6 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
|
|
|
253
255
|
|
|
254
256
|
type RemarkPlugins = NonNullable<NonNullable<AstroUserConfig['markdown']>['remarkPlugins']>;
|
|
255
257
|
|
|
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
|
-
|
|
264
258
|
export function starlightAsides(options: AsidesOptions): RemarkPlugins {
|
|
265
259
|
return [remarkDirective, remarkAsides(options)];
|
|
266
260
|
}
|
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type AstroExpressiveCodeOptions,
|
|
4
|
-
type CustomConfigPreprocessors,
|
|
5
|
-
} from 'astro-expressive-code';
|
|
6
|
-
import { addClassName } from 'astro-expressive-code/hast';
|
|
7
|
-
import type { AstroIntegration } from 'astro';
|
|
1
|
+
import { astroExpressiveCode, type AstroExpressiveCodeOptions } from 'astro-expressive-code';
|
|
2
|
+
import type { AstroConfig, AstroIntegration } from 'astro';
|
|
8
3
|
import type { HookParameters, StarlightConfig } from '../../types';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
applyStarlightUiThemeColors,
|
|
14
|
-
preprocessThemes,
|
|
15
|
-
type ThemeObjectOrBundledThemeName,
|
|
16
|
-
} from './theming';
|
|
17
|
-
import { addTranslations } from './translations';
|
|
4
|
+
import { getStarlightEcConfigPreprocessor } from './preprocessor';
|
|
5
|
+
import { type ThemeObjectOrBundledThemeName } from './theming';
|
|
6
|
+
import { getCollectionPosixPath } from '../../utils/collection-fs';
|
|
18
7
|
|
|
19
8
|
export type StarlightExpressiveCodeOptions = Omit<AstroExpressiveCodeOptions, 'themes'> & {
|
|
20
9
|
/**
|
|
@@ -63,114 +52,13 @@ export type StarlightExpressiveCodeOptions = Omit<AstroExpressiveCodeOptions, 't
|
|
|
63
52
|
};
|
|
64
53
|
|
|
65
54
|
type StarlightEcIntegrationOptions = {
|
|
55
|
+
astroConfig: AstroConfig;
|
|
66
56
|
starlightConfig: StarlightConfig;
|
|
67
57
|
useTranslations: HookParameters<'config:setup'>['useTranslations'];
|
|
68
58
|
};
|
|
69
59
|
|
|
70
|
-
/**
|
|
71
|
-
* Create an Expressive Code configuration preprocessor based on Starlight config.
|
|
72
|
-
* Used internally to set up Expressive Code and by the `<Code>` component.
|
|
73
|
-
*/
|
|
74
|
-
export function getStarlightEcConfigPreprocessor({
|
|
75
|
-
starlightConfig,
|
|
76
|
-
useTranslations,
|
|
77
|
-
}: StarlightEcIntegrationOptions): CustomConfigPreprocessors['preprocessAstroIntegrationConfig'] {
|
|
78
|
-
return (input): AstroExpressiveCodeOptions => {
|
|
79
|
-
const astroConfig = input.astroConfig;
|
|
80
|
-
const ecConfig = input.ecConfig as StarlightExpressiveCodeOptions;
|
|
81
|
-
|
|
82
|
-
const {
|
|
83
|
-
themes: themesInput,
|
|
84
|
-
cascadeLayer,
|
|
85
|
-
customizeTheme,
|
|
86
|
-
styleOverrides: { textMarkers: textMarkersStyleOverrides, ...otherStyleOverrides } = {},
|
|
87
|
-
useStarlightDarkModeSwitch,
|
|
88
|
-
useStarlightUiThemeColors = ecConfig.themes === undefined,
|
|
89
|
-
plugins = [],
|
|
90
|
-
...rest
|
|
91
|
-
} = ecConfig;
|
|
92
|
-
|
|
93
|
-
// Handle the `themes` option
|
|
94
|
-
const themes = preprocessThemes(themesInput);
|
|
95
|
-
if (useStarlightUiThemeColors === true && themes.length < 2) {
|
|
96
|
-
console.warn(
|
|
97
|
-
`*** Warning: Using the config option "useStarlightUiThemeColors: true" ` +
|
|
98
|
-
`with a single theme is not recommended. For better color contrast, ` +
|
|
99
|
-
`please provide at least one dark and one light theme.\n`
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Add the `not-content` class to all rendered blocks to prevent them from being affected
|
|
104
|
-
// by Starlight's default content styles
|
|
105
|
-
plugins.push({
|
|
106
|
-
name: 'Starlight Plugin',
|
|
107
|
-
hooks: {
|
|
108
|
-
postprocessRenderedBlock: ({ renderData }) => {
|
|
109
|
-
addClassName(renderData.blockAst, 'not-content');
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// Add Expressive Code UI translations for all defined locales
|
|
115
|
-
addTranslations(starlightConfig, useTranslations);
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
themes,
|
|
119
|
-
customizeTheme: (theme) => {
|
|
120
|
-
if (useStarlightUiThemeColors) {
|
|
121
|
-
applyStarlightUiThemeColors(theme);
|
|
122
|
-
}
|
|
123
|
-
if (customizeTheme) {
|
|
124
|
-
theme = customizeTheme(theme) ?? theme;
|
|
125
|
-
}
|
|
126
|
-
return theme;
|
|
127
|
-
},
|
|
128
|
-
defaultLocale: starlightConfig.defaultLocale?.lang ?? starlightConfig.defaultLocale?.locale,
|
|
129
|
-
themeCssSelector: (theme, { styleVariants }) => {
|
|
130
|
-
// If one dark and one light theme are available, and the user has not disabled it,
|
|
131
|
-
// generate theme CSS selectors compatible with Starlight's dark mode switch
|
|
132
|
-
if (useStarlightDarkModeSwitch !== false && styleVariants.length >= 2) {
|
|
133
|
-
const baseTheme = styleVariants[0]?.theme;
|
|
134
|
-
const altTheme = styleVariants.find((v) => v.theme.type !== baseTheme?.type)?.theme;
|
|
135
|
-
if (theme === baseTheme || theme === altTheme) return `[data-theme='${theme.type}']`;
|
|
136
|
-
}
|
|
137
|
-
// Return the default selector
|
|
138
|
-
return `[data-theme='${theme.name}']`;
|
|
139
|
-
},
|
|
140
|
-
cascadeLayer: cascadeLayer ?? 'starlight.components',
|
|
141
|
-
styleOverrides: {
|
|
142
|
-
borderRadius: '0px',
|
|
143
|
-
borderWidth: '1px',
|
|
144
|
-
codePaddingBlock: '0.75rem',
|
|
145
|
-
codePaddingInline: '1rem',
|
|
146
|
-
codeFontFamily: 'var(--__sl-font-mono)',
|
|
147
|
-
codeFontSize: 'var(--sl-text-code)',
|
|
148
|
-
codeLineHeight: 'var(--sl-line-height)',
|
|
149
|
-
uiFontFamily: 'var(--__sl-font)',
|
|
150
|
-
textMarkers: {
|
|
151
|
-
lineDiffIndicatorMarginLeft: '0.25rem',
|
|
152
|
-
defaultChroma: '45',
|
|
153
|
-
backgroundOpacity: '60%',
|
|
154
|
-
...textMarkersStyleOverrides,
|
|
155
|
-
},
|
|
156
|
-
...otherStyleOverrides,
|
|
157
|
-
},
|
|
158
|
-
getBlockLocale: ({ file }) => {
|
|
159
|
-
if (file.url) {
|
|
160
|
-
const locale = slugToLocale(file.url.pathname.slice(1), starlightConfig);
|
|
161
|
-
return localeToLang(starlightConfig, locale);
|
|
162
|
-
}
|
|
163
|
-
// Note that EC cannot use the `absolutePathToLang` helper passed down to plugins as this callback
|
|
164
|
-
// is also called in the context of the `<Code>` component.
|
|
165
|
-
return absolutePathToLang(file.path, { starlightConfig, astroConfig });
|
|
166
|
-
},
|
|
167
|
-
plugins,
|
|
168
|
-
...rest,
|
|
169
|
-
};
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
60
|
export const starlightExpressiveCode = ({
|
|
61
|
+
astroConfig,
|
|
174
62
|
starlightConfig,
|
|
175
63
|
useTranslations,
|
|
176
64
|
}: StarlightEcIntegrationOptions): AstroIntegration[] => {
|
|
@@ -213,11 +101,15 @@ export const starlightExpressiveCode = ({
|
|
|
213
101
|
typeof starlightConfig.expressiveCode === 'object'
|
|
214
102
|
? (starlightConfig.expressiveCode as AstroExpressiveCodeOptions)
|
|
215
103
|
: {};
|
|
104
|
+
|
|
105
|
+
const docsPath = getCollectionPosixPath('docs', astroConfig.srcDir);
|
|
106
|
+
|
|
216
107
|
return [
|
|
217
108
|
astroExpressiveCode({
|
|
218
109
|
...configArgs,
|
|
219
110
|
customConfigPreprocessors: {
|
|
220
111
|
preprocessAstroIntegrationConfig: getStarlightEcConfigPreprocessor({
|
|
112
|
+
docsPath,
|
|
221
113
|
starlightConfig,
|
|
222
114
|
useTranslations,
|
|
223
115
|
}),
|
|
@@ -225,7 +117,7 @@ export const starlightExpressiveCode = ({
|
|
|
225
117
|
import starlightConfig from 'virtual:starlight/user-config'
|
|
226
118
|
import { useTranslations, getStarlightEcConfigPreprocessor } from '@astrojs/starlight/internal'
|
|
227
119
|
|
|
228
|
-
export default getStarlightEcConfigPreprocessor({ starlightConfig, useTranslations })
|
|
120
|
+
export default getStarlightEcConfigPreprocessor({ docsPath: ${JSON.stringify(docsPath)}, starlightConfig, useTranslations })
|
|
229
121
|
`,
|
|
230
122
|
},
|
|
231
123
|
}),
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AstroExpressiveCodeOptions,
|
|
3
|
+
type CustomConfigPreprocessors,
|
|
4
|
+
} from 'astro-expressive-code';
|
|
5
|
+
import { addClassName } from 'astro-expressive-code/hast';
|
|
6
|
+
import type { StarlightExpressiveCodeOptions } from './index';
|
|
7
|
+
import type { HookParameters, StarlightConfig } from '../../types';
|
|
8
|
+
import { applyStarlightUiThemeColors, preprocessThemes } from './theming';
|
|
9
|
+
import { addTranslations } from './translations';
|
|
10
|
+
import { slugToLocale } from '../shared/slugToLocale';
|
|
11
|
+
import { localeToLang } from '../shared/localeToLang';
|
|
12
|
+
import { absolutePathToLang } from '../shared/absolutePathToLang';
|
|
13
|
+
|
|
14
|
+
type StarlightEcConfigPreprocessorOptions = {
|
|
15
|
+
docsPath: string;
|
|
16
|
+
starlightConfig: StarlightConfig;
|
|
17
|
+
useTranslations: HookParameters<'config:setup'>['useTranslations'];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create an Expressive Code configuration preprocessor based on Starlight config.
|
|
22
|
+
* Used internally to set up Expressive Code and by the `<Code>` component.
|
|
23
|
+
*/
|
|
24
|
+
export function getStarlightEcConfigPreprocessor({
|
|
25
|
+
docsPath,
|
|
26
|
+
starlightConfig,
|
|
27
|
+
useTranslations,
|
|
28
|
+
}: StarlightEcConfigPreprocessorOptions): CustomConfigPreprocessors['preprocessAstroIntegrationConfig'] {
|
|
29
|
+
return (input): AstroExpressiveCodeOptions => {
|
|
30
|
+
const ecConfig = input.ecConfig as StarlightExpressiveCodeOptions;
|
|
31
|
+
|
|
32
|
+
const {
|
|
33
|
+
themes: themesInput,
|
|
34
|
+
cascadeLayer,
|
|
35
|
+
customizeTheme,
|
|
36
|
+
styleOverrides: { textMarkers: textMarkersStyleOverrides, ...otherStyleOverrides } = {},
|
|
37
|
+
useStarlightDarkModeSwitch,
|
|
38
|
+
useStarlightUiThemeColors = ecConfig.themes === undefined,
|
|
39
|
+
plugins = [],
|
|
40
|
+
...rest
|
|
41
|
+
} = ecConfig;
|
|
42
|
+
|
|
43
|
+
// Handle the `themes` option
|
|
44
|
+
const themes = preprocessThemes(themesInput);
|
|
45
|
+
if (useStarlightUiThemeColors === true && themes.length < 2) {
|
|
46
|
+
console.warn(
|
|
47
|
+
`*** Warning: Using the config option "useStarlightUiThemeColors: true" ` +
|
|
48
|
+
`with a single theme is not recommended. For better color contrast, ` +
|
|
49
|
+
`please provide at least one dark and one light theme.\n`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Add the `not-content` class to all rendered blocks to prevent them from being affected
|
|
54
|
+
// by Starlight's default content styles
|
|
55
|
+
plugins.push({
|
|
56
|
+
name: 'Starlight Plugin',
|
|
57
|
+
hooks: {
|
|
58
|
+
postprocessRenderedBlock: ({ renderData }) => {
|
|
59
|
+
addClassName(renderData.blockAst, 'not-content');
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Add Expressive Code UI translations for all defined locales
|
|
65
|
+
addTranslations(starlightConfig, useTranslations);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
themes,
|
|
69
|
+
customizeTheme: (theme) => {
|
|
70
|
+
if (useStarlightUiThemeColors) {
|
|
71
|
+
applyStarlightUiThemeColors(theme);
|
|
72
|
+
}
|
|
73
|
+
if (customizeTheme) {
|
|
74
|
+
theme = customizeTheme(theme) ?? theme;
|
|
75
|
+
}
|
|
76
|
+
return theme;
|
|
77
|
+
},
|
|
78
|
+
defaultLocale: starlightConfig.defaultLocale?.lang ?? starlightConfig.defaultLocale?.locale,
|
|
79
|
+
themeCssSelector: (theme, { styleVariants }) => {
|
|
80
|
+
// If one dark and one light theme are available, and the user has not disabled it,
|
|
81
|
+
// generate theme CSS selectors compatible with Starlight's dark mode switch
|
|
82
|
+
if (useStarlightDarkModeSwitch !== false && styleVariants.length >= 2) {
|
|
83
|
+
const baseTheme = styleVariants[0]?.theme;
|
|
84
|
+
const altTheme = styleVariants.find((v) => v.theme.type !== baseTheme?.type)?.theme;
|
|
85
|
+
if (theme === baseTheme || theme === altTheme) return `[data-theme='${theme.type}']`;
|
|
86
|
+
}
|
|
87
|
+
// Return the default selector
|
|
88
|
+
return `[data-theme='${theme.name}']`;
|
|
89
|
+
},
|
|
90
|
+
cascadeLayer: cascadeLayer ?? 'starlight.components',
|
|
91
|
+
styleOverrides: {
|
|
92
|
+
borderRadius: '0px',
|
|
93
|
+
borderWidth: '1px',
|
|
94
|
+
codePaddingBlock: '0.75rem',
|
|
95
|
+
codePaddingInline: '1rem',
|
|
96
|
+
codeFontFamily: 'var(--__sl-font-mono)',
|
|
97
|
+
codeFontSize: 'var(--sl-text-code)',
|
|
98
|
+
codeLineHeight: 'var(--sl-line-height)',
|
|
99
|
+
uiFontFamily: 'var(--__sl-font)',
|
|
100
|
+
textMarkers: {
|
|
101
|
+
lineDiffIndicatorMarginLeft: '0.25rem',
|
|
102
|
+
defaultChroma: '45',
|
|
103
|
+
backgroundOpacity: '60%',
|
|
104
|
+
...textMarkersStyleOverrides,
|
|
105
|
+
},
|
|
106
|
+
...otherStyleOverrides,
|
|
107
|
+
},
|
|
108
|
+
getBlockLocale: ({ file }) => {
|
|
109
|
+
if (file.url) {
|
|
110
|
+
const locale = slugToLocale(file.url.pathname.slice(1), starlightConfig);
|
|
111
|
+
return localeToLang(starlightConfig, locale);
|
|
112
|
+
}
|
|
113
|
+
// Note that EC cannot use the `absolutePathToLang` helper passed down to plugins as this callback
|
|
114
|
+
// is also called in the context of the `<Code>` component.
|
|
115
|
+
return absolutePathToLang(file.path, { docsPath, starlightConfig });
|
|
116
|
+
},
|
|
117
|
+
plugins,
|
|
118
|
+
...rest,
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -3,10 +3,7 @@ import type { StarlightConfig } from '../../types';
|
|
|
3
3
|
import type { createTranslationSystemFromFs } from '../../utils/translations-fs';
|
|
4
4
|
import { localeToLang } from '../shared/localeToLang';
|
|
5
5
|
|
|
6
|
-
export function addTranslations(
|
|
7
|
-
config: StarlightConfig,
|
|
8
|
-
useTranslations: ReturnType<typeof createTranslationSystemFromFs>
|
|
9
|
-
) {
|
|
6
|
+
export function addTranslations(config: StarlightConfig, useTranslations: UseTranslations) {
|
|
10
7
|
addTranslationsForLocale(config.defaultLocale.locale, config, useTranslations);
|
|
11
8
|
if (config.isMultilingual) {
|
|
12
9
|
for (const locale in config.locales) {
|
|
@@ -19,7 +16,7 @@ export function addTranslations(
|
|
|
19
16
|
function addTranslationsForLocale(
|
|
20
17
|
locale: string | undefined,
|
|
21
18
|
config: StarlightConfig,
|
|
22
|
-
useTranslations:
|
|
19
|
+
useTranslations: UseTranslations
|
|
23
20
|
) {
|
|
24
21
|
const lang = localeToLang(config, locale);
|
|
25
22
|
const t = useTranslations(lang);
|
|
@@ -35,3 +32,5 @@ function addTranslationsForLocale(
|
|
|
35
32
|
pluginFramesTexts.overrideTexts(lang, { [ecId]: translation });
|
|
36
33
|
});
|
|
37
34
|
}
|
|
35
|
+
|
|
36
|
+
type UseTranslations = Awaited<ReturnType<typeof createTranslationSystemFromFs>>;
|
|
@@ -55,7 +55,7 @@ export default function rehypeAutolinkHeadings(
|
|
|
55
55
|
{
|
|
56
56
|
type: 'element',
|
|
57
57
|
tagName: 'a',
|
|
58
|
-
properties: { class: 'sl-anchor-link', href: '#' + node.properties.id },
|
|
58
|
+
properties: { class: 'sl-anchor-link', href: '#' + String(node.properties.id) },
|
|
59
59
|
children: [AnchorLinkIcon, h('span', { class: 'sr-only' }, accessibleLabel)],
|
|
60
60
|
}
|
|
61
61
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AstroConfig } from 'astro';
|
|
2
2
|
import type { VFile } from 'vfile';
|
|
3
|
-
import { resolveCollectionPath } from '../utils/collection';
|
|
3
|
+
import { resolveCollectionPath } from '../utils/collection-fs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Returns the path to the Starlight docs collection ready to be used in remark/rehype plugins,
|
|
@@ -1,24 +1,20 @@
|
|
|
1
|
-
import { pathToFileURL } from 'node:url';
|
|
2
|
-
import type { AstroConfig } from 'astro';
|
|
3
1
|
import type { StarlightConfig } from '../../types';
|
|
4
2
|
import { localeToLang } from './localeToLang';
|
|
5
|
-
import { getCollectionPath } from '../../utils/collection';
|
|
6
3
|
import { slugToLocale } from './slugToLocale';
|
|
7
4
|
|
|
8
5
|
/** Get current language from an absolute file path. */
|
|
9
6
|
export function absolutePathToLang(
|
|
10
7
|
path: string,
|
|
11
8
|
{
|
|
9
|
+
docsPath,
|
|
12
10
|
starlightConfig,
|
|
13
|
-
astroConfig,
|
|
14
11
|
}: {
|
|
12
|
+
docsPath: string;
|
|
15
13
|
starlightConfig: Pick<StarlightConfig, 'defaultLocale' | 'locales'>;
|
|
16
|
-
astroConfig: { root: AstroConfig['root']; srcDir: AstroConfig['srcDir'] };
|
|
17
14
|
}
|
|
18
15
|
): string {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
path = pathToFileURL(path).pathname;
|
|
16
|
+
// Format path to unix style path.
|
|
17
|
+
path = path?.replace(/\\/g, '/');
|
|
22
18
|
// Ensure that the page path starts with a slash if the docs directory also does,
|
|
23
19
|
// which makes stripping the docs path in the next step work on Windows, too.
|
|
24
20
|
if (path && !path.startsWith('/') && docsPath.startsWith('/')) path = '/' + path;
|
package/integrations/sitemap.ts
CHANGED
|
@@ -7,7 +7,7 @@ export function getSitemapConfig(opts: StarlightConfig): SitemapOptions {
|
|
|
7
7
|
sitemapConfig.i18n = {
|
|
8
8
|
defaultLocale: opts.defaultLocale.locale || 'root',
|
|
9
9
|
locales: Object.fromEntries(
|
|
10
|
-
Object.entries(opts.locales).map(([locale, config]) => [locale, config
|
|
10
|
+
Object.entries(opts.locales).map(([locale, config]) => [locale, config!.lang!])
|
|
11
11
|
),
|
|
12
12
|
};
|
|
13
13
|
}
|
|
@@ -2,7 +2,7 @@ import type { AstroConfig, HookParameters, ViteUserConfig } from 'astro';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { resolveCollectionPath } from '../utils/collection';
|
|
5
|
+
import { resolveCollectionPath } from '../utils/collection-fs';
|
|
6
6
|
import type { StarlightConfig } from '../utils/user-config';
|
|
7
7
|
import { getAllNewestCommitDate } from '../utils/git';
|
|
8
8
|
import type { PluginTranslations } from '../utils/plugins';
|
package/internal.ts
CHANGED
package/package.json
CHANGED
package/routes/common.astro
CHANGED
|
@@ -15,7 +15,7 @@ const route = await getRoute(Astro);
|
|
|
15
15
|
*/
|
|
16
16
|
const renderResult = await render(route.entry);
|
|
17
17
|
|
|
18
|
-
await attachRouteDataAndRunMiddleware(Astro,
|
|
18
|
+
await attachRouteDataAndRunMiddleware(Astro, useRouteData(Astro, route, renderResult));
|
|
19
19
|
|
|
20
20
|
const { Content, entry } = Astro.locals.starlightRoute;
|
|
21
21
|
---
|
package/schema.ts
CHANGED
|
@@ -123,7 +123,7 @@ type BaseSchemaWithoutEffects =
|
|
|
123
123
|
type BaseSchema = BaseSchemaWithoutEffects | z.ZodEffects<BaseSchemaWithoutEffects>;
|
|
124
124
|
|
|
125
125
|
/** Type that extends Starlight’s default schema with an optional, user-defined schema. */
|
|
126
|
-
type ExtendedSchema<T extends BaseSchema
|
|
126
|
+
type ExtendedSchema<T extends BaseSchema = never> = [T] extends [never]
|
|
127
127
|
? DefaultSchema
|
|
128
128
|
: T extends BaseSchema
|
|
129
129
|
? z.ZodIntersection<DefaultSchema, T>
|
|
@@ -155,7 +155,7 @@ interface DocsSchemaOpts<T extends BaseSchema> {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/** Content collection schema for Starlight’s `docs` collection. */
|
|
158
|
-
export function docsSchema<T extends BaseSchema
|
|
158
|
+
export function docsSchema<T extends BaseSchema = never>(
|
|
159
159
|
...args: [DocsSchemaOpts<T>?]
|
|
160
160
|
): (context: SchemaContext) => ExtendedSchema<T> {
|
|
161
161
|
const [options = {}] = args;
|
|
@@ -4,7 +4,9 @@ import type { StarlightExpressiveCodeOptions } from '../integrations/expressive-
|
|
|
4
4
|
export const ExpressiveCodeSchema = () =>
|
|
5
5
|
z
|
|
6
6
|
.union([
|
|
7
|
-
z.custom<StarlightExpressiveCodeOptions>(
|
|
7
|
+
z.custom<StarlightExpressiveCodeOptions>(
|
|
8
|
+
(value) => typeof value === 'object' && (value as StarlightExpressiveCodeOptions)
|
|
9
|
+
),
|
|
8
10
|
z.boolean(),
|
|
9
11
|
])
|
|
10
12
|
.describe(
|
package/schemas/i18n.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'astro/zod';
|
|
2
2
|
|
|
3
|
-
interface i18nSchemaOpts<T extends z.AnyZodObject = z.
|
|
3
|
+
interface i18nSchemaOpts<T extends z.AnyZodObject = z.SomeZodObject> {
|
|
4
4
|
/**
|
|
5
5
|
* Extend Starlight’s i18n schema with additional fields.
|
|
6
6
|
*
|
|
@@ -41,7 +41,7 @@ type ExtendedSchema<T extends z.AnyZodObject> = T extends z.AnyZodObject
|
|
|
41
41
|
: DefaultI18nSchema;
|
|
42
42
|
|
|
43
43
|
/** Content collection schema for Starlight’s optional `i18n` collection. */
|
|
44
|
-
export function i18nSchema<T extends z.AnyZodObject = z.
|
|
44
|
+
export function i18nSchema<T extends z.AnyZodObject = z.SomeZodObject>({
|
|
45
45
|
extend = z.object({}) as T,
|
|
46
46
|
}: i18nSchemaOpts<T> = {}): ExtendedSchema<T> {
|
|
47
47
|
return defaultI18nSchema().merge(extend).passthrough() as ExtendedSchema<T>;
|
package/translations/de.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"tableOfContents.overview": "Überblick",
|
|
16
16
|
"i18n.untranslatedContent": "Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.",
|
|
17
17
|
"page.editLink": "Seite bearbeiten",
|
|
18
|
-
"page.lastUpdated": "Zuletzt
|
|
18
|
+
"page.lastUpdated": "Zuletzt aktualisiert:",
|
|
19
19
|
"page.previousLink": "Vorherige Seite",
|
|
20
20
|
"page.nextLink": "Nächste Seite",
|
|
21
21
|
"page.draft": "Dieser Inhalt ist ein Entwurf und wird nicht in den Produktions-Builds enthalten sein.",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { AstroError } from 'astro/errors';
|
|
3
3
|
import Icon from './Icon.astro';
|
|
4
4
|
import { Icons, type StarlightIcon } from '../components/Icons';
|
|
5
|
-
import { throwInvalidAsideIconError } from '../integrations/asides';
|
|
5
|
+
import { throwInvalidAsideIconError } from '../integrations/asides-error';
|
|
6
6
|
|
|
7
7
|
const asideVariants = ['note', 'tip', 'caution', 'danger'] as const;
|
|
8
8
|
const icons = { note: 'information', tip: 'rocket', caution: 'warning', danger: 'error' } as const;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { getCollectionUrl, type StarlightCollection } from './collection';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @see {@link file://./collection.ts} for more context about this file.
|
|
7
|
+
*
|
|
8
|
+
* Below are various functions to easily get paths to collections used in Starlight that rely on
|
|
9
|
+
* Node.js builtins. They exist in a separate file from {@link file://./collection.ts} to avoid
|
|
10
|
+
* potentially importing Node.js builtins in the final bundle.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export function resolveCollectionPath(collection: StarlightCollection, srcDir: URL) {
|
|
14
|
+
return resolve(fileURLToPath(srcDir), `content/${collection}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getCollectionPosixPath(collection: StarlightCollection, srcDir: URL) {
|
|
18
|
+
// TODO: when Astro minimum Node.js version is >= 20.13.0, refactor to use the `fileURLToPath`
|
|
19
|
+
// second optional argument to enforce POSIX paths by setting `windows: false`.
|
|
20
|
+
return fileURLToPath(getCollectionUrl(collection, srcDir)).replace(/\\/g, '/');
|
|
21
|
+
}
|
package/utils/collection.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
|
-
|
|
4
|
-
const collectionNames = ['docs', 'i18n'] as const;
|
|
5
|
-
export type StarlightCollection = (typeof collectionNames)[number];
|
|
1
|
+
export type StarlightCollection = 'docs' | 'i18n';
|
|
6
2
|
|
|
7
3
|
/**
|
|
8
4
|
* We still rely on the content collection folder structure to be fixed for now:
|
|
@@ -22,12 +18,8 @@ export type StarlightCollection = (typeof collectionNames)[number];
|
|
|
22
18
|
* these helper functions should be updated to reflect that in one place.
|
|
23
19
|
*/
|
|
24
20
|
|
|
25
|
-
export function
|
|
26
|
-
return new URL(`content/${collection}/`, srcDir)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function resolveCollectionPath(collection: StarlightCollection, srcDir: URL) {
|
|
30
|
-
return resolve(fileURLToPath(srcDir), `content/${collection}`);
|
|
21
|
+
export function getCollectionUrl(collection: StarlightCollection, srcDir: URL) {
|
|
22
|
+
return new URL(`content/${collection}/`, srcDir);
|
|
31
23
|
}
|
|
32
24
|
|
|
33
25
|
export function getCollectionPathFromRoot(
|
|
@@ -12,7 +12,7 @@ import type { UserI18nKeys, UserI18nSchema } from './translations';
|
|
|
12
12
|
*/
|
|
13
13
|
export const I18nextNamespace = 'starlight' as const;
|
|
14
14
|
|
|
15
|
-
export function createTranslationSystem<T extends i18nSchemaOutput>(
|
|
15
|
+
export async function createTranslationSystem<T extends i18nSchemaOutput>(
|
|
16
16
|
config: Pick<StarlightConfig, 'defaultLocale' | 'locales'>,
|
|
17
17
|
userTranslations: Record<string, T>,
|
|
18
18
|
pluginTranslations: Record<string, T> = {}
|
|
@@ -42,7 +42,7 @@ export function createTranslationSystem<T extends i18nSchemaOutput>(
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const i18n = i18next.createInstance();
|
|
45
|
-
i18n.init({
|
|
45
|
+
await i18n.init({
|
|
46
46
|
resources: translations,
|
|
47
47
|
fallbackLng:
|
|
48
48
|
config.defaultLocale.lang || config.defaultLocale?.locale || BuiltInDefaultLocale.lang,
|
|
@@ -72,7 +72,7 @@ export function createTranslationSystem<T extends i18nSchemaOutput>(
|
|
|
72
72
|
lang ??= config.defaultLocale?.lang || BuiltInDefaultLocale.lang;
|
|
73
73
|
|
|
74
74
|
const t = i18n.getFixedT(lang, I18nextNamespace) as I18nT;
|
|
75
|
-
t.all = () => i18n.getResourceBundle(lang, I18nextNamespace)
|
|
75
|
+
t.all = () => i18n.getResourceBundle(lang, I18nextNamespace) as ReturnType<I18nT['all']>;
|
|
76
76
|
t.exists = (key, options) => i18n.exists(key, { lng: lang, ns: I18nextNamespace, ...options });
|
|
77
77
|
t.dir = (dirLang = lang) => i18n.dir(dirLang);
|
|
78
78
|
|
package/utils/error-map.ts
CHANGED
|
@@ -25,7 +25,7 @@ export function parseWithFriendlyErrors<T extends z.Schema>(
|
|
|
25
25
|
input: z.input<T>,
|
|
26
26
|
message: string
|
|
27
27
|
): z.output<T> {
|
|
28
|
-
return processParsedData(schema.safeParse(input, { errorMap }), message);
|
|
28
|
+
return processParsedData<T>(schema.safeParse(input, { errorMap }), message);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -42,10 +42,13 @@ export async function parseAsyncWithFriendlyErrors<T extends z.Schema>(
|
|
|
42
42
|
input: z.input<T>,
|
|
43
43
|
message: string
|
|
44
44
|
): Promise<z.output<T>> {
|
|
45
|
-
return processParsedData(await schema.safeParseAsync(input, { errorMap }), message);
|
|
45
|
+
return processParsedData<T>(await schema.safeParseAsync(input, { errorMap }), message);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function processParsedData
|
|
48
|
+
function processParsedData<T extends z.Schema>(
|
|
49
|
+
parsedData: z.SafeParseReturnType<T, T>,
|
|
50
|
+
message: string
|
|
51
|
+
) {
|
|
49
52
|
if (!parsedData.success) {
|
|
50
53
|
throw new AstroError(message, parsedData.error.issues.map((i) => i.message).join('\n'));
|
|
51
54
|
}
|
|
@@ -60,7 +63,7 @@ const errorMap: z.ZodErrorMap = (baseError, ctx) => {
|
|
|
60
63
|
// raise a single error when `key` does not match:
|
|
61
64
|
// > Did not match union.
|
|
62
65
|
// > key: Expected `'tutorial' | 'blog'`, received 'foo'
|
|
63
|
-
|
|
66
|
+
const typeOrLiteralErrByPath: Map<string, TypeOrLiteralErrByPathEntry> = new Map();
|
|
64
67
|
for (const unionError of baseError.unionErrors.map((e) => e.errors).flat()) {
|
|
65
68
|
if (unionError.code === 'invalid_type' || unionError.code === 'invalid_literal') {
|
|
66
69
|
const flattenedErrorPath = flattenErrorPath(unionError.path);
|
|
@@ -69,7 +72,7 @@ const errorMap: z.ZodErrorMap = (baseError, ctx) => {
|
|
|
69
72
|
} else {
|
|
70
73
|
typeOrLiteralErrByPath.set(flattenedErrorPath, {
|
|
71
74
|
code: unionError.code,
|
|
72
|
-
received:
|
|
75
|
+
received: unionError.received,
|
|
73
76
|
expected: [unionError.expected],
|
|
74
77
|
});
|
|
75
78
|
}
|
|
@@ -130,7 +133,7 @@ const errorMap: z.ZodErrorMap = (baseError, ctx) => {
|
|
|
130
133
|
baseErrorPath,
|
|
131
134
|
getTypeOrLiteralMsg({
|
|
132
135
|
code: baseError.code,
|
|
133
|
-
received:
|
|
136
|
+
received: baseError.received,
|
|
134
137
|
expected: [baseError.expected],
|
|
135
138
|
})
|
|
136
139
|
),
|
package/utils/i18n.ts
CHANGED
|
@@ -169,7 +169,7 @@ function getLocaleInfo(lang: string) {
|
|
|
169
169
|
label: label[0]?.toLocaleUpperCase(locale) + label.slice(1),
|
|
170
170
|
dir: getLocaleDir(locale),
|
|
171
171
|
};
|
|
172
|
-
} catch
|
|
172
|
+
} catch {
|
|
173
173
|
throw new AstroError(
|
|
174
174
|
`Failed to get locale informations for the '${lang}' locale.`,
|
|
175
175
|
'Make sure to provide a valid BCP-47 tags (e.g. en, ar, or zh-CN).'
|
|
@@ -184,9 +184,11 @@ function getLocaleInfo(lang: string) {
|
|
|
184
184
|
function getLocaleDir(locale: Intl.Locale): 'ltr' | 'rtl' {
|
|
185
185
|
if ('textInfo' in locale) {
|
|
186
186
|
// @ts-expect-error - `textInfo` is not typed but is available in v8 based environments.
|
|
187
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
187
188
|
return locale.textInfo.direction;
|
|
188
189
|
} else if ('getTextInfo' in locale) {
|
|
189
190
|
// @ts-expect-error - `getTextInfo` is not typed but is available in some non-v8 based environments.
|
|
191
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
|
|
190
192
|
return locale.getTextInfo().direction;
|
|
191
193
|
}
|
|
192
194
|
// Firefox does not support `textInfo` or `getTextInfo` yet so we fallback to a well-known list
|
package/utils/navigation.ts
CHANGED
|
@@ -254,7 +254,7 @@ function treeify(routes: Route[], locale: string | undefined, baseDir: string):
|
|
|
254
254
|
const isLeaf = index === parts.length - 1;
|
|
255
255
|
|
|
256
256
|
// Handle directory index pages by renaming them to `index`
|
|
257
|
-
if (isLeaf &&
|
|
257
|
+
if (isLeaf && Object.hasOwn(currentNode, part)) {
|
|
258
258
|
currentNode = currentNode[part] as Dir;
|
|
259
259
|
part = 'index';
|
|
260
260
|
}
|
package/utils/plugins.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import type { UserI18nSchema } from './translations';
|
|
11
11
|
import { createTranslationSystemFromFs } from './translations-fs';
|
|
12
12
|
import { absolutePathToLang as getAbsolutePathFromLang } from '../integrations/shared/absolutePathToLang';
|
|
13
|
+
import { getCollectionPosixPath } from './collection-fs';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Runs Starlight plugins in the order that they are configured after validating the user-provided
|
|
@@ -51,21 +52,24 @@ export async function runPlugins(
|
|
|
51
52
|
// Merge the translations injected by the plugin.
|
|
52
53
|
for (const [locale, localeTranslations] of Object.entries(translations)) {
|
|
53
54
|
pluginTranslations[locale] ??= {};
|
|
54
|
-
Object.assign(pluginTranslations[locale]
|
|
55
|
+
Object.assign(pluginTranslations[locale], localeTranslations);
|
|
55
56
|
}
|
|
56
57
|
},
|
|
57
58
|
});
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
const useTranslations = createTranslationSystemFromFs(
|
|
62
|
+
const useTranslations = await createTranslationSystemFromFs(
|
|
62
63
|
starlightConfig,
|
|
63
64
|
context.config,
|
|
64
65
|
pluginTranslations
|
|
65
66
|
);
|
|
66
67
|
|
|
67
68
|
function absolutePathToLang(path: string) {
|
|
68
|
-
return getAbsolutePathFromLang(path, {
|
|
69
|
+
return getAbsolutePathFromLang(path, {
|
|
70
|
+
docsPath: getCollectionPosixPath('docs', context.config.srcDir),
|
|
71
|
+
starlightConfig,
|
|
72
|
+
});
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
// A list of Astro integrations added by the various plugins.
|
|
@@ -315,7 +319,9 @@ const configSetupHookSchema = z
|
|
|
315
319
|
* }
|
|
316
320
|
* }
|
|
317
321
|
*/
|
|
318
|
-
useTranslations: z.any() as z.Schema<
|
|
322
|
+
useTranslations: z.any() as z.Schema<
|
|
323
|
+
Awaited<ReturnType<typeof createTranslationSystemFromFs>>
|
|
324
|
+
>,
|
|
319
325
|
/**
|
|
320
326
|
* A callback function to get the language for a given absolute file path. The returned
|
|
321
327
|
* language can be used with the `useTranslations` helper to get UI strings for that
|
|
@@ -432,7 +438,7 @@ export type StarlightPlugin = z.input<typeof starlightPluginSchema>;
|
|
|
432
438
|
export type HookParameters<
|
|
433
439
|
Hook extends keyof StarlightPlugin['hooks'],
|
|
434
440
|
HookFn = StarlightPlugin['hooks'][Hook],
|
|
435
|
-
> = HookFn extends (...args: any) =>
|
|
441
|
+
> = HookFn extends (...args: any[]) => unknown ? Parameters<HookFn>[0] : never;
|
|
436
442
|
|
|
437
443
|
export type StarlightUserConfigWithPlugins = StarlightUserConfig & {
|
|
438
444
|
/**
|
package/utils/routing/data.ts
CHANGED
|
@@ -32,11 +32,11 @@ export async function getRoute(context: APIContext): Promise<Route> {
|
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export
|
|
35
|
+
export function useRouteData(
|
|
36
36
|
context: APIContext,
|
|
37
37
|
route: Route,
|
|
38
38
|
{ Content, headings }: RenderResult
|
|
39
|
-
):
|
|
39
|
+
): StarlightRouteData {
|
|
40
40
|
const routeData = generateRouteData({ props: { ...route, headings }, context });
|
|
41
41
|
return { ...routeData, Content };
|
|
42
42
|
}
|
|
@@ -118,7 +118,7 @@ function getEditUrl({ entry }: PageProps): URL | undefined {
|
|
|
118
118
|
export function getSiteTitle(lang: string): string {
|
|
119
119
|
const defaultLang = config.defaultLocale.lang as string;
|
|
120
120
|
if (lang && config.title[lang]) {
|
|
121
|
-
return config.title[lang]
|
|
121
|
+
return config.title[lang];
|
|
122
122
|
}
|
|
123
123
|
return config.title[defaultLang] as string;
|
|
124
124
|
}
|
package/utils/slugs.ts
CHANGED
|
@@ -39,12 +39,18 @@ function localeToDir(locale: string | undefined): 'ltr' | 'rtl' {
|
|
|
39
39
|
return dir || config.defaultLocale.dir;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Convert a content collection slug to a param as expected by Astro’s router.
|
|
44
|
+
* This utility handles stripping `index` from file names and matches
|
|
45
|
+
* [Astro’s param sanitization logic](https://github.com/withastro/astro/blob/687d25365a41ff8a9e6da155d3527f841abb70dd/packages/astro/src/core/routing/manifest/generator.ts#L4-L18)
|
|
46
|
+
* by normalizing strings to their canonical representations.
|
|
47
|
+
* @param slug Content collection slug
|
|
48
|
+
* @returns Param compatible with Astro’s router
|
|
49
|
+
*/
|
|
42
50
|
export function slugToParam(slug: string): string | undefined {
|
|
43
51
|
return slug === 'index' || slug === '' || slug === '/'
|
|
44
52
|
? undefined
|
|
45
|
-
: slug.endsWith('/index')
|
|
46
|
-
? slug.slice(0, -6)
|
|
47
|
-
: slug;
|
|
53
|
+
: (slug.endsWith('/index') ? slug.slice(0, -6) : slug).normalize();
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
export function slugToPathname(slug: string): string {
|
package/utils/starlight-page.ts
CHANGED
|
@@ -187,12 +187,12 @@ async function getStarlightPageFrontmatter(frontmatter: StarlightPageFrontmatter
|
|
|
187
187
|
// well as containing the metadata properties and this ensures we handle those correctly.
|
|
188
188
|
z.custom(
|
|
189
189
|
(value) =>
|
|
190
|
-
value &&
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
190
|
+
(value &&
|
|
191
|
+
(typeof value === 'function' || typeof value === 'object') &&
|
|
192
|
+
'src' in value &&
|
|
193
|
+
'width' in value &&
|
|
194
|
+
'height' in value &&
|
|
195
|
+
'format' in value) as ReturnType<ImageFunction>,
|
|
196
196
|
'Invalid image passed to `<StarlightPage>` component. Expected imported `ImageMetadata` object.'
|
|
197
197
|
)) as ImageFunction,
|
|
198
198
|
});
|
package/utils/translations-fs.ts
CHANGED
|
@@ -16,13 +16,13 @@ const contentCollectionFileExtensions = ['.json', '.yaml', '.yml'];
|
|
|
16
16
|
*
|
|
17
17
|
* @see [`./translations.ts`](./translations.ts)
|
|
18
18
|
*/
|
|
19
|
-
export function createTranslationSystemFromFs<T extends i18nSchemaOutput>(
|
|
19
|
+
export async function createTranslationSystemFromFs<T extends i18nSchemaOutput>(
|
|
20
20
|
opts: Pick<StarlightConfig, 'defaultLocale' | 'locales'>,
|
|
21
21
|
{ srcDir }: Pick<AstroConfig, 'srcDir'>,
|
|
22
22
|
pluginTranslations: Record<string, T> = {}
|
|
23
23
|
) {
|
|
24
24
|
/** All translation data from the i18n collection, keyed by `id`, which matches locale. */
|
|
25
|
-
|
|
25
|
+
const userTranslations: Record<string, i18nSchemaOutput> = {};
|
|
26
26
|
try {
|
|
27
27
|
const i18nDir = new URL('content/i18n/', srcDir);
|
|
28
28
|
// Load the user’s i18n directory
|
|
@@ -34,11 +34,12 @@ export function createTranslationSystemFromFs<T extends i18nSchemaOutput>(
|
|
|
34
34
|
const id = filePath.name;
|
|
35
35
|
const url = new URL(filePath.base, i18nDir);
|
|
36
36
|
const content = fs.readFileSync(new URL(file, i18nDir), 'utf-8');
|
|
37
|
-
const data =
|
|
37
|
+
const data = (
|
|
38
38
|
filePath.ext === '.json'
|
|
39
39
|
? JSON.parse(content)
|
|
40
|
-
: yaml.load(content, { filename: fileURLToPath(url) })
|
|
41
|
-
|
|
40
|
+
: yaml.load(content, { filename: fileURLToPath(url) })
|
|
41
|
+
) as i18nSchemaOutput;
|
|
42
|
+
userTranslations[id] = data;
|
|
42
43
|
}
|
|
43
44
|
} catch (e: unknown) {
|
|
44
45
|
if (e instanceof Error && 'code' in e && e.code === 'ENOENT') {
|
package/utils/translations.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { RemoveIndexSignature } from './types';
|
|
|
8
8
|
import { getCollectionPathFromRoot } from './collection';
|
|
9
9
|
import { stripExtension, stripLeadingSlash } from './path';
|
|
10
10
|
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
11
12
|
// @ts-ignore - This may be a type error in projects without an i18n collection and running
|
|
12
13
|
// `tsc --noEmit` in their project. Note that it is not possible to inline this type in
|
|
13
14
|
// `UserI18nSchema` because this would break types for users having multiple data collections.
|
|
@@ -28,6 +29,7 @@ async function loadTranslations() {
|
|
|
28
29
|
const warn = console.warn;
|
|
29
30
|
console.warn = () => {};
|
|
30
31
|
const userTranslations: Record<string, UserI18nSchema> = Object.fromEntries(
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
31
33
|
// @ts-ignore — may be a type error in projects without an i18n collection
|
|
32
34
|
(await getCollection('i18n')).map(({ id, data, filePath }) => {
|
|
33
35
|
const lang =
|
|
@@ -49,7 +51,7 @@ async function loadTranslations() {
|
|
|
49
51
|
* const t = useTranslations('en');
|
|
50
52
|
* const label = t('search.label'); // => 'Search'
|
|
51
53
|
*/
|
|
52
|
-
export const useTranslations = createTranslationSystem(
|
|
54
|
+
export const useTranslations = await createTranslationSystem(
|
|
53
55
|
config,
|
|
54
56
|
await loadTranslations(),
|
|
55
57
|
pluginTranslations
|