@astrojs/starlight 0.0.6 → 0.0.8
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 +2 -1
- package/CHANGELOG.md +28 -0
- package/components/HeadSEO.astro +3 -2
- package/components/Header.astro +6 -12
- package/components/RightSidebar.astro +2 -2
- package/components/Select.astro +4 -0
- package/components/SiteTitle.astro +82 -0
- package/components/TableOfContents/MobileTableOfContents.astro +1 -1
- package/components/TableOfContents/starlight-toc.ts +24 -6
- package/index.astro +14 -0
- package/index.ts +6 -0
- package/package.json +1 -1
- package/schemas/logo.ts +28 -0
- package/utils/base.ts +14 -0
- package/utils/navigation.ts +3 -7
- package/utils/routing.ts +23 -5
- package/utils/slugs.ts +9 -11
- package/utils/user-config.ts +4 -0
- package/virtual.d.ts +8 -0
package/404.astro
CHANGED
|
@@ -17,6 +17,7 @@ import ThemeProvider from './components/ThemeProvider.astro';
|
|
|
17
17
|
|
|
18
18
|
// Important that this is the last import so it can override built-in styles.
|
|
19
19
|
import 'virtual:starlight/user-css';
|
|
20
|
+
import { withBase } from './utils/base';
|
|
20
21
|
|
|
21
22
|
const { lang = 'en', dir = 'ltr', locale } = config.defaultLocale || {};
|
|
22
23
|
---
|
|
@@ -37,7 +38,7 @@ const { lang = 'en', dir = 'ltr', locale } = config.defaultLocale || {};
|
|
|
37
38
|
<p>Houston, we have a problem.</p>
|
|
38
39
|
<p>
|
|
39
40
|
We couldn’t find that link. Check the address or <a
|
|
40
|
-
href={
|
|
41
|
+
href={withBase('/')}>head back home</a
|
|
41
42
|
>.
|
|
42
43
|
</p>
|
|
43
44
|
</MarkdownContent>
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @astrojs/starlight
|
|
2
2
|
|
|
3
|
+
## 0.0.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#62](https://github.com/withastro/starlight/pull/62) [`a91191e`](https://github.com/withastro/starlight/commit/a91191e8308ffa746a3eadeea61e39412f32f926) Thanks [@delucis](https://github.com/delucis)! - Make `base` support consistent, including when `trailingSlash: 'never'` is set.
|
|
8
|
+
|
|
9
|
+
- [#61](https://github.com/withastro/starlight/pull/61) [`608f34c`](https://github.com/withastro/starlight/commit/608f34cbbe485c39730f33828971397f9c8a3534) Thanks [@liruifengv](https://github.com/liruifengv)! - Fix toc headingsObserver rootMargin
|
|
10
|
+
|
|
11
|
+
- [#66](https://github.com/withastro/starlight/pull/66) [`9ca67d8`](https://github.com/withastro/starlight/commit/9ca67d8984f76c22e5411d7352aa8e0bd4514f42) Thanks [@Yan-Thomas](https://github.com/Yan-Thomas)! - Make site title width fit the content
|
|
12
|
+
|
|
13
|
+
- [#64](https://github.com/withastro/starlight/pull/64) [`4460e55`](https://github.com/withastro/starlight/commit/4460e55de210fd9a23a762fe76f5c32297d68d76) Thanks [@delucis](https://github.com/delucis)! - Fix table of contents intersection observer for all possible viewport sizes.
|
|
14
|
+
|
|
15
|
+
- [#67](https://github.com/withastro/starlight/pull/67) [`38c2c1f`](https://github.com/withastro/starlight/commit/38c2c1f1ed25d6efe6ab2637ca2d9fbcdafcd240) Thanks [@TheOtterlord](https://github.com/TheOtterlord)! - Fix background color on select component
|
|
16
|
+
|
|
17
|
+
- [#57](https://github.com/withastro/starlight/pull/57) [`5b6cccb`](https://github.com/withastro/starlight/commit/5b6cccb7f9ee5810c75fbbb45496e2a1d022f7dd) Thanks [@BryceRussell](https://github.com/BryceRussell)! - Update site title link to include locale
|
|
18
|
+
|
|
19
|
+
## 0.0.7
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- [#55](https://github.com/withastro/starlight/pull/55) [`8597b9c`](https://github.com/withastro/starlight/commit/8597b9c1002f8c5073d25ae5cacd4060ded2f8c8) Thanks [@delucis](https://github.com/delucis)! - Fix routing logic to handle `index.md` slug differences between docs collection root and nested directories.
|
|
24
|
+
|
|
25
|
+
- [#54](https://github.com/withastro/starlight/pull/54) [`db728d6`](https://github.com/withastro/starlight/commit/db728d61afa5cea060c66f746a4cc4ab3e1c3bcd) Thanks [@TheOtterlord](https://github.com/TheOtterlord)! - Add padding to scroll preventing headings being obscured by nav
|
|
26
|
+
|
|
27
|
+
- [#51](https://github.com/withastro/starlight/pull/51) [`3adbdbb`](https://github.com/withastro/starlight/commit/3adbdbbb71a4b3648984fa1028fa116d0aff9a7d) Thanks [@delucis](https://github.com/delucis)! - Support displaying a custom logo in the nav bar.
|
|
28
|
+
|
|
29
|
+
- [#51](https://github.com/withastro/starlight/pull/51) [`3adbdbb`](https://github.com/withastro/starlight/commit/3adbdbbb71a4b3648984fa1028fa116d0aff9a7d) Thanks [@delucis](https://github.com/delucis)! - All Starlight projects now use Astro’s experimental optimized asset support.
|
|
30
|
+
|
|
3
31
|
## 0.0.6
|
|
4
32
|
|
|
5
33
|
### Patch Changes
|
package/components/HeadSEO.astro
CHANGED
|
@@ -4,6 +4,7 @@ import config from 'virtual:starlight/user-config';
|
|
|
4
4
|
import type { HeadConfigSchema } from '../schemas/head';
|
|
5
5
|
import { createHead } from '../utils/head';
|
|
6
6
|
import { localizedUrl } from '../utils/localizedUrl';
|
|
7
|
+
import { withBase } from '../utils/base';
|
|
7
8
|
|
|
8
9
|
interface Props {
|
|
9
10
|
data: CollectionEntry<'docs'>['data'];
|
|
@@ -32,7 +33,7 @@ const headDefaults: z.input<ReturnType<typeof HeadConfigSchema>> = [
|
|
|
32
33
|
tag: 'link',
|
|
33
34
|
attrs: {
|
|
34
35
|
rel: 'shortcut icon',
|
|
35
|
-
href:
|
|
36
|
+
href: withBase('/favicon.svg'),
|
|
36
37
|
type: 'image/svg+xml',
|
|
37
38
|
},
|
|
38
39
|
},
|
|
@@ -80,7 +81,7 @@ if (Astro.site) {
|
|
|
80
81
|
tag: 'link',
|
|
81
82
|
attrs: {
|
|
82
83
|
rel: 'sitemap',
|
|
83
|
-
href:
|
|
84
|
+
href: withBase('/sitemap-index.xml'),
|
|
84
85
|
},
|
|
85
86
|
});
|
|
86
87
|
}
|
package/components/Header.astro
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
---
|
|
2
|
-
import config from 'virtual:starlight/user-config';
|
|
3
2
|
import LanguageSelect from './LanguageSelect.astro';
|
|
4
3
|
import Search from './Search.astro';
|
|
5
|
-
import
|
|
4
|
+
import SiteTitle from './SiteTitle.astro';
|
|
6
5
|
import SocialIcons from './SocialIcons.astro';
|
|
6
|
+
import ThemeSelect from './ThemeSelect.astro';
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
locale: string | undefined;
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
const { locale } = Astro.props
|
|
11
13
|
---
|
|
12
14
|
|
|
13
15
|
<div class="header">
|
|
14
|
-
<
|
|
16
|
+
<SiteTitle {locale} />
|
|
15
17
|
<Search />
|
|
16
18
|
<div class="hidden md:flex right-group">
|
|
17
19
|
<SocialIcons />
|
|
18
20
|
<ThemeSelect />
|
|
19
|
-
<LanguageSelect
|
|
21
|
+
<LanguageSelect {locale}/>
|
|
20
22
|
</div>
|
|
21
23
|
</div>
|
|
22
24
|
|
|
@@ -29,14 +31,6 @@ interface Props {
|
|
|
29
31
|
height: 100%;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
.site-title {
|
|
33
|
-
font-size: var(--sl-text-h4);
|
|
34
|
-
font-weight: 600;
|
|
35
|
-
color: var(--sl-color-text-accent);
|
|
36
|
-
text-decoration: none;
|
|
37
|
-
white-space: nowrap;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
34
|
.right-group {
|
|
41
35
|
gap: 1rem;
|
|
42
36
|
align-items: center;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { MarkdownHeading } from 'astro';
|
|
3
|
-
import type { CollectionEntry } from 'astro:content';
|
|
4
3
|
import config from 'virtual:starlight/user-config';
|
|
4
|
+
import type { StarlightDocsEntry } from '../utils/routing';
|
|
5
5
|
import EditLink from './EditLink.astro';
|
|
6
6
|
import RightSidebarPanel from './RightSidebarPanel.astro';
|
|
7
7
|
import TableOfContents from './TableOfContents.astro';
|
|
8
8
|
|
|
9
9
|
interface Props {
|
|
10
|
-
entry:
|
|
10
|
+
entry: StarlightDocsEntry;
|
|
11
11
|
headings: MarkdownHeading[];
|
|
12
12
|
}
|
|
13
13
|
|
package/components/Select.astro
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { logos } from 'virtual:starlight/user-images';
|
|
3
|
+
import config from 'virtual:starlight/user-config';
|
|
4
|
+
import { withBase } from '../utils/base';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (config.logo) {
|
|
11
|
+
let err: string | undefined;
|
|
12
|
+
if ('src' in config.logo) {
|
|
13
|
+
if (!logos.dark || !logos.light) {
|
|
14
|
+
err = `Could not resolve logo import for "${config.logo.src}" (logo.src)`;
|
|
15
|
+
}
|
|
16
|
+
} else {
|
|
17
|
+
if (!logos.dark) {
|
|
18
|
+
err = `Could not resolve logo import for "${config.logo.dark}" (logo.dark)`;
|
|
19
|
+
} else if (!logos.light) {
|
|
20
|
+
err = `Could not resolve logo import for "${config.logo.light}" (logo.light)`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (err) throw new Error(err);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const href = withBase(Astro.props.locale || '/');
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
<a {href} class="site-title flex">
|
|
30
|
+
{
|
|
31
|
+
config.logo && logos.dark && (
|
|
32
|
+
<>
|
|
33
|
+
<img
|
|
34
|
+
class:list={{ 'dark-only': !('src' in config.logo) }}
|
|
35
|
+
alt={config.logo.alt}
|
|
36
|
+
src={logos.dark.src}
|
|
37
|
+
width={logos.dark.width}
|
|
38
|
+
height={logos.dark.height}
|
|
39
|
+
/>
|
|
40
|
+
{/* Show light alternate if a user configure both light and dark logos. */}
|
|
41
|
+
{!('src' in config.logo) && (
|
|
42
|
+
<img
|
|
43
|
+
class="light-only"
|
|
44
|
+
alt={config.logo.alt}
|
|
45
|
+
src={logos.light?.src}
|
|
46
|
+
width={logos.light?.width}
|
|
47
|
+
height={logos.light?.height}
|
|
48
|
+
/>
|
|
49
|
+
)}
|
|
50
|
+
</>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
<span class:list={{ 'sr-only': config.logo?.replacesTitle }}>
|
|
54
|
+
{config.title}
|
|
55
|
+
</span>
|
|
56
|
+
</a>
|
|
57
|
+
|
|
58
|
+
<style>
|
|
59
|
+
.site-title {
|
|
60
|
+
justify-self: flex-start;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: var(--sl-nav-gap);
|
|
63
|
+
font-size: var(--sl-text-h4);
|
|
64
|
+
font-weight: 600;
|
|
65
|
+
color: var(--sl-color-text-accent);
|
|
66
|
+
text-decoration: none;
|
|
67
|
+
white-space: nowrap;
|
|
68
|
+
}
|
|
69
|
+
img {
|
|
70
|
+
height: calc(var(--sl-nav-height) - 2 * var(--sl-nav-pad-y));
|
|
71
|
+
width: auto;
|
|
72
|
+
max-width: 100%;
|
|
73
|
+
object-fit: contain;
|
|
74
|
+
object-position: 0 50%;
|
|
75
|
+
}
|
|
76
|
+
:global([data-theme='light']) .dark-only {
|
|
77
|
+
display: none;
|
|
78
|
+
}
|
|
79
|
+
:global([data-theme='dark']) .light-only {
|
|
80
|
+
display: none;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
@@ -121,7 +121,7 @@ const toc = generateToC(Astro.props.headings, config.tableOfContents);
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
constructor() {
|
|
124
|
-
super();
|
|
124
|
+
super({ smallViewport: true });
|
|
125
125
|
const details = this.querySelector('details');
|
|
126
126
|
if (!details) return;
|
|
127
127
|
const closeToC = () => {
|
|
@@ -12,7 +12,7 @@ export class StarlightTOC extends HTMLElement {
|
|
|
12
12
|
this._current = link;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
constructor() {
|
|
15
|
+
constructor({ smallViewport = false } = {}) {
|
|
16
16
|
super();
|
|
17
17
|
|
|
18
18
|
/** All the links in the table of contents. */
|
|
@@ -68,17 +68,35 @@ export class StarlightTOC extends HTMLElement {
|
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
const headingsObserver = new IntersectionObserver(setCurrent, {
|
|
72
|
-
rootMargin: '5% 0% -85%',
|
|
73
|
-
});
|
|
74
|
-
|
|
75
71
|
// Observe elements with an `id` (most likely headings) and their siblings.
|
|
76
72
|
// Also observe direct children of `.content` to include elements before
|
|
77
73
|
// the first heading.
|
|
78
74
|
const toObserve = document.querySelectorAll(
|
|
79
75
|
'main [id], main [id] ~ *, main .content > *'
|
|
80
76
|
);
|
|
81
|
-
|
|
77
|
+
/** Start intersections at nav height + 2rem padding. */
|
|
78
|
+
const top = (smallViewport ? 104 : 64) + 32;
|
|
79
|
+
/** End intersections 1.5rem later. */
|
|
80
|
+
const bottom = top + 24;
|
|
81
|
+
|
|
82
|
+
let observer: IntersectionObserver | undefined;
|
|
83
|
+
function observe() {
|
|
84
|
+
if (observer) observer.disconnect();
|
|
85
|
+
const height = document.documentElement.clientHeight;
|
|
86
|
+
const rootMargin = `-${top}px 0% ${bottom - height}px`;
|
|
87
|
+
observer = new IntersectionObserver(setCurrent, { rootMargin });
|
|
88
|
+
toObserve.forEach((h) => observer!.observe(h));
|
|
89
|
+
}
|
|
90
|
+
observe();
|
|
91
|
+
|
|
92
|
+
const onIdle = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
|
|
93
|
+
let timeout: NodeJS.Timeout;
|
|
94
|
+
window.addEventListener('resize', () => {
|
|
95
|
+
// Disable intersection observer while window is resizing.
|
|
96
|
+
if (observer) observer.disconnect();
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
timeout = setTimeout(() => onIdle(observe), 200);
|
|
99
|
+
});
|
|
82
100
|
}
|
|
83
101
|
}
|
|
84
102
|
|
package/index.astro
CHANGED
|
@@ -47,6 +47,20 @@ const prevNextLinks = getPrevNextLinks(sidebar);
|
|
|
47
47
|
<html lang={lang} dir={dir}>
|
|
48
48
|
<head>
|
|
49
49
|
<HeadSEO data={entry.data} lang={lang} />
|
|
50
|
+
<style>
|
|
51
|
+
/* Add scroll padding to ensure anchor headings aren't obscured by nav */
|
|
52
|
+
html {
|
|
53
|
+
/* Additional padding is needed to account for the mobile TOC */
|
|
54
|
+
scroll-padding-top: calc(
|
|
55
|
+
1.5rem + var(--sl-nav-height) + var(--sl-mobile-toc-height)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
@media (min-width: 72em) {
|
|
59
|
+
html {
|
|
60
|
+
scroll-padding-top: calc(1.5rem + var(--sl-nav-height));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
50
64
|
</head>
|
|
51
65
|
<body>
|
|
52
66
|
<ThemeProvider />
|
package/index.ts
CHANGED
|
@@ -45,6 +45,7 @@ export default function StarlightIntegration(
|
|
|
45
45
|
? {}
|
|
46
46
|
: { theme: 'css-variables' },
|
|
47
47
|
},
|
|
48
|
+
experimental: { assets: true },
|
|
48
49
|
};
|
|
49
50
|
updateConfig(newConfig);
|
|
50
51
|
},
|
|
@@ -84,6 +85,11 @@ function vitePluginStarlightUserConfig(
|
|
|
84
85
|
'virtual:starlight/user-css': opts.customCss
|
|
85
86
|
.map((id) => `import "${id}";`)
|
|
86
87
|
.join(''),
|
|
88
|
+
'virtual:starlight/user-images': opts.logo
|
|
89
|
+
? 'src' in opts.logo
|
|
90
|
+
? `import src from "${opts.logo.src}"; export const logos = { dark: src, light: src };`
|
|
91
|
+
: `import dark from "${opts.logo.dark}"; import light from "${opts.logo.light}"; export const logos = { dark, light };`
|
|
92
|
+
: 'export const logos = {};',
|
|
87
93
|
};
|
|
88
94
|
const resolutionMap = Object.fromEntries(
|
|
89
95
|
(Object.keys(modules) as (keyof typeof modules)[]).map((key) => [
|
package/package.json
CHANGED
package/schemas/logo.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'astro/zod';
|
|
2
|
+
|
|
3
|
+
export const LogoConfigSchema = () =>
|
|
4
|
+
z
|
|
5
|
+
.union([
|
|
6
|
+
z.object({
|
|
7
|
+
/** Source of the image file to use. */
|
|
8
|
+
src: z.string(),
|
|
9
|
+
/** Alternative text description of the logo. */
|
|
10
|
+
alt: z.string().default(''),
|
|
11
|
+
/** Set to `true` to hide the site title text and only show the logo. */
|
|
12
|
+
replacesTitle: z.boolean().default(false),
|
|
13
|
+
}),
|
|
14
|
+
z.object({
|
|
15
|
+
/** Source of the image file to use in dark mode. */
|
|
16
|
+
dark: z.string(),
|
|
17
|
+
/** Source of the image file to use in light mode. */
|
|
18
|
+
light: z.string(),
|
|
19
|
+
/** Alternative text description of the logo. */
|
|
20
|
+
alt: z.string().default(''),
|
|
21
|
+
/** Set to `true` to hide the site title text and only show the logo. */
|
|
22
|
+
replacesTitle: z.boolean().default(false),
|
|
23
|
+
}),
|
|
24
|
+
])
|
|
25
|
+
.optional();
|
|
26
|
+
|
|
27
|
+
export type LogoUserConfig = z.input<ReturnType<typeof LogoConfigSchema>>;
|
|
28
|
+
export type LogoConfig = z.output<ReturnType<typeof LogoConfigSchema>>;
|
package/utils/base.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const base = stripTrailingSlash(import.meta.env.BASE_URL);
|
|
2
|
+
|
|
3
|
+
/** Get the a root-relative URL path with the site’s `base` prefixed. */
|
|
4
|
+
export function withBase(path: string) {
|
|
5
|
+
path = stripLeadingSlash(stripTrailingSlash(path));
|
|
6
|
+
return path ? base + '/' + path + '/' : base + '/';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function stripLeadingSlash(path: string) {
|
|
10
|
+
return path.replace(/^\//, '');
|
|
11
|
+
}
|
|
12
|
+
function stripTrailingSlash(path: string) {
|
|
13
|
+
return path.replace(/\/$/, '');
|
|
14
|
+
}
|
package/utils/navigation.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { basename, dirname } from 'node:path';
|
|
2
2
|
import config from 'virtual:starlight/user-config';
|
|
3
|
-
import {
|
|
3
|
+
import { withBase } from './base';
|
|
4
4
|
import { Route, getLocaleRoutes, routes } from './routing';
|
|
5
|
+
import { slugToPathname } from './slugs';
|
|
5
6
|
import type {
|
|
6
7
|
AutoSidebarGroup,
|
|
7
8
|
SidebarItem,
|
|
@@ -100,12 +101,7 @@ function linkFromConfig(
|
|
|
100
101
|
|
|
101
102
|
/** Create a link entry. */
|
|
102
103
|
function makeLink(href: string, label: string, currentPathname: string): Link {
|
|
103
|
-
if (!isAbsolute(href))
|
|
104
|
-
href = ensureLeadingAndTrailingSlashes(href);
|
|
105
|
-
/** Base URL with trailing `/` stripped. */
|
|
106
|
-
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
|
|
107
|
-
if (base) href = base + href;
|
|
108
|
-
}
|
|
104
|
+
if (!isAbsolute(href)) href = withBase(href);
|
|
109
105
|
const isCurrent = href === currentPathname;
|
|
110
106
|
return { type: 'link', label, href, isCurrent };
|
|
111
107
|
}
|
package/utils/routing.ts
CHANGED
|
@@ -8,8 +8,12 @@ import {
|
|
|
8
8
|
slugToParam,
|
|
9
9
|
} from './slugs';
|
|
10
10
|
|
|
11
|
+
export type StarlightDocsEntry = Omit<CollectionEntry<'docs'>, 'slug'> & {
|
|
12
|
+
slug: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
11
15
|
export interface Route extends LocaleData {
|
|
12
|
-
entry:
|
|
16
|
+
entry: StarlightDocsEntry;
|
|
13
17
|
entryMeta: LocaleData;
|
|
14
18
|
slug: string;
|
|
15
19
|
isFallback?: true;
|
|
@@ -21,8 +25,17 @@ interface Path extends GetStaticPathsItem {
|
|
|
21
25
|
props: Route;
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Astro is inconsistent in its `index.md` slug generation. In most cases,
|
|
30
|
+
* `index` is stripped, but in the root of a collection, we get a slug of `index`.
|
|
31
|
+
* We map that to an empty string for consistent behaviour.
|
|
32
|
+
*/
|
|
33
|
+
const normalizeIndexSlug = (slug: string) => (slug === 'index' ? '' : slug);
|
|
34
|
+
|
|
24
35
|
/** All entries in the docs content collection. */
|
|
25
|
-
const docs = await getCollection('docs')
|
|
36
|
+
const docs: StarlightDocsEntry[] = (await getCollection('docs')).map(
|
|
37
|
+
({ slug, ...entry }) => ({ ...entry, slug: normalizeIndexSlug(slug) })
|
|
38
|
+
);
|
|
26
39
|
|
|
27
40
|
function getRoutes(): Route[] {
|
|
28
41
|
const routes: Route[] = docs.map((entry) => ({
|
|
@@ -87,7 +100,7 @@ export function getLocaleRoutes(locale: string | undefined): Route[] {
|
|
|
87
100
|
* Get all entries in the docs content collection for a specific locale.
|
|
88
101
|
* A locale of `undefined` is treated as the “root” locale, if configured.
|
|
89
102
|
*/
|
|
90
|
-
function getLocaleDocs(locale: string | undefined):
|
|
103
|
+
function getLocaleDocs(locale: string | undefined): StarlightDocsEntry[] {
|
|
91
104
|
return filterByLocale(docs, locale);
|
|
92
105
|
}
|
|
93
106
|
|
|
@@ -98,11 +111,16 @@ function filterByLocale<T extends { slug: string }>(
|
|
|
98
111
|
): T[] {
|
|
99
112
|
if (config.locales) {
|
|
100
113
|
if (locale && locale in config.locales) {
|
|
101
|
-
return items.filter(
|
|
114
|
+
return items.filter(
|
|
115
|
+
(i) => i.slug === locale || i.slug.startsWith(locale + '/')
|
|
116
|
+
);
|
|
102
117
|
} else if (config.locales.root) {
|
|
103
118
|
const langKeys = Object.keys(config.locales).filter((k) => k !== 'root');
|
|
119
|
+
const isLangIndex = new RegExp(`^(${langKeys.join('|')})$`);
|
|
104
120
|
const isLangDir = new RegExp(`^(${langKeys.join('|')})/`);
|
|
105
|
-
return items.filter(
|
|
121
|
+
return items.filter(
|
|
122
|
+
(i) => !isLangIndex.test(i.slug) && !isLangDir.test(i.slug)
|
|
123
|
+
);
|
|
106
124
|
}
|
|
107
125
|
}
|
|
108
126
|
return items;
|
package/utils/slugs.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { CollectionEntry } from 'astro:content';
|
|
2
1
|
import config from 'virtual:starlight/user-config';
|
|
3
2
|
|
|
4
3
|
export interface LocaleData {
|
|
@@ -16,9 +15,7 @@ export interface LocaleData {
|
|
|
16
15
|
* Root locale slugs will return `undefined`.
|
|
17
16
|
* @param slug A collection entry slug
|
|
18
17
|
*/
|
|
19
|
-
function slugToLocale(
|
|
20
|
-
slug: CollectionEntry<'docs'>['slug']
|
|
21
|
-
): string | undefined {
|
|
18
|
+
function slugToLocale(slug: string): string | undefined {
|
|
22
19
|
const locales = Object.keys(config.locales || {});
|
|
23
20
|
const baseSegment = slug.split('/')[0];
|
|
24
21
|
if (baseSegment && locales.includes(baseSegment)) return baseSegment;
|
|
@@ -26,9 +23,7 @@ function slugToLocale(
|
|
|
26
23
|
}
|
|
27
24
|
|
|
28
25
|
/** Get locale information for a given slug. */
|
|
29
|
-
export function slugToLocaleData(
|
|
30
|
-
slug: CollectionEntry<'docs'>['slug']
|
|
31
|
-
): LocaleData {
|
|
26
|
+
export function slugToLocaleData(slug: string): LocaleData {
|
|
32
27
|
const locale = slugToLocale(slug);
|
|
33
28
|
return { dir: localeToDir(locale), lang: localeToLang(locale), locale };
|
|
34
29
|
}
|
|
@@ -56,7 +51,7 @@ function localeToDir(locale: string | undefined): 'ltr' | 'rtl' {
|
|
|
56
51
|
}
|
|
57
52
|
|
|
58
53
|
export function slugToParam(slug: string): string | undefined {
|
|
59
|
-
return slug === 'index'
|
|
54
|
+
return slug === 'index' || slug === ''
|
|
60
55
|
? undefined
|
|
61
56
|
: slug.endsWith('/index')
|
|
62
57
|
? slug.replace('/index', '')
|
|
@@ -79,13 +74,16 @@ export function slugToPathname(slug: string): string {
|
|
|
79
74
|
* localizedSlug('en/home', undefined) // => 'home'
|
|
80
75
|
*/
|
|
81
76
|
export function localizedSlug(
|
|
82
|
-
slug:
|
|
77
|
+
slug: string,
|
|
83
78
|
locale: string | undefined
|
|
84
79
|
): string {
|
|
85
80
|
const slugLocale = slugToLocale(slug);
|
|
86
81
|
if (slugLocale === locale) return slug;
|
|
82
|
+
locale = locale || '';
|
|
87
83
|
if (slugLocale) {
|
|
88
|
-
return slug
|
|
84
|
+
return slug
|
|
85
|
+
.replace(slugLocale + '/', locale ? locale + '/' : '')
|
|
86
|
+
.replace(/\/$/, '');
|
|
89
87
|
}
|
|
90
|
-
return locale + '/' + slug;
|
|
88
|
+
return slug ? locale + '/' + slug : locale;
|
|
91
89
|
}
|
package/utils/user-config.ts
CHANGED
|
@@ -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 { HeadConfigSchema } from '../schemas/head';
|
|
4
|
+
import { LogoConfigSchema } from '../schemas/logo';
|
|
4
5
|
|
|
5
6
|
const LocaleSchema = z.object({
|
|
6
7
|
/** The label for this language to show in UI, e.g. `"English"`, `"العربية"`, or `"简体中文"`. */
|
|
@@ -101,6 +102,9 @@ const UserConfigSchema = z.object({
|
|
|
101
102
|
'Description metadata for your website. Can be used in page metadata.'
|
|
102
103
|
),
|
|
103
104
|
|
|
105
|
+
/** Set a logo image to show in the navigation bar alongside or instead of the site title. */
|
|
106
|
+
logo: LogoConfigSchema(),
|
|
107
|
+
|
|
104
108
|
/** Optional details about the social media accounts for this site. */
|
|
105
109
|
social: z
|
|
106
110
|
.object({
|
package/virtual.d.ts
CHANGED
|
@@ -7,3 +7,11 @@ declare module 'virtual:starlight/project-context' {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
declare module 'virtual:starlight/user-css' {}
|
|
10
|
+
|
|
11
|
+
declare module 'virtual:starlight/user-images' {
|
|
12
|
+
type ImageMetadata = import('astro').ImageMetadata;
|
|
13
|
+
export const logos: {
|
|
14
|
+
dark?: ImageMetadata;
|
|
15
|
+
light?: ImageMetadata;
|
|
16
|
+
};
|
|
17
|
+
}
|