@astrojs/starlight 0.12.0 → 0.13.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/components/Icons.ts +2 -0
  3. package/components/MarkdownContent.astro +2 -123
  4. package/components/Search.astro +3 -2
  5. package/components/SiteTitle.astro +2 -2
  6. package/index.ts +17 -1
  7. package/integrations/asides.ts +17 -14
  8. package/integrations/expressive-code/exports.ts +36 -0
  9. package/integrations/expressive-code/index.ts +156 -0
  10. package/integrations/expressive-code/themes/night-owl-dark.jsonc +1796 -0
  11. package/integrations/expressive-code/themes/night-owl-light.jsonc +1695 -0
  12. package/integrations/expressive-code/theming.ts +108 -0
  13. package/integrations/expressive-code/translations.ts +26 -0
  14. package/integrations/shared/pathToLocale.ts +32 -0
  15. package/integrations/sitemap.ts +11 -7
  16. package/integrations/virtual-user-config.ts +14 -2
  17. package/package.json +6 -2
  18. package/schemas/expressiveCode.ts +13 -0
  19. package/schemas/i18n.ts +28 -2
  20. package/schemas/social.ts +2 -0
  21. package/style/markdown.css +115 -0
  22. package/translations/ar.json +5 -1
  23. package/translations/cs.json +5 -1
  24. package/translations/da.json +5 -1
  25. package/translations/de.json +5 -1
  26. package/translations/en.json +5 -1
  27. package/translations/es.json +5 -1
  28. package/translations/fa.json +5 -1
  29. package/translations/fr.json +5 -1
  30. package/translations/gl.json +5 -1
  31. package/translations/he.json +5 -1
  32. package/translations/hi.json +26 -0
  33. package/translations/id.json +5 -1
  34. package/translations/index.ts +4 -0
  35. package/translations/it.json +5 -1
  36. package/translations/ja.json +5 -1
  37. package/translations/ko.json +5 -1
  38. package/translations/nb.json +5 -1
  39. package/translations/nl.json +5 -1
  40. package/translations/pt.json +5 -1
  41. package/translations/ro.json +26 -0
  42. package/translations/ru.json +5 -1
  43. package/translations/sv.json +5 -1
  44. package/translations/tr.json +5 -1
  45. package/translations/uk.json +5 -1
  46. package/translations/vi.json +5 -1
  47. package/translations/zh-CN.json +5 -1
  48. package/utils/base.ts +4 -4
  49. package/utils/createPathFormatter.ts +57 -0
  50. package/utils/createTranslationSystem.ts +1 -1
  51. package/utils/format-path.ts +7 -0
  52. package/utils/navigation.ts +21 -10
  53. package/utils/path.ts +15 -0
  54. package/utils/route-data.ts +2 -2
  55. package/utils/user-config.ts +7 -0
  56. package/virtual.d.ts +9 -1
@@ -18,5 +18,9 @@
18
18
  "page.lastUpdated": "最終更新日:",
19
19
  "page.previousLink": "前へ",
20
20
  "page.nextLink": "次へ",
21
- "404.text": "ページが見つかりません。 URL を確認するか、検索バーを使用してみてください。"
21
+ "404.text": "ページが見つかりません。 URL を確認するか、検索バーを使用してみてください。",
22
+ "aside.note": "ノート",
23
+ "aside.tip": "ヒント",
24
+ "aside.caution": "注意",
25
+ "aside.danger": "危険"
22
26
  }
@@ -18,5 +18,9 @@
18
18
  "page.lastUpdated": "최종 수정:",
19
19
  "page.previousLink": "이전 페이지",
20
20
  "page.nextLink": "다음 페이지",
21
- "404.text": "페이지를 찾을 수 없습니다. URL을 확인하거나 검색창을 사용해보세요."
21
+ "404.text": "페이지를 찾을 수 없습니다. URL을 확인하거나 검색창을 사용해보세요.",
22
+ "aside.note": "노트",
23
+ "aside.tip": "팁",
24
+ "aside.caution": "주의",
25
+ "aside.danger": "위험"
22
26
  }
@@ -18,5 +18,9 @@
18
18
  "page.lastUpdated": "Sist oppdatert:",
19
19
  "page.previousLink": "Forrige",
20
20
  "page.nextLink": "Neste",
