@astrojs/starlight 0.1.4 → 0.3.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 CHANGED
@@ -1,58 +1,39 @@
1
1
  ---
2
+ import { getEntry } from 'astro:content';
2
3
  import config from 'virtual:starlight/user-config';
3
- import { pathWithBase } from './utils/base';
4
+ import EmptyContent from './components/EmptyMarkdown.md';
5
+ import Page from './layout/Page.astro';
6
+ import type { StarlightDocsEntry } from './utils/routing';
7
+ import { useTranslations } from './utils/translations';
4
8
 
5
- // Built-in CSS styles.
6
- import './style/props.css';
7
- import './style/reset.css';
8
- import './style/shiki.css';
9
- import './style/util.css';
10
-
11
- // Layout
12
- import PageFrame from './layout/PageFrame.astro';
13
-
14
- // Components
15
- import Header from './components/Header.astro';
16
- import MarkdownContent from './components/MarkdownContent.astro';
17
- import ThemeProvider from './components/ThemeProvider.astro';
18
- import SkipLink from './components/SkipLink.astro';
9
+ const { lang = 'en', dir = 'ltr', locale } = config.defaultLocale || {};
10
+ const entryMeta = { dir, lang, locale };
11
+ const t = useTranslations(locale);
19
12
 
20
- // Important that this is the last import so it can override built-in styles.
21
- import 'virtual:starlight/user-css';
13
+ const fallbackEntry: StarlightDocsEntry = {
14
+ slug: '404',
15
+ id: '404.md' as StarlightDocsEntry['id'],
16
+ body: '',
17
+ collection: 'docs',
18
+ data: {
19
+ title: '404',
20
+ template: 'splash',
21
+ editUrl: false,
22
+ head: [],
23
+ hero: { tagline: t('404.text'), actions: [] },
24
+ },
25
+ render: async () => ({
26
+ Content: EmptyContent,
27
+ headings: [],
28
+ remarkPluginFrontmatter: {},
29
+ }),
30
+ };
22
31
 
23
- const { lang = 'en', dir = 'ltr', locale } = config.defaultLocale || {};
32
+ const userEntry = await getEntry('docs', '404');
33
+ const entry = userEntry || fallbackEntry;
34
+ const { Content, headings } = await entry.render();
24
35
  ---
25
36
 
