@astrojs/starlight 0.5.6 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/404.astro +17 -17
  2. package/CHANGELOG.md +50 -2
  3. package/components/CallToAction.astro +32 -35
  4. package/components/ContentPanel.astro +18 -18
  5. package/components/EditLink.astro +23 -23
  6. package/components/FallbackContentNotice.astro +16 -18
  7. package/components/Footer.astro +28 -34
  8. package/components/HeadSEO.astro +69 -75
  9. package/components/Header.astro +47 -51
  10. package/components/Hero.astro +108 -121
  11. package/components/Icons.ts +81 -71
  12. package/components/LanguageSelect.astro +31 -31
  13. package/components/LastUpdated.astro +16 -18
  14. package/components/MarkdownContent.astro +105 -117
  15. package/components/MobileMenuToggle.astro +80 -80
  16. package/components/PrevNextLinks.astro +60 -60
  17. package/components/RightSidebar.astro +17 -17
  18. package/components/RightSidebarPanel.astro +41 -41
  19. package/components/Search.astro +293 -306
  20. package/components/Select.astro +64 -65
  21. package/components/Sidebar.astro +23 -23
  22. package/components/SidebarSublist.astro +96 -95
  23. package/components/SiteTitle.astro +65 -63
  24. package/components/SkipLink.astro +17 -17
  25. package/components/SocialIcons.astro +40 -34
  26. package/components/TableOfContents/MobileTableOfContents.astro +124 -124
  27. package/components/TableOfContents/TableOfContentsList.astro +67 -69
  28. package/components/TableOfContents/generateToC.ts +41 -43
  29. package/components/TableOfContents/starlight-toc.ts +84 -90
  30. package/components/TableOfContents.astro +8 -8
  31. package/components/ThemeProvider.astro +28 -32
  32. package/components/ThemeSelect.astro +66 -71
  33. package/global.d.ts +3 -3
  34. package/index.astro +1 -1
  35. package/index.ts +52 -59
  36. package/integrations/asides.ts +109 -111
  37. package/integrations/sitemap.ts +10 -13
  38. package/integrations/virtual-user-config.ts +37 -37
  39. package/layout/Page.astro +84 -72
  40. package/layout/PageFrame.astro +67 -69
  41. package/layout/TwoColumnContent.astro +40 -42
  42. package/package.json +2 -2
  43. package/schema.ts +126 -113
  44. package/schemas/favicon.ts +40 -0
  45. package/schemas/head.ts +12 -23
  46. package/schemas/i18n.ts +146 -160
  47. package/schemas/logo.ts +22 -22
  48. package/schemas/prevNextLink.ts +14 -14
  49. package/schemas/tableOfContents.ts +14 -18
  50. package/style/asides.css +27 -27
  51. package/style/props.css +168 -172
  52. package/style/reset.css +13 -13
  53. package/style/shiki.css +11 -11
  54. package/style/util.css +30 -30
  55. package/translations/fr.json +21 -21
  56. package/translations/index.ts +4 -4
  57. package/translations/it.json +20 -20
  58. package/translations/tr.json +1 -1
  59. package/translations/zh.json +0 -1
  60. package/user-components/Card.astro +50 -54
  61. package/user-components/CardGrid.astro +21 -21
  62. package/user-components/Icon.astro +19 -21
  63. package/user-components/TabItem.astro +3 -3
  64. package/user-components/Tabs.astro +113 -117
  65. package/user-components/rehype-tabs.ts +72 -72
  66. package/utils/base.ts +6 -6
  67. package/utils/git.ts +55 -55
  68. package/utils/head.ts +56 -54
  69. package/utils/i18n.ts +3 -3
  70. package/utils/localizedUrl.ts +25 -25
  71. package/utils/navigation.ts +225 -203
  72. package/utils/routing.ts +74 -85
  73. package/utils/slugs.ts +41 -51
  74. package/utils/translations.ts +26 -32
  75. package/utils/user-config.ts +282 -278
  76. 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
- type LocaleData,
6
- localizedId,
7
- localizedSlug,
8
- slugToLocaleData,
9
- slugToParam,
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
- slug: string;
13
+ slug: string;
14
14
  };
15
15
 