21
- "404.text": "Siden ble ikke funnet. Sjekk URL-en eller prøv å bruke søkefeltet."
21
+ "404.text": "Siden ble ikke funnet. Sjekk URL-en eller prøv å bruke søkefeltet.",
22
+ "aside.note": "Note",
23
+ "aside.tip": "Tip",
24
+ "aside.caution": "Caution",
25
+ "aside.danger": "Danger"
22
26
  }
@@ -18,5 +18,9 @@
18
18
  "page.lastUpdated": "Laatst bewerkt:",
19
19
  "page.previousLink": "Vorige",
20
20
  "page.nextLink": "Volgende",
21
- "404.text": "Pagina niet gevonden. Controleer de URL of probeer de zoekbalk."
21
+ "404.text": "Pagina niet gevonden. Controleer de URL of probeer de zoekbalk.",
22
+ "aside.note": "Note",
23
+ "aside.tip": "Tip",
24
+ "aside.caution": "Caution",
25
+ "aside.danger": "Danger"
22
26
  }
@@ -18,5 +18,9 @@
18
18
  "page.lastUpdated": "Última atualização:",
19
19
  "page.previousLink": "Anterior",
20
20
  "page.nextLink": "Próximo",
21
- "404.text": "Página não encontrada. Verifique o URL ou tente usar a barra de pesquisa."
21
+ "404.text": "Página não encontrada. Verifique o URL ou tente usar a barra de pesquisa.",
22
+ "aside.note": "Nota",
23
+ "aside.tip": "Dica",
24
+ "aside.caution": "Cuidado",
25
+ "aside.danger": "Perigo"
22
26
  }
@@ -0,0 +1,26 @@
1
+ {
2
+ "skipLink.label": "Sari la conținut",
3
+ "search.label": "Caută",
4
+ "search.shortcutLabel": "(Apasă pe / ca să cauți)",
5
+ "search.cancelLabel": "Anulează",
6
+ "search.devWarning": "Căutarea este disponibilă numai în versiunea de producție. \nÎncercă să construiești și să previzualizezi site-ul pentru a-l testa local.",
7
+ "themeSelect.accessibleLabel": "Selectează tema",
8
+ "themeSelect.dark": "Întunecată",
9
+ "themeSelect.light": "Deschisă",
10
+ "themeSelect.auto": "Auto",
11
+ "languageSelect.accessibleLabel": "Selectează limba",
12
+ "menuButton.accessibleLabel": "Meniu",
13
+ "sidebarNav.accessibleLabel": "Principal",
14
+ "tableOfContents.onThisPage": "Pe această pagină",
15
+ "tableOfContents.overview": "Sinopsis",
16
+ "i18n.untranslatedContent": "Acest conținut nu este încă disponibil în limba selectată.",
17
+ "page.editLink": "Editează pagina",
18
+ "page.lastUpdated": "Ultima actualizare:",
19
+ "page.previousLink": "Pagina precendentă",
20
+ "page.nextLink": "Pagina următoare",
21
+ "404.text": "Pagina nu a fost găsită. Verifică adresa URL sau încercă să folosești bara de căutare.",
22
+ "aside.note": "Mențiune",
23
+ "aside.tip": "Sfat",
24
+ "aside.caution": "Atenție",
25
+ "aside.danger": "Pericol"
26
+ }
@@ -18,5 +18,9 @@
18
18
  "page.lastUpdated": "Последнее обновление:",
19
19
  "page.previousLink": "Предыдущая",
20
20
  "page.nextLink": "Следующая",
21
- "404.text": "Страница не найдена. Проверьтье URL или используйте поиск по сайту"
21
+ "404.text": "Страница не найдена. Проверьтье URL или используйте поиск по сайту",
22
+ "aside.note": "Заметка",
23
+ "aside.tip": "Знали ли вы?",
24
+ "aside.caution": "Осторожно",
25
+ "aside.danger": "Опасно"
22
26
  }
@@ -18,5 +18,9 @@
18
18
  "page.lastUpdated": "Senast uppdaterad:",
19
19
  "page.previousLink": "Föregående",
20
20
  "page.nextLink": "Nästa",
21
- "404.text": "Sidan hittades inte. Kontrollera URL:n eller testa att använda sökfältet."
21
+ "404.text": "Sidan hittades inte. Kontrollera URL:n eller testa att använda sökfältet.",
22
+ "aside.note": "Note",
23
+ "aside.tip": "Tip",
24
+ "aside.caution": "Caution",
25
+ "aside.danger": "Danger"
22
26
  }