26
- <html lang={lang} dir={dir}>
27
- <head>
28
- <meta charset="utf-8" />
29
- <meta name="viewport" content="width=device-width" />
30
- <title>Not found</title>
31
- </head>
32
- <body>
33
- <ThemeProvider />
34
- <SkipLink {locale} />
35
- <PageFrame {locale} hasSidebar={false}>
36
- <Header slot="header" {locale} />
37
- <main>
38
- <MarkdownContent>
39
- <h1 id="starlight__overview">404</h1>
40
- <p>Houston, we have a problem.</p>
41
- <p>
42
- We couldn’t find that link. Check the address or
43
- <a href={pathWithBase('/')}>head back home</a>.
44
- </p>
45
- </MarkdownContent>
46
- </main>
47
- </PageFrame>
48
-
49
- <style>
50
- main {
51
- margin: auto;
52
- padding: clamp(2rem, 10vmin, 6rem) var(--sl-nav-pad-x);
53
- max-width: var(--sl-content-width);
54
- max-width: max-content;
55
- }
56
- </style>
57
- </body>
58
- </html>
37
+ <Page {headings} entry={entry} slug={entry.slug} {...entryMeta} {entryMeta}>
38
+ <Content />
39
+ </Page>
package/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # @astrojs/starlight
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#237](https://github.com/withastro/starlight/pull/237) [`4279d75`](https://github.com/withastro/starlight/commit/4279d7512a8261b576056471f5aa1ede1e6aae4a) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Use path instead of slugified path for auto-generated sidebar item configuration
8
+
9
+ ⚠️ Potentially breaking change. If your docs directory names don’t match their URLs, for example they contain whitespace like `docs/my docs/`, and you were referencing these in an `autogenerate` sidebar group as `my-docs`, update your config to reference these with the directory name instead of the slugified version:
10
+
11
+ ```diff
12
+ autogenerate: {
13
+ - directory: 'my-docs',
14
+ + directory: 'my docs',
15
+ }
16
+ ```
17
+
18
+ - [#226](https://github.com/withastro/starlight/pull/226) [`1aa2187`](https://github.com/withastro/starlight/commit/1aa2187944dde4419e523f0087139f5a21efd826) Thanks [@delucis](https://github.com/delucis)! - Add support for custom 404 pages.
19
+
20
+ ### Patch Changes
21
+
22
+ - [#234](https://github.com/withastro/starlight/pull/234) [`91309ae`](https://github.com/withastro/starlight/commit/91309ae13250c5fd9f91a8e1843f16430773ff15) Thanks [@morinokami](https://github.com/morinokami)! - Add Japanese translation for `search.devWarning`
23
+
24
+ - [#227](https://github.com/withastro/starlight/pull/227) [`fbdecfa`](https://github.com/withastro/starlight/commit/fbdecfab47effb0cba7cbc9233a7b6bffdded320) Thanks [@Yan-Thomas](https://github.com/Yan-Thomas)! - Add missing i18n support to the Search component's dev warning.
25
+
26
+ - [#244](https://github.com/withastro/starlight/pull/244) [`f1bcbeb`](https://github.com/withastro/starlight/commit/f1bcbebeb441b6bb9ed6a1ab2414791e9d5de6ef) Thanks [@Waxer59](https://github.com/Waxer59)! - Add Spanish translation for `search.devWarning`
27
+
28
+ ## 0.2.0
29
+
30
+ ### Minor Changes
31
+
32
+ - [#171](https://github.com/withastro/starlight/pull/171) [`198c3f0`](https://github.com/withastro/starlight/commit/198c3f001410f259dab7d085136a37afe863cfa4) Thanks [@delucis](https://github.com/delucis)! - Add Starlight generator tag to HTML output
33
+
34
+ - [#217](https://github.com/withastro/starlight/pull/217) [`490fd98`](https://github.com/withastro/starlight/commit/490fd98d4e7b38ec01c568eee0ab00844e59c53d) Thanks [@delucis](https://github.com/delucis)! - Updated sidebar styles. Sidebars now support top-level links and groups are styled with a subtle border and indentation to improve comprehension of nesting.
35
+
36
+ - [#178](https://github.com/withastro/starlight/pull/178) [`d046c55`](https://github.com/withastro/starlight/commit/d046c55a62290c15f2e09faf4359f02df9492f6d) Thanks [@delucis](https://github.com/delucis)! - Add support for translating the Pagefind search modal
37
+
38
+ - [#210](https://github.com/withastro/starlight/pull/210) [`cb5b121`](https://github.com/withastro/starlight/commit/cb5b1210e23548e2983865a4b38308b0f54dc7ce) Thanks [@delucis](https://github.com/delucis)! - Change page title ID to `_top` for cleaner hash URLs
39
+
40
+ ⚠️ Potentially breaking change if you were linking manually to `#starlight__overview` anywhere. If you were, update these links to use `#_top` instead.
41
+
42
+ ### Patch Changes
43
+
44
+ - [#208](https://github.com/withastro/starlight/pull/208) [`09fc565`](https://github.com/withastro/starlight/commit/09fc565d44bd3abb4508541b458531de8624036f) Thanks [@delucis](https://github.com/delucis)! - Update `@astrojs/mdx` and `@astrojs/sitemap` to latest
45
+
46
+ - [#216](https://github.com/withastro/starlight/pull/216) [`54905c5`](https://github.com/withastro/starlight/commit/54905c502c5e6de5516e36ddcd4969893572baa5) Thanks [@morinokami](https://github.com/morinokami)! - Encode heading id when finding current link
47
+
3
48
  ## 0.1.4
4
49
 
5
50
  ### Patch Changes
File without changes
@@ -5,6 +5,7 @@ import type { HeadConfigSchema } from '../schemas/head';
5
5
  import { createHead } from '../utils/head';
6
6
  import { localizedUrl } from '../utils/localizedUrl';
7
7
  import { fileWithBase } from '../utils/base';
8
+ import { version } from '../package.json';
8
9
 
9
10
  interface Props {
10
11
  data: CollectionEntry<'docs'>['data'];
@@ -28,6 +29,10 @@ const headDefaults: z.input<ReturnType<typeof HeadConfigSchema>> = [
28
29
  { tag: 'title', content: title },
29
30
  { tag: 'link', attrs: { rel: 'canonical', href: canonical?.href } },
30
31
  { tag: 'meta', attrs: { name: 'generator', content: Astro.generator } },
32
+ {
33
+ tag: 'meta',
34
+ attrs: { name: 'generator', content: `Starlight v${version}` },
35
+ },
31
36
  // Favicon
32
37
  {
33
38
  tag: 'link',
@@ -38,7 +38,7 @@ const imageAttrs = {
38
38
  }
39
39
  <div class="flex stack">
40
40
  <div class="flex copy">
41
- <h1 id="starlight__overview" set:html={title} />
41
+ <h1 id="_top" data-page-title set:html={title} />
42
42
  {tagline && <div class="tagline" set:html={tagline} />}
43
43
  </div>
44
44
  {
@@ -8,9 +8,18 @@ interface Props {
8
8
  }
9
9
 
10
10
  const t = useTranslations(Astro.props.locale);
11
+ const pagefindTranslations = {
12
+ placeholder: t('search.label'),
13
+ ...Object.fromEntries(
14
+ Object.entries(t.pick('pagefind.')).map(([key, value]) => [
15
+ key.replace('pagefind.', ''),
16
+ value,
17
+ ])
18
+ ),
19
+ };
11
20
  ---
12
21
 
13
- <site-search>
22
+ <site-search data-translations={JSON.stringify(pagefindTranslations)}>
14
23
  <button data-open-modal disabled>
15
24
  {
16
25
  /* The span is `aria-hidden` because it is not shown on small screens. Instead, the icon label is used for accessibility purposes. */
@@ -34,9 +43,8 @@ const t = useTranslations(Astro.props.locale);
34
43
  </button>
35
44
  {
36
45
  import.meta.env.DEV ? (
37
- <div style="margin: auto; text-align: center;" dir="ltr">
38
- <p>Search is only available in production builds.</p>
39
- <p>Try building and previewing the site to test it out locally.</p>
46
+ <div style="margin: auto; text-align: center; white-space: pre-line;" dir="ltr">
47
+ <p>{t("search.devWarning")}</p>
40
48
  </div>
41
49
  ) : (
42
50
  <div class="search-container">
@@ -96,23 +104,31 @@ const t = useTranslations(Astro.props.locale);
96
104
  e.preventDefault();
97
105
  }
98
106
  });
107
+
108
+ let translations = {};
109
+ try {
110
+ translations = JSON.parse(this.dataset.translations || '{}');
111
+ } catch {}
112
+
113
+ window.addEventListener('DOMContentLoaded', () => {
114
+ if (import.meta.env.DEV) return;
115
+ const onIdle =
116
+ window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
117
+ onIdle(async () => {
118
+ const { PagefindUI } = await import('@pagefind/default-ui');
119
+ new PagefindUI({
120
+ element: '#starlight__search',
121
+ baseUrl: import.meta.env.BASE_URL,
122
+ bundlePath:
123
+ import.meta.env.BASE_URL.replace(/\/$/, '') + '/_pagefind/',
124
+ showImages: false,
125
+ translations,
126
+ });
127
+ });
128
+ });
99
129
  }
100
130
  }
101
131
  customElements.define('site-search', SiteSearch);
102
-
103
- window.addEventListener('DOMContentLoaded', () => {
104
- if (import.meta.env.DEV) return;
105
- const onIdle = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
106
- onIdle(async () => {
107
- const { PagefindUI } = await import('@pagefind/default-ui');
108
- new PagefindUI({
109
- element: '#starlight__search',
110
- baseUrl: import.meta.env.BASE_URL,
111
- bundlePath: import.meta.env.BASE_URL.replace(/\/$/, '') + '/_pagefind/',
112
- showImages: false,
113
- });
114
- });
115
- });
116
132
  </script>
117
133
 
118
134
  <style>
@@ -4,24 +4,29 @@ import Icon from './Icon.astro';
4
4
 
5
5
  interface Props {
6
6
  sublist: SidebarEntry[];
7
+ nested?: boolean;
7
8
  }
8
9
  ---
9
10
 
10
- <ul>
11
+ <ul class:list={{ 'top-level': !Astro.props.nested }}>
11
12
  {
12
13
  Astro.props.sublist.map((entry) => (
13
- <li class:list={{ 'sidebar-group': entry.type === 'group' }}>
14
+ <li>
14
15
  {entry.type === 'link' ? (
15
- <a href={entry.href} aria-current={entry.isCurrent && 'page'}>
16
+ <a
17
+ href={entry.href}
18
+ aria-current={entry.isCurrent && 'page'}
19
+ class:list={{ large: !Astro.props.nested }}
20
+ >
16
21
  {entry.label}
17
22
  </a>
18
23
  ) : (
19
24
  <details open>
20
25
  <summary>
21
- <h2>{entry.label}</h2>
26
+ <h2 class="large">{entry.label}</h2>
22
27
  <Icon name="right-caret" class="caret" size="1.25rem" />
23
28
  </summary>
24
- <Astro.self sublist={entry.entries} />
29
+ <Astro.self sublist={entry.entries} nested />
25
30
  </details>
26
31
  )}
27
32
  </li>
@@ -36,13 +41,19 @@ interface Props {
36
41
  padding: 0;
37
42
  }
38
43
 
39
- h2 {
44
+ ul ul li {
45
+ margin-inline-start: var(--sl-sidebar-item-padding-inline);
46
+ border-inline-start: 1px solid var(--sl-color-hairline-light);
47
+ padding-inline-start: var(--sl-sidebar-item-padding-inline);
48
+ }
49
+
50
+ .large {
40
51
  font-size: var(--sl-text-lg);
41
52
  font-weight: 600;
42
53
  color: var(--sl-color-white);
43
54
  }
44
55
 
45
- .sidebar-group + .sidebar-group {
56
+ .top-level > li + li {
46
57
  margin-top: 0.75rem;
47
58
  }
48
59
 
@@ -91,10 +102,10 @@ interface Props {
91
102
  }
92
103
 
93
104
  @media (min-width: 50rem) {
94
- .sidebar-group + .sidebar-group {
105
+ .top-level > li + li {
95
106
  margin-top: 0.5rem;
96
107
  }
97
- h2 {
108
+ .large {
98
109
  font-size: var(--sl-text-base);
99
110
  }
100
111
  a {
@@ -8,7 +8,7 @@ interface Props {
8
8
  const t = useTranslations(Astro.props.locale);
9
9
  ---
10
10
 
11
- <a href="#starlight__overview">{t('skipLink.label')}</a>
11
+ <a href="#_top">{t('skipLink.label')}</a>
12
12
 
13
13
  <style>
14
14
  a {
@@ -25,7 +25,7 @@ export function generateToC(
25
25
  headings: MarkdownHeading[],
26
26
  { minHeadingLevel, maxHeadingLevel, title = 'Overview' }: TocOpts
27
27
  ) {
28
- const overview = { depth: 2, slug: 'starlight__overview', text: title };
28
+ const overview = { depth: 2, slug: '_top', text: title };
29
29
  headings = [
30
30
  overview,
31
31
  ...headings.filter(
@@ -22,7 +22,7 @@ export class StarlightTOC extends HTMLElement {
22
22
  const isHeading = (el: Element): el is HTMLHeadingElement => {
23
23
  if (el instanceof HTMLHeadingElement) {
24
24
  // Special case for page title h1
25
- if (el.id === 'starlight__overview') return true;
25
+ if ('pageTitle' in el.dataset) return true;
26
26
  // Check the heading level is within the user-configured limits for the ToC
27
27
  const level = el.tagName[1];
28
28
  if (level) {
@@ -60,7 +60,7 @@ export class StarlightTOC extends HTMLElement {
60
60
  if (!isIntersecting) continue;
61
61
  const heading = getElementHeading(target);
62
62
  if (!heading) continue;
63
- const link = links.find((link) => link.hash === '#' + heading.id);
63
+ const link = links.find((link) => link.hash === '#' + encodeURIComponent(heading.id));
64
64
  if (link) {
65
65
  this.current = link;
66
66
  break;
package/index.astro CHANGED
@@ -1,121 +1,15 @@
1
1
  ---
2
2
  import type { InferGetStaticPropsType } from 'astro';
3
- import config from 'virtual:starlight/user-config';
4
-
5
- import { getSidebar } from './utils/navigation';
6
3
  import { paths } from './utils/routing';
7
4
 
8
- // Built-in CSS styles.
9
- import './style/props.css';
10
- import './style/reset.css';
11
- import './style/shiki.css';
12
- import './style/util.css';
13
-
14
- // Components — can override built-in CSS, but not user CSS.
15
- import ContentPanel from './components/ContentPanel.astro';
16
- import FallbackContentNotice from './components/FallbackContentNotice.astro';
17
- import HeadSEO from './components/HeadSEO.astro';
18
- import Header from './components/Header.astro';
19
- import Footer from './components/Footer.astro';
20
- import MarkdownContent from './components/MarkdownContent.astro';
21
- import RightSidebar from './components/RightSidebar.astro';
22
- import Sidebar from './components/Sidebar.astro';
23
- import SkipLink from './components/SkipLink.astro';
24
- import ThemeProvider from './components/ThemeProvider.astro';
25
- import PageFrame from './layout/PageFrame.astro';
26
- import TwoColumnContent from './layout/TwoColumnContent.astro';
27
- import Hero from './components/Hero.astro';
28
-
29
- // Remark component CSS (needs to override `MarkdownContent.astro`)
30
- import './style/asides.css';
31
-
32
- // Important that this is the last import so it can override built-in styles.
33
- import 'virtual:starlight/user-css';
5
+ import Page from './layout/Page.astro';
34
6
 
35
7
  export async function getStaticPaths() {
36
8
  return paths;
37
9
  }
38
10
 
39
11
  type Props = InferGetStaticPropsType<typeof getStaticPaths>;
40
-
41
- const { dir, entry, entryMeta, isFallback, lang, locale } = Astro.props;
42
- const { Content, headings } = await entry.render();
43
- const sidebar = getSidebar(Astro.url.pathname, locale);
44
-
45
- const hasSidebar = entry.data.template !== 'splash';
46
- const tocConfig = !hasSidebar
47
- ? false
48
- : entry.data.tableOfContents !== undefined
49
- ? entry.data.tableOfContents
50
- : config.tableOfContents;
51
- const hasToC = Boolean(tocConfig);
52
- const hasHero = Boolean(entry.data.hero);
12
+ const { Content, headings } = await Astro.props.entry.render();
53
13
  ---
54
14
 
55
- <html lang={lang} dir={dir} data-has-toc={hasToC} data-has-sidebar={hasSidebar} data-has-hero={hasHero}>
56
- <head>
57
- <HeadSEO data={entry.data} lang={lang} />
58
- <style>
59
- html:not([data-has-toc]) {
60
- --sl-mobile-toc-height: 0rem;
61
- }
62
- html:not([data-has-sidebar]) {
63
- --sl-content-width: 67.5rem;
64
- }
65
- /* Add scroll padding to ensure anchor headings aren't obscured by nav */
66
- html {
67
- /* Additional padding is needed to account for the mobile TOC */
68
- scroll-padding-top: calc(
69
- 1.5rem + var(--sl-nav-height) + var(--sl-mobile-toc-height)
70
- );
71
- }
72
- main {
73
- padding-bottom: 3vh;
74
- }
75
- @media (min-width: 50em) {
76
- [data-has-sidebar] {
77
- --sl-content-inline-start: var(--sl-sidebar-width);
78
- }
79
- }
80
- @media (min-width: 72em) {
81
- html {
82
- scroll-padding-top: calc(1.5rem + var(--sl-nav-height));
83
- }
84
- }
85
- </style>
86
- </head>
87
- <body>
88
- <ThemeProvider />
89
- <SkipLink {locale} />
90
- <PageFrame {locale} {hasSidebar}>
91
- <Header slot="header" {locale} />
92
- {hasSidebar && <Sidebar slot="sidebar" {sidebar} {locale} />}
93
- <TwoColumnContent {hasToC}>
94
- <RightSidebar slot="right-sidebar" {headings} {locale} {tocConfig} />
95
- <main data-pagefind-body lang={entryMeta.lang} dir={entryMeta.dir}>
96
- {/* TODO: Revisit how this logic flows. */}
97
- {entry.data.hero ? (
98
- <ContentPanel>
99
- <Hero hero={entry.data.hero} fallbackTitle={entry.data.title} />
100
- <MarkdownContent><Content /></MarkdownContent>
101
- </ContentPanel>
102
- ) : (
103
- <ContentPanel>
104
- <h1
105
- id="starlight__overview"
106
- style="font-size: var(--sl-text-h1); line-height: var(--sl-line-height-headings); font-weight: 600; color: var(--sl-color-white); margin-top: 1rem;"
107
- >
108
- {entry.data.title}
109
- </h1>
110
- {isFallback && <FallbackContentNotice {locale} />}
111
- </ContentPanel>
112
- <ContentPanel>
113
- <MarkdownContent><Content /></MarkdownContent>
114
- <Footer {...{ entry, dir, lang, locale, sidebar }} />
115
- </ContentPanel>
116
- )}
117
- </main>
118
- </TwoColumnContent>
119
- </PageFrame>
120
- </body>
121
- </html>
15
+ <Page {...Astro.props} {headings}><Content /></Page>
@@ -0,0 +1,116 @@
1
+ ---
2
+ import config from 'virtual:starlight/user-config';
3
+ import type { MarkdownHeading } from 'astro';
4
+ import { getSidebar } from '../utils/navigation';
5
+ import type { Route } from '../utils/routing';
6
+
7
+ // Built-in CSS styles.
8
+ import '../style/props.css';
9
+ import '../style/reset.css';
10
+ import '../style/shiki.css';
11
+ import '../style/util.css';
12
+
13
+ // Components — can override built-in CSS, but not user CSS.
14
+ import ContentPanel from '../components/ContentPanel.astro';
15
+ import FallbackContentNotice from '../components/FallbackContentNotice.astro';
16
+ import Footer from '../components/Footer.astro';
17
+ import HeadSEO from '../components/HeadSEO.astro';
18
+ import Header from '../components/Header.astro';
19
+ import Hero from '../components/Hero.astro';
20
+ import MarkdownContent from '../components/MarkdownContent.astro';
21
+ import RightSidebar from '../components/RightSidebar.astro';
22
+ import Sidebar from '../components/Sidebar.astro';
23
+ import SkipLink from '../components/SkipLink.astro';
24
+ import ThemeProvider from '../components/ThemeProvider.astro';
25
+ import PageFrame from '../layout/PageFrame.astro';
26
+ import TwoColumnContent from '../layout/TwoColumnContent.astro';
27
+
28
+ // Remark component CSS (needs to override `MarkdownContent.astro`)
29
+ import '../style/asides.css';
30
+
31
+ // Important that this is the last import so it can override built-in styles.
32
+ import 'virtual:starlight/user-css';
33
+
34
+ type Props = Route & { headings: MarkdownHeading[] };
35
+
36
+ const { dir, entry, entryMeta, headings, isFallback, lang, locale } = Astro.props;
37
+ const sidebar = getSidebar(Astro.url.pathname, locale);
38
+
39
+ const hasSidebar = entry.data.template !== 'splash';
40
+ const tocConfig = !hasSidebar
41
+ ? false
42
+ : entry.data.tableOfContents !== undefined
43
+ ? entry.data.tableOfContents
44
+ : config.tableOfContents;
45
+ const hasToC = Boolean(tocConfig);
46
+ const hasHero = Boolean(entry.data.hero);
47
+ ---
48
+
49
+ <html lang={lang} dir={dir} data-has-toc={hasToC} data-has-sidebar={hasSidebar} data-has-hero={hasHero}>
50
+ <head>
51
+ <HeadSEO data={entry.data} lang={lang} />
52
+ <style>
53
+ html:not([data-has-toc]) {
54
+ --sl-mobile-toc-height: 0rem;
55
+ }
56
+ html:not([data-has-sidebar]) {
57
+ --sl-content-width: 67.5rem;
58
+ }
59
+ /* Add scroll padding to ensure anchor headings aren't obscured by nav */
60
+ html {
61
+ /* Additional padding is needed to account for the mobile TOC */
62
+ scroll-padding-top: calc(
63
+ 1.5rem + var(--sl-nav-height) + var(--sl-mobile-toc-height)
64
+ );
65
+ }
66
+ main {
67
+ padding-bottom: 3vh;
68
+ }
69
+ @media (min-width: 50em) {
70
+ [data-has-sidebar] {
71
+ --sl-content-inline-start: var(--sl-sidebar-width);
72
+ }
73
+ }
74
+ @media (min-width: 72em) {
75
+ html {
76
+ scroll-padding-top: calc(1.5rem + var(--sl-nav-height));
77
+ }
78
+ }
79
+ </style>
80
+ </head>
81
+ <body>
82
+ <ThemeProvider />
83
+ <SkipLink {locale} />
84
+ <PageFrame {locale} {hasSidebar}>
85
+ <Header slot="header" {locale} />
86
+ {hasSidebar && <Sidebar slot="sidebar" {sidebar} {locale} />}
87
+ <TwoColumnContent {hasToC}>
88
+ <RightSidebar slot="right-sidebar" {headings} {locale} {tocConfig} />
89
+ <main data-pagefind-body={entry.slug !== '404'} lang={entryMeta.lang} dir={entryMeta.dir}>
90
+ {/* TODO: Revisit how this logic flows. */}
91
+ {entry.data.hero ? (
92
+ <ContentPanel>
93
+ <Hero hero={entry.data.hero} fallbackTitle={entry.data.title} />
94
+ <MarkdownContent><slot /></MarkdownContent>
95
+ </ContentPanel>
96
+ ) : (
97
+ <ContentPanel>
98
+ <h1
99
+ id="_top"
100
+ data-page-title
101
+ style="font-size: var(--sl-text-h1); line-height: var(--sl-line-height-headings); font-weight: 600; color: var(--sl-color-white); margin-top: 1rem;"
102
+ >
103
+ {entry.data.title}
104
+ </h1>
105
+ {isFallback && <FallbackContentNotice {locale} />}
106
+ </ContentPanel>
107
+ <ContentPanel>
108
+ <MarkdownContent><slot /></MarkdownContent>
109
+ <Footer {...{ entry, dir, lang, locale, sidebar }} />
110
+ </ContentPanel>
111
+ )}
112
+ </main>
113
+ </TwoColumnContent>
114
+ </PageFrame>
115
+ </body>
116
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/starlight",
3
- "version": "0.1.4",
3
+ "version": "0.3.0",
4
4
  "description": "Build beautiful, high-performance documentation websites with Astro",
5
5
  "keywords": [
6
6
  "docs",
@@ -34,8 +34,8 @@
34
34
  "astro": "^2.5.0"
35
35
  },
36
36
  "dependencies": {
37
- "@astrojs/mdx": "^0.19.1",
38
- "@astrojs/sitemap": "^1.3.1",
37
+ "@astrojs/mdx": "^0.19.7",
38
+ "@astrojs/sitemap": "^1.3.3",
39
39
  "@pagefind/default-ui": "^1.0.0-alpha.5",
40
40
  "@types/mdast": "^3.0.11",
41
41
  "bcp-47": "^2.1.0",
@@ -47,7 +47,7 @@
47
47
  "unified": "^10.1.2",
48
48
  "unist-util-remove": "^3.1.1",
49
49
  "unist-util-visit": "^4.1.2",
50
- "vfile": "^5.3.7"
50
+ "vfile": "^6.0.0"
51
51
  },
52
52
  "scripts": {}
53
53
  }
package/schemas/i18n.ts CHANGED
@@ -1,6 +1,14 @@
1
1
  import { z } from 'astro/zod';
2
2
 
3
3
  export function i18nSchema() {
4
+ return starlightI18nSchema().merge(pagefindI18nSchema());
5
+ }
6
+
7
+ export function builtinI18nSchema() {
8
+ return starlightI18nSchema().required().strict().merge(pagefindI18nSchema());
9
+ }
10
+
11
+ function starlightI18nSchema() {
4
12
  return z
5
13
  .object({
6
14
  'skipLink.label': z
@@ -21,6 +29,10 @@ export function i18nSchema() {
21
29
  .string()
22
30
  .describe('Text for the “Cancel” button that closes the search modal.'),
23
31
 
32
+ 'search.devWarning': z
33
+ .string()
34
+ .describe('Warning displayed when opening the Search in a dev environment.'),
35
+
24
36
  'themeSelect.accessibleLabel': z
25
37
  .string()
26
38
  .describe('Accessible label for the theme selection dropdown.'),
@@ -86,6 +98,76 @@ export function i18nSchema() {
86
98
  .describe(
87
99
  'Label shown on the “next page” pagination arrow in the page footer.'
88
100
  ),
101
+
102
+ '404.text': z
103
+ .string()
104
+ .describe('Text shown on Starlight’s default 404 page'),
105
+ })
106
+ .partial();
107
+ }
108
+
109
+ function pagefindI18nSchema() {
110
+ return z
111
+ .object({
112
+ 'pagefind.clear_search': z
113
+ .string()
114
+ .describe(
115
+ 'Pagefind UI translation. English default value: `"Clear"`. See https://pagefind.app/docs/ui/#translations'
116
+ ),
117
+
118
+ 'pagefind.load_more': z
119
+ .string()
120
+ .describe(
121
+ 'Pagefind UI translation. English default value: `"Load more results"`. See https://pagefind.app/docs/ui/#translations'
122
+ ),
123
+
124
+ 'pagefind.search_label': z
125
+ .string()
126
+ .describe(
127
+ 'Pagefind UI translation. English default value: `"Search this site"`. See https://pagefind.app/docs/ui/#translations'
128
+ ),
129
+
130
+ 'pagefind.filters_label': z
131
+ .string()
132
+ .describe(
133
+ 'Pagefind UI translation. English default value: `"Filters"`. See https://pagefind.app/docs/ui/#translations'
134
+ ),
135
+
136
+ 'pagefind.zero_results': z
137
+ .string()
138
+ .describe(
139
+ 'Pagefind UI translation. English default value: `"No results for [SEARCH_TERM]"`. See https://pagefind.app/docs/ui/#translations'
140
+ ),
141
+
142
+ 'pagefind.many_results': z
143
+ .string()
144
+ .describe(
145
+ 'Pagefind UI translation. English default value: `"[COUNT] results for [SEARCH_TERM]"`. See https://pagefind.app/docs/ui/#translations'
146
+ ),
147
+
148
+ 'pagefind.one_result': z
149
+ .string()
150
+ .describe(
151
+ 'Pagefind UI translation. English default value: `"[COUNT] result for [SEARCH_TERM]"`. See https://pagefind.app/docs/ui/#translations'
152
+ ),
153
+
154
+ 'pagefind.alt_search': z
155
+ .string()
156
+ .describe(
157
+ 'Pagefind UI translation. English default value: `"No results for [SEARCH_TERM]. Showing results for [DIFFERENT_TERM] instead"`. See https://pagefind.app/docs/ui/#translations'
158
+ ),
159
+
160
+ 'pagefind.search_suggestion': z
161
+ .string()
162
+ .describe(
163
+ 'Pagefind UI translation. English default value: `"No results for [SEARCH_TERM]. Try one of the following searches:"`. See https://pagefind.app/docs/ui/#translations'
164
+ ),
165
+
166
+ 'pagefind.searching': z
167
+ .string()
168
+ .describe(
169
+ 'Pagefind UI translation. English default value: `"Searching for [SEARCH_TERM]..."`. See https://pagefind.app/docs/ui/#translations'
170
+ ),
89
171
  })
90
172
  .partial();
91
173
  }
package/style/props.css CHANGED
@@ -43,6 +43,7 @@
43
43
  --sl-color-bg-nav: var(--sl-color-gray-6);
44
44
  --sl-color-bg-sidebar: var(--sl-color-gray-6);
45
45
  --sl-color-bg-inline-code: var(--sl-color-gray-5);
46
+ --sl-color-hairline-light: var(--sl-color-gray-5);
46
47
  --sl-color-hairline: var(--sl-color-gray-6);
47
48
  --sl-color-hairline-shade: var(--sl-color-black);
48
49
 
@@ -150,6 +151,7 @@
150
151
  --sl-color-bg-nav: var(--sl-color-gray-7);
151
152
  --sl-color-bg-sidebar: var(--sl-color-bg);
152
153
  --sl-color-bg-inline-code: var(--sl-color-gray-6);
154
+ --sl-color-hairline-light: var(--sl-color-gray-6);
153
155
  --sl-color-hairline-shade: var(--sl-color-gray-6);
154
156
 
155
157
  --sl-color-backdrop-overlay: hsla(225, 9%, 36%, 0.66);
@@ -0,0 +1,14 @@
1
+ # Starlight UI translation files
2
+
3
+ This directory contains translation data for Starlight’s UI.
4
+ Each language has its own JSON file and follows the [translation structure described in Starlight’s docs](https://starlight.astro.build/guides/i18n/#translate-starlights-ui).
5
+
6
+ ## Add a new language
7
+
8
+ 1. Create a JSON file named using the BCP-47 tag for the language, e.g. `en.json` or `ja.json`.
9
+
10
+ 2. Fill the file with translations for each UI string. You can base your translations on [`en.json`](./en.json). Translate only the values, leaving the keys in English (e.g. `"search.label": "Buscar"`).
11
+
12
+ 3. Import your file in [`index.ts`](./index.ts) and add your language to the `Object.entries`.
13
+
14
+ 4. Open a pull request on GitHub to add your file to Starlight!
@@ -3,6 +3,7 @@
3
3
  "search.label": "Suchen",
4
4
  "search.shortcutLabel": "(Drücke / zum Suchen)",
5
5
  "search.cancelLabel": "Abbrechen",
6
+ "search.devWarning": "Search is only available in production builds. \nTry building and previewing the site to test it out locally.",
6
7
  "themeSelect.accessibleLabel": "Farbschema wählen",
7
8
  "themeSelect.dark": "Dunkel",
8
9
  "themeSelect.light": "Hell",
@@ -16,5 +17,6 @@
16
17
  "page.editLink": "Seite bearbeiten",
17
18
  "page.lastUpdated": "Zuletzt bearbeitet:",
18
19
  "page.previousLink": "Vorherige Seite",
19
- "page.nextLink": "Nächste Seite"
20
+ "page.nextLink": "Nächste Seite",
21
+ "404.text": "Seite nicht gefunden. Überprüfe die URL oder nutze die Suchleiste."
20
22
  }
@@ -3,6 +3,7 @@
3
3
  "search.label": "Search",
4
4
  "search.shortcutLabel": "(Press / to Search)",
5
5
  "search.cancelLabel": "Cancel",
6
+ "search.devWarning": "Search is only available in production builds. \nTry building and previewing the site to test it out locally.",
6
7
  "themeSelect.accessibleLabel": "Select theme",
7
8
  "themeSelect.dark": "Dark",
8
9
  "themeSelect.light": "Light",
@@ -16,5 +17,6 @@
16
17
  "page.editLink": "Edit page",
17
18
  "page.lastUpdated": "Last updated:",
18
19
  "page.previousLink": "Previous",
19
- "page.nextLink": "Next"
20
+ "page.nextLink": "Next",
21
+ "404.text": "Page not found. Check the URL or try using the search bar."
20
22
  }
@@ -3,6 +3,7 @@
3
3
  "search.label": "Buscar",
4
4
  "search.shortcutLabel": "(Presiona / para buscar)",
5
5
  "search.cancelLabel": "Interrumpir",
6
+ "search.devWarning": "La búsqueda solo está disponible en las versiones de producción. \nIntenta construir y previsualizar el sitio para probarlo localmente.",
6
7
  "themeSelect.accessibleLabel": "Seleccionar tema",
7
8
  "themeSelect.dark": "Oscuro",
8
9
  "themeSelect.light": "Claro",
@@ -16,5 +17,6 @@
16
17
  "page.editLink": "Edita esta página",
17
18
  "page.lastUpdated": "Última actualización:",
18
19
  "page.previousLink": "Página anterior",
19
- "page.nextLink": "Siguiente página"
20
+ "page.nextLink": "Siguiente página",
21
+ "404.text": "Página no encontrada. Verifique la URL o intente usar la barra de búsqueda."
20
22
  }
@@ -3,6 +3,7 @@
3
3
  "search.label": "Rechercher",
4
4
  "search.shortcutLabel": "(Presser / pour rechercher)",
5
5
  "search.cancelLabel": "Annuler",
6
+ "search.devWarning": "Search is only available in production builds. \nTry building and previewing the site to test it out locally.",
6
7
  "themeSelect.accessibleLabel": "Selectionner le thème",
7
8
  "themeSelect.dark": "Dark",
8
9
  "themeSelect.light": "Light",
@@ -16,5 +17,6 @@
16
17
  "page.editLink": "Editer la page",
17
18
  "page.lastUpdated": "Dernière mise à jour :",
18
19
  "page.previousLink": "Précédent",
19
- "page.nextLink": "Suivant"
20
+ "page.nextLink": "Suivant",
21
+ "404.text": "Page non trouvée. Vérifiez l'URL ou essayez d'utiliser la barre de recherche."
20
22
  }
@@ -1,4 +1,4 @@
1
- import { i18nSchema } from '../schemas/i18n';
1
+ import { builtinI18nSchema } from '../schemas/i18n';
2
2
  import en from './en.json';
3
3
  import es from './es.json';
4
4
  import de from './de.json';
@@ -7,7 +7,7 @@ import pt from './pt.json';
7
7
  import fr from './fr.json';
8
8
  import it from './it.json';
9
9
 
10
- const parse = i18nSchema().required().strict().parse;
10
+ const { parse } = builtinI18nSchema();
11
11
 
12
12
  export default Object.fromEntries(
13
13
  Object.entries({ en, es, de, ja, pt, fr, it }).map(([key, dict]) => [key, parse(dict)])
@@ -3,6 +3,7 @@
3
3
  "search.label": "Cerca",
4
4
  "search.shortcutLabel": "(Usa / per cercare)",
5
5
  "search.cancelLabel": "Cancella",
6
+ "search.devWarning": "Search is only available in production builds. \nTry building and previewing the site to test it out locally.",
6
7
  "themeSelect.accessibleLabel": "Seleziona tema",
7
8
  "themeSelect.dark": "Scuro",
8
9
  "themeSelect.light": "Chiaro",
@@ -16,5 +17,6 @@
16
17
  "page.editLink": "Modifica pagina",
17
18
  "page.lastUpdated": "Ultimo aggiornamento:",
18
19
  "page.previousLink": "Indietro",
19
- "page.nextLink": "Avanti"
20
+ "page.nextLink": "Avanti",
21
+ "404.text": "Pagina non trovata. Verifica l'URL o prova a utilizzare la barra di ricerca."
20
22
  }
@@ -3,6 +3,7 @@
3
3
  "search.label": "検索",
4
4
  "search.shortcutLabel": "(/を押して検索)",
5
5
  "search.cancelLabel": "キャンセル",
6
+ "search.devWarning": "検索はプロダクションビルドでのみ利用可能です。\nローカルでテストするには、サイトをビルドしてプレビューしてください。",
6
7
  "themeSelect.accessibleLabel": "テーマの選択",
7
8
  "themeSelect.dark": "ダーク",
8
9
  "themeSelect.light": "ライト",
@@ -16,5 +17,6 @@
16
17
  "page.editLink": "ページを編集",
17
18
  "page.lastUpdated": "最終更新日:",
18
19
  "page.previousLink": "前へ",
19
- "page.nextLink": "次へ"
20
+ "page.nextLink": "次へ",
21
+ "404.text": "ページが見つかりません。 URL を確認するか、検索バーを使用してみてください。"
20
22
  }
@@ -3,6 +3,7 @@
3
3
  "search.label": "Pesquisar",
4
4
  "search.shortcutLabel": "(Pressione / para Pesquisar)",
5
5
  "search.cancelLabel": "Cancelar",
6
+ "search.devWarning": "A pesquisa está disponível apenas em builds em produção. \nTente fazer a build e pré-visualize o site para testar localmente.",
6
7
  "themeSelect.accessibleLabel": "Selecionar tema",
7
8
  "themeSelect.dark": "Escuro",
8
9
  "themeSelect.light": "Claro",
@@ -16,5 +17,6 @@
16
17
  "page.editLink": "Editar página",
17
18
  "page.lastUpdated": "Última atualização:",
18
19
  "page.previousLink": "Anterior",
19
- "page.nextLink": "Próximo"
20
+ "page.nextLink": "Próximo",
21
+ "404.text": "Página não encontrada. Verifique o URL ou tente usar a barra de pesquisa."
20
22
  }
@@ -69,9 +69,9 @@ function groupFromAutogenerateConfig(
69
69
  const dirDocs = routes.filter(
70
70
  (doc) =>
71
71
  // Match against `foo.md` or `foo/index.md`.
72
- doc.slug === localeDir ||
72
+ stripExtension(doc.entry.id) === localeDir ||
73
73
  // Match against `foo/anything/else.md`.
74
- doc.slug.startsWith(localeDir + '/')
74
+ doc.entry.id.startsWith(localeDir + '/')
75
75
  );
76
76
  const tree = treeify(dirDocs, localeDir);
77
77
  return {
@@ -115,16 +115,18 @@ function makeLink(href: string, label: string, currentPathname: string): Link {
115
115
  }
116
116
 
117
117
  /** Get the segments leading to a page. */
118
- function getBreadcrumbs(slug: string, baseDir: string): string[] {
119
- // Index slugs will match `baseDir` and don’t include breadcrumbs.
120
- if (slug === baseDir) return [];
118
+ function getBreadcrumbs(path: string, baseDir: string): string[] {
119
+ // Strip extension from path.
120
+ const pathWithoutExt = stripExtension(path);
121
+ // Index paths will match `baseDir` and don’t include breadcrumbs.
122
+ if (pathWithoutExt === baseDir) return [];
121
123
  // Ensure base directory ends in a trailing slash.
122
124
  if (!baseDir.endsWith('/')) baseDir += '/';
123
- // Strip base directory from slug if present.
124
- const relativeSlug = slug.startsWith(baseDir)
125
- ? slug.replace(baseDir, '')
126
- : slug;
127
- let dir = dirname(relativeSlug);
125
+ // Strip base directory from path if present.
126
+ const relativePath = pathWithoutExt.startsWith(baseDir)
127
+ ? pathWithoutExt.replace(baseDir, '')
128
+ : pathWithoutExt;
129
+ let dir = dirname(relativePath);
128
130
  // Return no breadcrumbs for items in the root directory.
129
131
  if (dir === '.') return [];
130
132
  return dir.split('/');
@@ -134,7 +136,7 @@ function getBreadcrumbs(slug: string, baseDir: string): string[] {
134
136
  function treeify(routes: Route[], baseDir: string): Dir {
135
137
  const treeRoot: Dir = {};
136
138
  routes.forEach((doc) => {
137
- const breadcrumbs = getBreadcrumbs(doc.slug, baseDir);
139
+ const breadcrumbs = getBreadcrumbs(doc.entry.id, baseDir);
138
140
 
139
141
  // Walk down the route’s path to generate the tree.
140
142
  let currentDir = treeRoot;
@@ -236,3 +238,6 @@ export function getPrevNextLinks(sidebar: SidebarEntry[]): {
236
238
  const next = currentIndex > -1 ? entries[currentIndex + 1] : undefined;
237
239
  return { prev, next };
238
240
  }
241
+
242
+ /** Remove the extension from a path. */
243
+ const stripExtension = (path: string) => path.replace(/\.\w+$/, '');
@@ -38,7 +38,12 @@ export function useTranslations(locale: string | undefined) {
38
38
  builtinTranslations[lang],
39
39
  userTranslations[lang]
40
40
  );
41
- return (key: keyof typeof dictionary) => dictionary[key];
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;
42
47
  }
43
48
 
44
49
  /** Build a dictionary by layering preferred translation sources. */
@@ -95,14 +95,6 @@ const SidebarItemSchema = z.union([
95
95
  ]);
96
96
  export type SidebarItem = z.infer<typeof SidebarItemSchema>;
97
97
 
98
- const SidebarGroupSchema: z.ZodType<
99
- | z.output<typeof ManualSidebarGroupSchema>
100
- | z.output<typeof AutoSidebarGroupSchema>,
101
- z.ZodTypeDef,
102
- | z.input<typeof ManualSidebarGroupSchema>
103
- | z.input<typeof AutoSidebarGroupSchema>
104
- > = z.union([ManualSidebarGroupSchema, AutoSidebarGroupSchema]);
105
-
106
98
  const UserConfigSchema = z.object({
107
99
  /** Title for your website. Will be used in metadata and as browser tab title. */
108
100
  title: z
@@ -202,7 +194,7 @@ const UserConfigSchema = z.object({
202
194
  defaultLocale: z.string().optional(),
203
195
 
204
196
  /** Configure your site’s sidebar navigation items. */
205
- sidebar: SidebarGroupSchema.array().optional(),
197
+ sidebar: SidebarItemSchema.array().optional(),
206
198
 
207
199
  /**
208
200
  * Add extra tags to your site’s `<head>`.