16
16
  export interface Route extends LocaleData {
17
- entry: StarlightDocsEntry;
18
- entryMeta: LocaleData;
19
- slug: string;
20
- id: string;
21
- isFallback?: true;
22
- [key: string]: unknown;
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
- params: { slug: string | undefined };
27
- props: Route;
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
- ({ slug, ...entry }) => ({ ...entry, slug: normalizeIndexSlug(slug) })
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
- const routes: Route[] = docs.map((entry) => ({
44
- entry,
45
- slug: entry.slug,
46
- id: entry.id,
47
- entryMeta: slugToLocaleData(entry.slug),
48
- ...slugToLocaleData(entry.slug),
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
- // In multilingual sites, add required fallback routes.
52
- if (config.isMultilingual) {
53
- /** Entries in the docs content collection for the default locale. */
54
- const defaultLocaleDocs = getLocaleDocs(
55
- config.defaultLocale?.locale === 'root'
56
- ? undefined
57
- : config.defaultLocale?.locale
58
- );
59
- for (const key in config.locales) {
60
- if (key === config.defaultLocale.locale) continue;
61
- const localeConfig = config.locales[key];
62
- if (!localeConfig) continue;
63
- const locale = key === 'root' ? undefined : key;
64
- const localeDocs = getLocaleDocs(locale);
65
- for (const fallback of defaultLocaleDocs) {
66
- const slug = localizedSlug(fallback.slug, locale);
67
- const id = localizedId(fallback.id, locale);
68
- const doesNotNeedFallback = localeDocs.some((doc) => doc.slug === slug);
69
- if (doesNotNeedFallback) continue;
70
- routes.push({
71
- entry: fallback,
72
- slug,
73
- id,
74
- isFallback: true,
75
- lang: localeConfig.lang || 'en',
76
- locale,
77
- dir: localeConfig.dir,
78
- entryMeta: slugToLocaleData(fallback.slug),
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
- // Sort alphabetically by page slug to guarantee order regardless of platform.
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
- return routes.map((route) => ({
93
- params: { slug: slugToParam(route.slug) },
94
- props: route,
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
- return filterByLocale(routes, locale);
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
- return filterByLocale(docs, locale);
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
- items: T[],
118
- locale: string | undefined
119
- ): T[] {
120
- if (config.locales) {
121
- if (locale && locale in config.locales) {
122
- return items.filter(
123
- (i) => i.slug === locale || i.slug.startsWith(locale + '/')
124
- );
125
- } else if (config.locales.root) {
126
- const langKeys = Object.keys(config.locales).filter((k) => k !== 'root');
127
- const isLangIndex = new RegExp(`^(${langKeys.join('|')})$`);
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
- /** 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;
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
- const locales = Object.keys(config.locales || {});
20
- const baseSegment = slug.split('/')[0];
21
- if (baseSegment && locales.includes(baseSegment)) return baseSegment;
22
- return undefined;
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
- const locale = slugToLocale(slug);
28
- return { dir: localeToDir(locale), lang: localeToLang(locale), locale };
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
- const lang = locale
37
- ? config.locales?.[locale]?.lang
38
- : config.locales?.root?.lang;
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
- const dir = locale
50
- ? config.locales?.[locale]?.dir
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
- return slug === 'index' || slug === ''
57
- ? undefined
58
- : slug.endsWith('/index')
59
- ? slug.replace(/\/index$/, '')
60
- : slug;
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
- const param = slugToParam(slug);
65
- return param ? '/' + param + '/' : '/';
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
- slug: string,
80
- locale: string | undefined
81
- ): string {
82
- const slugLocale = slugToLocale(slug);
83
- if (slugLocale === locale) return slug;
84
- locale = locale || '';
85
- if (slugLocale === slug) return locale;
86
- if (slugLocale) {
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
- const idLocale = slugToLocale(id);
106
- if (idLocale) {
107
- return id.replace(idLocale + '/', locale ? locale + '/' : '');
108
- } else if (locale) {
109
- return locale + '/' + id;
110
- } else {
111
- return id;
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
  }
@@ -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
- // 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
- );
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
- builtinTranslations.en!,
21
- userTranslations.en,
22
- builtinTranslations[defaultLocale],
23
- userTranslations[defaultLocale]
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
- // TODO: Use better mapping, e.g. so that `en-GB` matches `en`.
35
- const lang = localeToLang(locale);
36
- const dictionary = buildDictionary(
37
- defaults,
38
- builtinTranslations[lang],
39
- userTranslations[lang]
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
- base: (typeof builtinTranslations)[string],
52
- ...dictionaries: (CollectionEntry<'i18n'>['data'] | undefined)[]
45
+ base: (typeof builtinTranslations)[string],
46
+ ...dictionaries: (CollectionEntry<'i18n'>['data'] | undefined)[]
53
47
  ) {
54
- const dictionary = { ...base };
55
- // Iterate over alternate dictionaries to avoid overwriting preceding values with `undefined`.
56
- for (const dict of dictionaries) {
57
- for (const key in dict) {
58
- const value = dict[key as keyof typeof dict];
59
- if (value) dictionary[key as keyof typeof dict] = value;
60
- }
61
- }
62
- return dictionary;
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
  }