@@ -18,5 +18,9 @@
18
18
  "page.lastUpdated": "Son güncelleme:",
19
19
  "page.previousLink": "Önceki",
20
20
  "page.nextLink": "Sonraki",
21
- "404.text": "Sayfa bulunamadı. URL'i kontrol edin ya da arama çubuğunu kullanmayı deneyin."
21
+ "404.text": "Sayfa bulunamadı. URL'i kontrol edin ya da arama çubuğunu kullanmayı deneyin.",
22
+ "aside.note": "Note",
23
+ "aside.tip": "Tip",
24
+ "aside.caution": "Caution",
25
+ "aside.danger": "Danger"
22
26
  }
@@ -18,5 +18,9 @@
18
18
  "page.lastUpdated": "Останнє оновлення:",
19
19
  "page.previousLink": "Попередня",
20
20
  "page.nextLink": "Наступна",
21
- "404.text": "Сторінку не знайдено. Перевірте URL-адресу або спробуйте скористатися рядком пошуку."
21
+ "404.text": "Сторінку не знайдено. Перевірте URL-адресу або спробуйте скористатися рядком пошуку.",
22
+ "aside.note": "Note",
23
+ "aside.tip": "Tip",
24
+ "aside.caution": "Caution",
25
+ "aside.danger": "Danger"
22
26
  }
@@ -18,5 +18,9 @@
18
18
  "page.lastUpdated": "Cập nhật lần cuối:",
19
19
  "page.previousLink": "Tiếp",
20
20
  "page.nextLink": "Trước",
21
- "404.text": "Không tìm thấy trang. Kiểm tra URL hoặc thử sử dụng thanh tìm kiếm."
21
+ "404.text": "Không tìm thấy trang. Kiểm tra URL hoặc thử sử dụng thanh tìm kiếm.",
22
+ "aside.note": "Note",
23
+ "aside.tip": "Tip",
24
+ "aside.caution": "Caution",
25
+ "aside.danger": "Danger"
22
26
  }
@@ -18,5 +18,9 @@
18
18
  "page.lastUpdated": "最近更新:",
19
19
  "page.previousLink": "上一页",
20
20
  "page.nextLink": "下一页",
21
- "404.text": "页面未找到。检查 URL 或尝试使用搜索栏。"
21
+ "404.text": "页面未找到。检查 URL 或尝试使用搜索栏。",
22
+ "aside.note": "注意",
23
+ "aside.tip": "提示",
24
+ "aside.caution": "警告",
25
+ "aside.danger": "危险"
22
26
  }
package/utils/base.ts CHANGED
@@ -1,15 +1,15 @@
1
- import { stripLeadingAndTrailingSlashes, stripTrailingSlash } from './path';
1
+ import { stripLeadingSlash, stripTrailingSlash } from './path';
2
2
 
3
3
  const base = stripTrailingSlash(import.meta.env.BASE_URL);
4
4
 
5
5
  /** Get the a root-relative URL path with the site’s `base` prefixed. */
6
6
  export function pathWithBase(path: string) {
7
- path = stripLeadingAndTrailingSlashes(path);
8
- return path ? base + '/' + path + '/' : base + '/';
7
+ path = stripLeadingSlash(path);
8
+ return path ? base + '/' + path : base + '/';
9
9
  }
10
10
 
11
11
  /** Get the a root-relative file URL path with the site’s `base` prefixed. */
12
12
  export function fileWithBase(path: string) {
13
- path = stripLeadingAndTrailingSlashes(path);
13
+ path = stripLeadingSlash(path);
14
14
  return path ? base + '/' + path : base;
15
15
  }
