@astrojs/starlight 0.5.6 → 0.6.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/404.astro +17 -17
- package/CHANGELOG.md +40 -2
- package/components/CallToAction.astro +32 -35
- package/components/ContentPanel.astro +18 -18
- package/components/EditLink.astro +23 -23
- package/components/FallbackContentNotice.astro +16 -18
- package/components/Footer.astro +28 -34
- package/components/HeadSEO.astro +69 -75
- package/components/Header.astro +47 -51
- package/components/Hero.astro +108 -121
- package/components/Icons.ts +73 -71
- package/components/LanguageSelect.astro +31 -31
- package/components/LastUpdated.astro +16 -18
- package/components/MarkdownContent.astro +105 -117
- package/components/MobileMenuToggle.astro +80 -80
- package/components/PrevNextLinks.astro +60 -60
- package/components/RightSidebar.astro +17 -17
- package/components/RightSidebarPanel.astro +41 -41
- package/components/Search.astro +293 -306
- package/components/Select.astro +64 -65
- package/components/Sidebar.astro +23 -23
- package/components/SidebarSublist.astro +96 -95
- package/components/SiteTitle.astro +65 -63
- package/components/SkipLink.astro +17 -17
- package/components/SocialIcons.astro +36 -34
- package/components/TableOfContents/MobileTableOfContents.astro +124 -124
- package/components/TableOfContents/TableOfContentsList.astro +64 -69
- package/components/TableOfContents/generateToC.ts +41 -43
- package/components/TableOfContents/starlight-toc.ts +84 -90
- package/components/TableOfContents.astro +8 -8
- package/components/ThemeProvider.astro +28 -32
- package/components/ThemeSelect.astro +66 -71
- package/global.d.ts +3 -3
- package/index.astro +1 -1
- package/index.ts +52 -59
- package/integrations/asides.ts +109 -111
- package/integrations/sitemap.ts +10 -13
- package/integrations/virtual-user-config.ts +37 -37
- package/layout/Page.astro +84 -72
- package/layout/PageFrame.astro +67 -69
- package/layout/TwoColumnContent.astro +40 -42
- package/package.json +2 -2
- package/schema.ts +126 -113
- package/schemas/favicon.ts +40 -0
- package/schemas/head.ts +12 -23
- package/schemas/i18n.ts +146 -160
- package/schemas/logo.ts +22 -22
- package/schemas/prevNextLink.ts +14 -14
- package/schemas/tableOfContents.ts +14 -18
- package/style/asides.css +27 -27
- package/style/props.css +168 -172
- package/style/reset.css +13 -13
- package/style/shiki.css +11 -11
- package/style/util.css +30 -30
- package/translations/fr.json +21 -21
- package/translations/index.ts +4 -4
- package/translations/it.json +20 -20
- package/translations/tr.json +1 -1
- package/translations/zh.json +0 -1
- package/user-components/Card.astro +50 -54
- package/user-components/CardGrid.astro +21 -21
- package/user-components/Icon.astro +19 -21
- package/user-components/TabItem.astro +3 -3
- package/user-components/Tabs.astro +113 -117
- package/user-components/rehype-tabs.ts +72 -72
- package/utils/base.ts +6 -6
- package/utils/git.ts +55 -55
- package/utils/head.ts +56 -54
- package/utils/i18n.ts +3 -3
- package/utils/localizedUrl.ts +25 -25
- package/utils/navigation.ts +225 -203
- package/utils/routing.ts +74 -85
- package/utils/slugs.ts +41 -51
- package/utils/translations.ts +26 -32
- package/utils/user-config.ts +278 -278
- package/virtual.d.ts +8 -8
package/utils/routing.ts
CHANGED
|
@@ -2,29 +2,29 @@ import type { GetStaticPathsItem } from 'astro';
|
|
|
2
2
|
import { type CollectionEntry, getCollection } from 'astro:content';
|
|
3
3
|
import config from 'virtual:starlight/user-config';
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
type LocaleData,
|
|
6
|
+
localizedId,
|
|
7
|
+
localizedSlug,
|
|
8
|
+
slugToLocaleData,
|
|
9
|
+
slugToParam,
|
|
10
10
|
} from './slugs';
|
|
11
11
|
|
|
12
12
|
export type StarlightDocsEntry = Omit<CollectionEntry<'docs'>, 'slug'> & {
|
|
13
|
-
|
|
13
|
+
slug: string;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export interface Route extends LocaleData {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
entry: StarlightDocsEntry;
|
|
18
|
+
entryMeta: LocaleData;
|
|
19
|
+
slug: string;
|
|
20
|
+
id: string;
|
|
21
|
+
isFallback?: true;
|
|
22
|
+
[key: string]: unknown;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
interface Path extends GetStaticPathsItem {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
params: { slug: string | undefined };
|
|
27
|
+
props: Route;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/**
|
|
@@ -35,64 +35,60 @@ interface Path extends GetStaticPathsItem {
|
|
|
35
35
|
const normalizeIndexSlug = (slug: string) => (slug === 'index' ? '' : slug);
|
|
36
36
|
|
|
37
37
|
/** All entries in the docs content collection. */
|
|
38
|
-
const docs: StarlightDocsEntry[] = (await getCollection('docs')).map(
|
|
39
|
-
|
|
40
|
-
)
|
|
38
|
+
const docs: StarlightDocsEntry[] = (await getCollection('docs')).map(({ slug, ...entry }) => ({
|
|
39
|
+
...entry,
|
|
40
|
+
slug: normalizeIndexSlug(slug),
|
|
41
|
+
}));
|
|
41
42
|
|
|
42
43
|
function getRoutes(): Route[] {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
const routes: Route[] = docs.map((entry) => ({
|
|
45
|
+
entry,
|
|
46
|
+
slug: entry.slug,
|
|
47
|
+
id: entry.id,
|
|
48
|
+
entryMeta: slugToLocaleData(entry.slug),
|
|
49
|
+
...slugToLocaleData(entry.slug),
|
|
50
|
+
}));
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
}
|
|
52
|
+
// In multilingual sites, add required fallback routes.
|
|
53
|
+
if (config.isMultilingual) {
|
|
54
|
+
/** Entries in the docs content collection for the default locale. */
|
|
55
|
+
const defaultLocaleDocs = getLocaleDocs(
|
|
56
|
+
config.defaultLocale?.locale === 'root' ? undefined : config.defaultLocale?.locale
|
|
57
|
+
);
|
|
58
|
+
for (const key in config.locales) {
|
|
59
|
+
if (key === config.defaultLocale.locale) continue;
|
|
60
|
+
const localeConfig = config.locales[key];
|
|
61
|
+
if (!localeConfig) continue;
|
|
62
|
+
const locale = key === 'root' ? undefined : key;
|
|
63
|
+
const localeDocs = getLocaleDocs(locale);
|
|
64
|
+
for (const fallback of defaultLocaleDocs) {
|
|
65
|
+
const slug = localizedSlug(fallback.slug, locale);
|
|
66
|
+
const id = localizedId(fallback.id, locale);
|
|
67
|
+
const doesNotNeedFallback = localeDocs.some((doc) => doc.slug === slug);
|
|
68
|
+
if (doesNotNeedFallback) continue;
|
|
69
|
+
routes.push({
|
|
70
|
+
entry: fallback,
|
|
71
|
+
slug,
|
|
72
|
+
id,
|
|
73
|
+
isFallback: true,
|
|
74
|
+
lang: localeConfig.lang || 'en',
|
|
75
|
+
locale,
|
|
76
|
+
dir: localeConfig.dir,
|
|
77
|
+
entryMeta: slugToLocaleData(fallback.slug),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
83
82
|
|
|
84
|
-
|
|
85
|
-
return routes.sort((a, b) =>
|
|
86
|
-
a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0
|
|
87
|
-
);
|
|
83
|
+
return routes;
|
|
88
84
|
}
|
|
89
85
|
export const routes = getRoutes();
|
|
90
86
|
|
|
91
87
|
function getPaths(): Path[] {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
88
|
+
return routes.map((route) => ({
|
|
89
|
+
params: { slug: slugToParam(route.slug) },
|
|
90
|
+
props: route,
|
|
91
|
+
}));
|
|
96
92
|
}
|
|
97
93
|
export const paths = getPaths();
|
|
98
94
|
|
|
@@ -101,7 +97,7 @@ export const paths = getPaths();
|
|
|
101
97
|
* A locale of `undefined` is treated as the “root” locale, if configured.
|
|
102
98
|
*/
|
|
103
99
|
export function getLocaleRoutes(locale: string | undefined): Route[] {
|
|
104
|
-
|
|
100
|
+
return filterByLocale(routes, locale);
|
|
105
101
|
}
|
|
106
102
|
|
|
107
103
|
/**
|
|
@@ -109,27 +105,20 @@ export function getLocaleRoutes(locale: string | undefined): Route[] {
|
|
|
109
105
|
* A locale of `undefined` is treated as the “root” locale, if configured.
|
|
110
106
|
*/
|
|
111
107
|
function getLocaleDocs(locale: string | undefined): StarlightDocsEntry[] {
|
|
112
|
-
|
|
108
|
+
return filterByLocale(docs, locale);
|
|
113
109
|
}
|
|
114
110
|
|
|
115
111
|
/** Filter an array to find items whose slug matches the passed locale. */
|
|
116
|
-
function filterByLocale<T extends { slug: string }>(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const isLangDir = new RegExp(`^(${langKeys.join('|')})/`);
|
|
129
|
-
return items.filter(
|
|
130
|
-
(i) => !isLangIndex.test(i.slug) && !isLangDir.test(i.slug)
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return items;
|
|
112
|
+
function filterByLocale<T extends { slug: string }>(items: T[], locale: string | undefined): T[] {
|
|
113
|
+
if (config.locales) {
|
|
114
|
+
if (locale && locale in config.locales) {
|
|
115
|
+
return items.filter((i) => i.slug === locale || i.slug.startsWith(locale + '/'));
|
|
116
|
+
} else if (config.locales.root) {
|
|
117
|
+
const langKeys = Object.keys(config.locales).filter((k) => k !== 'root');
|
|
118
|
+
const isLangIndex = new RegExp(`^(${langKeys.join('|')})$`);
|
|
119
|
+
const isLangDir = new RegExp(`^(${langKeys.join('|')})/`);
|
|
120
|
+
return items.filter((i) => !isLangIndex.test(i.slug) && !isLangDir.test(i.slug));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return items;
|
|
135
124
|
}
|
package/utils/slugs.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import config from 'virtual:starlight/user-config';
|
|
2
2
|
|
|
3
3
|
export interface LocaleData {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
/** Writing direction. */
|
|
5
|
+
dir: 'ltr' | 'rtl';
|
|
6
|
+
/** BCP-47 language tag. */
|
|
7
|
+
lang: string;
|
|
8
|
+
/** The base path at which a language is served. `undefined` for root locale slugs. */
|
|
9
|
+
locale: string | undefined;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -16,16 +16,16 @@ export interface LocaleData {
|
|
|
16
16
|
* @param slug A collection entry slug
|
|
17
17
|
*/
|
|
18
18
|
function slugToLocale(slug: string): string | undefined {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const locales = Object.keys(config.locales || {});
|
|
20
|
+
const baseSegment = slug.split('/')[0];
|
|
21
|
+
if (baseSegment && locales.includes(baseSegment)) return baseSegment;
|
|
22
|
+
return undefined;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/** Get locale information for a given slug. */
|
|
26
26
|
export function slugToLocaleData(slug: string): LocaleData {
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const locale = slugToLocale(slug);
|
|
28
|
+
return { dir: localeToDir(locale), lang: localeToLang(locale), locale };
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -33,12 +33,9 @@ export function slugToLocaleData(slug: string): LocaleData {
|
|
|
33
33
|
* @param locale Locale string or `undefined` for the root locale.
|
|
34
34
|
*/
|
|
35
35
|
export function localeToLang(locale: string | undefined): string {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const defaultLang =
|
|
40
|
-
config.defaultLocale?.lang || config.defaultLocale?.locale;
|
|
41
|
-
return lang || defaultLang || 'en';
|
|
36
|
+
const lang = locale ? config.locales?.[locale]?.lang : config.locales?.root?.lang;
|
|
37
|
+
const defaultLang = config.defaultLocale?.lang || config.defaultLocale?.locale;
|
|
38
|
+
return lang || defaultLang || 'en';
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
/**
|
|
@@ -46,23 +43,21 @@ export function localeToLang(locale: string | undefined): string {
|
|
|
46
43
|
* @param locale Locale string or `undefined` for the root locale.
|
|
47
44
|
*/
|
|
48
45
|
function localeToDir(locale: string | undefined): 'ltr' | 'rtl' {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
: config.locales?.root?.dir;
|
|
52
|
-
return dir || config.defaultLocale.dir;
|
|
46
|
+
const dir = locale ? config.locales?.[locale]?.dir : config.locales?.root?.dir;
|
|
47
|
+
return dir || config.defaultLocale.dir;
|
|
53
48
|
}
|
|
54
49
|
|
|
55
50
|
export function slugToParam(slug: string): string | undefined {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
return slug === 'index' || slug === ''
|
|
52
|
+
? undefined
|
|
53
|
+
: slug.endsWith('/index')
|
|
54
|
+
? slug.replace(/\/index$/, '')
|
|
55
|
+
: slug;
|
|
61
56
|
}
|
|
62
57
|
|
|
63
58
|
export function slugToPathname(slug: string): string {
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
const param = slugToParam(slug);
|
|
60
|
+
return param ? '/' + param + '/' : '/';
|
|
66
61
|
}
|
|
67
62
|
|
|
68
63
|
/**
|
|
@@ -75,20 +70,15 @@ export function slugToPathname(slug: string): string {
|
|
|
75
70
|
* localizedSlug('en/home', 'fr') // => 'fr/home'
|
|
76
71
|
* localizedSlug('en/home', undefined) // => 'home'
|
|
77
72
|
*/
|
|
78
|
-
export function localizedSlug(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return slug
|
|
88
|
-
.replace(slugLocale + '/', locale ? locale + '/' : '')
|
|
89
|
-
.replace(/\/$/, '');
|
|
90
|
-
}
|
|
91
|
-
return slug ? locale + '/' + slug : locale;
|
|
73
|
+
export function localizedSlug(slug: string, locale: string | undefined): string {
|
|
74
|
+
const slugLocale = slugToLocale(slug);
|
|
75
|
+
if (slugLocale === locale) return slug;
|
|
76
|
+
locale = locale || '';
|
|
77
|
+
if (slugLocale === slug) return locale;
|
|
78
|
+
if (slugLocale) {
|
|
79
|
+
return slug.replace(slugLocale + '/', locale ? locale + '/' : '').replace(/\/$/, '');
|
|
80
|
+
}
|
|
81
|
+
return slug ? locale + '/' + slug : locale;
|
|
92
82
|
}
|
|
93
83
|
|
|
94
84
|
/**
|
|
@@ -102,12 +92,12 @@ export function localizedSlug(
|
|
|
102
92
|
* localizedSlug('en/home.md', undefined) // => 'home.md'
|
|
103
93
|
*/
|
|
104
94
|
export function localizedId(id: string, locale: string | undefined): string {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
95
|
+
const idLocale = slugToLocale(id);
|
|
96
|
+
if (idLocale) {
|
|
97
|
+
return id.replace(idLocale + '/', locale ? locale + '/' : '');
|
|
98
|
+
} else if (locale) {
|
|
99
|
+
return locale + '/' + id;
|
|
100
|
+
} else {
|
|
101
|
+
return id;
|
|
102
|
+
}
|
|
113
103
|
}
|
package/utils/translations.ts
CHANGED
|
@@ -9,18 +9,18 @@ const defaultLocale = config.defaultLocale?.locale || 'root';
|
|
|
9
9
|
/** All translation data from the i18n collection, keyed by `id`, which matches locale. */
|
|
10
10
|
let userTranslations: Record<string, CollectionEntry<'i18n'>['data']> = {};
|
|
11
11
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
// Load the user’s i18n collection and ignore the error if it doesn’t exist.
|
|
13
|
+
userTranslations = Object.fromEntries(
|
|
14
|
+
(await getCollection('i18n')).map(({ id, data }) => [id, data] as const)
|
|
15
|
+
);
|
|
16
16
|
} catch {}
|
|
17
17
|
|
|
18
18
|
/** Default map of UI strings based on Starlight and user-configured defaults. */
|
|
19
19
|
const defaults = buildDictionary(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
builtinTranslations.en!,
|
|
21
|
+
userTranslations.en,
|
|
22
|
+
builtinTranslations[defaultLocale],
|
|
23
|
+
userTranslations[defaultLocale]
|
|
24
24
|
);
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -31,33 +31,27 @@ const defaults = buildDictionary(
|
|
|
31
31
|
* const label = t('search.label'); // => 'Search'
|
|
32
32
|
*/
|
|
33
33
|
export function useTranslations(locale: string | undefined) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const t = <K extends keyof typeof dictionary>(key: K) => dictionary[key];
|
|
42
|
-
t.pick = (startOfKey: string) =>
|
|
43
|
-
Object.fromEntries(
|
|
44
|
-
Object.entries(dictionary).filter(([k]) => k.startsWith(startOfKey))
|
|
45
|
-
);
|
|
46
|
-
return t;
|
|
34
|
+
// TODO: Use better mapping, e.g. so that `en-GB` matches `en`.
|
|
35
|
+
const lang = localeToLang(locale);
|
|
36
|
+
const dictionary = buildDictionary(defaults, builtinTranslations[lang], userTranslations[lang]);
|
|
37
|
+
const t = <K extends keyof typeof dictionary>(key: K) => dictionary[key];
|
|
38
|
+
t.pick = (startOfKey: string) =>
|
|
39
|
+
Object.fromEntries(Object.entries(dictionary).filter(([k]) => k.startsWith(startOfKey)));
|
|
40
|
+
return t;
|
|
47
41
|
}
|
|
48
42
|
|
|
49
43
|
/** Build a dictionary by layering preferred translation sources. */
|
|
50
44
|
function buildDictionary(
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
base: (typeof builtinTranslations)[string],
|
|
46
|
+
...dictionaries: (CollectionEntry<'i18n'>['data'] | undefined)[]
|
|
53
47
|
) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
48
|
+
const dictionary = { ...base };
|
|
49
|
+
// Iterate over alternate dictionaries to avoid overwriting preceding values with `undefined`.
|
|
50
|
+
for (const dict of dictionaries) {
|
|
51
|
+
for (const key in dict) {
|
|
52
|
+
const value = dict[key as keyof typeof dict];
|
|
53
|
+
if (value) dictionary[key as keyof typeof dict] = value;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return dictionary;
|
|
63
57
|
}
|