@@ -0,0 +1,57 @@
1
+ import type { AstroConfig } from 'astro';
2
+ import { fileWithBase, pathWithBase } from './base';
3
+ import {
4
+ ensureHtmlExtension,
5
+ ensureTrailingSlash,
6
+ stripHtmlExtension,
7
+ stripTrailingSlash,
8
+ } from './path';
9
+
10
+ interface FormatPathOptions {
11
+ format?: AstroConfig['build']['format'];
12
+ trailingSlash?: AstroConfig['trailingSlash'];
13
+ }
14
+
15
+ const formatStrategies = {
16
+ file: {
17
+ addBase: fileWithBase,
18
+ handleExtension: (href: string) => ensureHtmlExtension(href),
19
+ },
20
+ directory: {
21
+ addBase: pathWithBase,
22
+ handleExtension: (href: string) => stripHtmlExtension(href),
23
+ },
24
+ };
25
+
26
+ const trailingSlashStrategies = {
27
+ always: ensureTrailingSlash,
28
+ never: stripTrailingSlash,
29
+ ignore: (href: string) => href,
30
+ };
31
+
32
+ /** Format a path based on the project config. */
33
+ function formatPath(
34
+ href: string,
35
+ { format = 'directory', trailingSlash = 'ignore' }: FormatPathOptions
36
+ ) {
37
+ const formatStrategy = formatStrategies[format];
38
+ const trailingSlashStrategy = trailingSlashStrategies[trailingSlash];
39
+
40
+ // Add base
41
+ href = formatStrategy.addBase(href);
42
+
43
+ // Handle extension
44
+ href = formatStrategy.handleExtension(href);
45
+
46
+ // Skip trailing slash handling for `build.format: 'file'`
47
+ if (format === 'file') return href;
48
+
49
+ // Handle trailing slash
50
+ href = href === '/' ? href : trailingSlashStrategy(href);
51
+
52
+ return href;
53
+ }
54
+
55
+ export function createPathFormatter(opts: FormatPathOptions) {
56
+ return (href: string) => formatPath(href, opts);
57
+ }
@@ -1,5 +1,5 @@
1
1
  import type { i18nSchemaOutput } from '../schemas/i18n';
2
- import builtinTranslations from '../translations';
2
+ import builtinTranslations from '../translations/index';
3
3
  import type { StarlightConfig } from './user-config';
4
4
 
5
5
  export function createTranslationSystem(
@@ -0,0 +1,7 @@
1
+ import project from 'virtual:starlight/project-context';
2
+ import { createPathFormatter } from './createPathFormatter';
3
+
4
+ export const formatPath = createPathFormatter({
5
+ format: project.build.format,
6
+ trailingSlash: project.trailingSlash,
7
+ });
@@ -1,18 +1,19 @@
1
1
  import { basename, dirname } from 'node:path';
2
2
  import config from 'virtual:starlight/user-config';
3
- import type { PrevNextLinkConfig } from '../schemas/prevNextLink';
4
- import { pathWithBase } from './base';
5
- import { pickLang } from './i18n';
6
- import { getLocaleRoutes, type Route } from './routing';
7
- import { localeToLang, slugToPathname } from './slugs';
8
- import { ensureLeadingAndTrailingSlashes, ensureTrailingSlash } from './path';
9
3
  import type { Badge } from '../schemas/badge';
4
+ import type { PrevNextLinkConfig } from '../schemas/prevNextLink';
10
5
  import type {
11
6
  AutoSidebarGroup,
12
7
  LinkHTMLAttributes,
13
8
  SidebarItem,
14
9
  SidebarLinkItem,
15
10
  } from '../schemas/sidebar';
11
+ import { createPathFormatter } from './createPathFormatter';
12
+ import { formatPath } from './format-path';
13
+ import { pickLang } from './i18n';
14
+ import { ensureLeadingSlash } from './path';
15
+ import { getLocaleRoutes, type Route } from './routing';
16
+ import { localeToLang, slugToPathname } from './slugs';
16
17
 
17
18
  const DirKey = Symbol('DirKey');
18
19
 
@@ -118,7 +119,7 @@ function linkFromConfig(
118
119
  ) {
119
120
  let href = item.link;
120
121
  if (!isAbsolute(href)) {
121
- href = ensureLeadingAndTrailingSlashes(href);
122
+ href = ensureLeadingSlash(href);
122
123
  // Inject current locale into link.
123
124
  if (locale) href = '/' + locale + href;
124
125
  }
@@ -134,11 +135,21 @@ function makeLink(
134
135
  badge?: Badge,
135
136
  attrs?: LinkHTMLAttributes
136
137
  ): Link {
137
- if (!isAbsolute(href)) href = pathWithBase(href);
138
- const isCurrent = href === ensureTrailingSlash(currentPathname);
138
+ if (!isAbsolute(href)) {
139
+ href = formatPath(href);
140
+ }
141
+
142
+ const isCurrent = pathsMatch(encodeURI(href), currentPathname);
143
+
139
144
  return { type: 'link', label, href, isCurrent, badge, attrs: attrs ?? {} };
140
145
  }
141
146
 
147
+ /** Test if two paths are equivalent even if formatted differently. */
148
+ function pathsMatch(pathA: string, pathB: string) {
149
+ const format = createPathFormatter({ trailingSlash: 'never' });
150
+ return format(pathA) === format(pathB);
151
+ }
152
+
142
153
  /** Get the segments leading to a page. */
143
154
  function getBreadcrumbs(path: string, baseDir: string): string[] {
144
155
  // Strip extension from path.
@@ -333,7 +344,7 @@ function applyPrevNextLinkConfig(
333
344
  } else if (config.link && config.label) {
334
345
  // If there is no link and the frontmatter contains both a URL and a label,
335
346
  // create a new link.
336
- return makeLink(config.link, config.label, config.link);
347
+ return makeLink(config.link, config.label, '');
337
348
  }
338
349
  }
339
350
  // Otherwise, if the global config is enabled, return the generated link if any.
package/utils/path.ts CHANGED
@@ -35,3 +35,18 @@ export function stripLeadingAndTrailingSlashes(href: string): string {
35
35
  href = stripTrailingSlash(href);
36
36
  return href;
37
37
  }
38
+
39
+ /** Remove the extension from a path. */
40
+ export function stripHtmlExtension(path: string) {
41
+ const pathWithoutTrailingSlash = stripTrailingSlash(path);
42
+ return pathWithoutTrailingSlash.endsWith('.html') ? pathWithoutTrailingSlash.slice(0, -5) : path;
43
+ }
44
+
45
+ /** Add '.html' extension to a path. */
46
+ export function ensureHtmlExtension(path: string) {
47
+ path = stripLeadingAndTrailingSlashes(path);
48
+ if (!path.endsWith('.html')) {
49
+ path = path ? path + '.html' : '/index.html';
50
+ }
51
+ return ensureLeadingSlash(path);
52
+ }
@@ -66,9 +66,9 @@ function getToC({ entry, locale, headings }: PageProps) {
66
66
  };
67
67
  }
68
68
 
69
- function getLastUpdated({ entry, id }: PageProps): Date | undefined {
69
+ function getLastUpdated({ entry }: PageProps): Date | undefined {
70
70
  if (entry.data.lastUpdated ?? config.lastUpdated) {
71
- const currentFilePath = fileURLToPath(new URL('src/content/docs/' + id, project.root));
71
+ const currentFilePath = fileURLToPath(new URL('src/content/docs/' + entry.id, project.root));
72
72
  let date = typeof entry.data.lastUpdated !== 'boolean' ? entry.data.lastUpdated : undefined;
73
73
  if (!date) {
74
74
  try {
@@ -1,6 +1,7 @@
1
1
  import { z } from 'astro/zod';
2
2
  import { parse as bcpParse, stringify as bcpStringify } from 'bcp-47';
3
3
  import { ComponentConfigSchema } from '../schemas/components';
4
+ import { ExpressiveCodeSchema } from '../schemas/expressiveCode';
4
5
  import { FaviconSchema } from '../schemas/favicon';
5
6
  import { HeadConfigSchema } from '../schemas/head';
6
7
  import { LogoConfigSchema } from '../schemas/logo';
@@ -183,6 +184,12 @@ const UserConfigSchema = z.object({
183
184
  /** The default favicon for your site which should be a path to an image in the `public/` directory. */
184
185
  favicon: FaviconSchema(),
185
186
 
187
+ /**
188
+ * Define how code blocks are rendered by passing options to Expressive Code,
189
+ * or disable the integration by passing `false`.
190
+ */
191
+ expressiveCode: ExpressiveCodeSchema(),
192
+
186
193
  /** Specify paths to components that should override Starlight’s default components */
187
194
  components: ComponentConfigSchema(),
188
195
 
package/virtual.d.ts CHANGED
@@ -3,7 +3,15 @@ declare module 'virtual:starlight/user-config' {
3
3
  export default Config;
4
4
  }
5
5
  declare module 'virtual:starlight/project-context' {
6
- export default { root: string, srcDir: string };
6
+ const ProjectContext: {
7
+ root: string;
8
+ srcDir: string;
9
+ trailingSlash: import('astro').AstroConfig['trailingSlash'];
10
+ build: {
11
+ format: import('astro').AstroConfig['build']['format'];
12
+ };
13
+ };
14
+ export default ProjectContext;
7
15
  }
8
16
 
9
17
  declare module 'virtual:starlight/user-css' {}