@astrojs/starlight 0.32.6 → 0.33.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/CHANGELOG.md +79 -0
- package/components/Head.astro +1 -96
- package/components/MobileMenuToggle.astro +10 -1
- package/components/SocialIcons.astro +4 -6
- package/components/StarlightPage.astro +1 -1
- package/package.json +1 -1
- package/schemas/hero.ts +3 -5
- package/schemas/icon.ts +7 -0
- package/schemas/social.ts +18 -107
- package/style/markdown.css +23 -1
- package/user-components/Badge.astro +6 -1
- package/user-components/Icon.astro +16 -9
- package/utils/canonical.ts +19 -0
- package/utils/head.ts +87 -2
- package/utils/routing/data.ts +8 -4
- package/utils/routing/types.ts +3 -0
- package/utils/starlight-page.ts +25 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,84 @@
|
|
|
1
1
|
# @astrojs/starlight
|
|
2
2
|
|
|
3
|
+
## 0.33.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#3026](https://github.com/withastro/starlight/pull/3026) [`82deb84`](https://github.com/withastro/starlight/commit/82deb847418aedb9c01e05bb9de4b9bd10a1a885) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes a potential list styling issue if the last element of a list item is a `<script>` tag.
|
|
8
|
+
|
|
9
|
+
⚠️ **BREAKING CHANGE:**
|
|
10
|
+
|
|
11
|
+
This release drops official support for Chromium-based browsers prior to version 105 (released 30 August 2022) and Firefox-based browsers prior to version 121 (released 19 December 2023). You can find a list of currently supported browsers and their versions using this [browserslist query](https://browsersl.ist/#q=%3E+0.5%25%2C+not+dead%2C+Chrome+%3E%3D+105%2C+Edge+%3E%3D+105%2C+Firefox+%3E%3D+121%2C+Safari+%3E%3D+15.4%2C+iOS+%3E%3D+15.4%2C+not+op_mini+all).
|
|
12
|
+
|
|
13
|
+
With this release, Starlight-generated sites will still work fine on those older browsers except for this small detail in list item styling, but future releases may introduce further breaking changes for impacted browsers, including in patch releases.
|
|
14
|
+
|
|
15
|
+
- [#3025](https://github.com/withastro/starlight/pull/3025) [`f87e9ac`](https://github.com/withastro/starlight/commit/f87e9acbf5090a31858c1cde568cc798140f1366) Thanks [@delucis](https://github.com/delucis)! - Makes `social` configuration more flexible.
|
|
16
|
+
|
|
17
|
+
⚠️ **BREAKING CHANGE:** The `social` configuration option has changed syntax. You will need to update this in `astro.config.mjs` when upgrading.
|
|
18
|
+
|
|
19
|
+
Previously, a limited set of platforms were supported using a shorthand syntax with labels built in to Starlight. While convenient, this approach was less flexible and required dedicated code for each social platform added.
|
|
20
|
+
|
|
21
|
+
Now, you must specify the icon and label for each social link explicitly and you can use any of [Starlight’s built-in icons](https://starlight.astro.build/reference/icons/) for social links.
|
|
22
|
+
|
|
23
|
+
The following example shows updating the old `social` syntax to the new:
|
|
24
|
+
|
|
25
|
+
```diff
|
|
26
|
+
- social: {
|
|
27
|
+
- github: 'https://github.com/withastro/starlight',
|
|
28
|
+
- discord: 'https://astro.build/chat',
|
|
29
|
+
- },
|
|
30
|
+
+ social: [
|
|
31
|
+
+ { icon: 'github', label: 'GitHub', href: 'https://github.com/withastro/starlight' },
|
|
32
|
+
+ { icon: 'discord', label: 'Discord', href: 'https://astro.build/chat' },
|
|
33
|
+
+ ],
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- [#2927](https://github.com/withastro/starlight/pull/2927) [`c46904c`](https://github.com/withastro/starlight/commit/c46904c4a16cf1c7f4f895e42cb164474b2301b3) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Adds the [`head`](https://starlight.astro.build/reference/route-data/#head) route data property which contains an array of all tags to include in the `<head>` of the current page.
|
|
37
|
+
|
|
38
|
+
Previously, the [`<Head>`](https://starlight.astro.build/reference/overrides/#head-1) component was responsible for generating a list of tags to include in the `<head>` of the current page and rendering them.
|
|
39
|
+
This data is now available as `Astro.locals.starlightRoute.head` instead and can be modified using [route data middleware](https://starlight.astro.build/guides/route-data/#customizing-route-data).
|
|
40
|
+
The `<Head>` component now only renders the tags provided in `Astro.locals.starlightRoute.head`.
|
|
41
|
+
|
|
42
|
+
- [#2924](https://github.com/withastro/starlight/pull/2924) [`6a56d1b`](https://github.com/withastro/starlight/commit/6a56d1b80d9d67e63e930177cf085a25864e1952) Thanks [@HiDeoo](https://github.com/HiDeoo)! - ⚠️ **BREAKING CHANGE:** Ensures that the `<Badge>` and `<Icon>` components no longer render with a trailing space.
|
|
43
|
+
|
|
44
|
+
In Astro, components that include styles render with a trailing space which can prevent some use cases from working as expected, e.g. when using such components inlined with text. This change ensures that the `<Badge>` and `<Icon>` components no longer render with a trailing space.
|
|
45
|
+
|
|
46
|
+
If you were previously relying on that implementation detail, you may need to update your code to account for this change. For example, considering the following code:
|
|
47
|
+
|
|
48
|
+
```mdx
|
|
49
|
+
<Badge text="New" />
|
|
50
|
+
Feature
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The rendered text would previously include a space between the badge and the text due to the trailing space automatically added by the component:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
New Feature
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Such code will now render the badge and text without a space:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
NewFeature
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
To fix this, you can add a space between the badge and the text:
|
|
66
|
+
|
|
67
|
+
```diff
|
|
68
|
+
- <Badge text="New" />Feature
|
|
69
|
+
+ <Badge text="New" /> Feature
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- [#2727](https://github.com/withastro/starlight/pull/2727) [`7c8fa30`](https://github.com/withastro/starlight/commit/7c8fa30f0ac2459c83b71a8a7b705b16dcf98d6f) Thanks [@techfg](https://github.com/techfg)! - Updates mobile menu toggle styles to display a close icon while the menu is open
|
|
73
|
+
|
|
74
|
+
### Patch Changes
|
|
75
|
+
|
|
76
|
+
- [#2927](https://github.com/withastro/starlight/pull/2927) [`c46904c`](https://github.com/withastro/starlight/commit/c46904c4a16cf1c7f4f895e42cb164474b2301b3) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes an issue where overriding the [canonical URL](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel#canonical) of a page using the [`head` configuration option](https://starlight.astro.build/reference/configuration/#head) or [`head` frontmatter field](https://starlight.astro.build/reference/frontmatter/#head) would strip any other `<link>` tags from the `<head>`.
|
|
77
|
+
|
|
78
|
+
- [#2927](https://github.com/withastro/starlight/pull/2927) [`c46904c`](https://github.com/withastro/starlight/commit/c46904c4a16cf1c7f4f895e42cb164474b2301b3) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes an issue where generated [canonical URLs](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel#canonical) would include a trailing slash when using the [`trailingSlash` Astro option](https://docs.astro.build/en/reference/configuration-reference/#trailingslash) is set to `'never'`.
|
|
79
|
+
|
|
80
|
+
- [#3025](https://github.com/withastro/starlight/pull/3025) [`f87e9ac`](https://github.com/withastro/starlight/commit/f87e9acbf5090a31858c1cde568cc798140f1366) Thanks [@delucis](https://github.com/delucis)! - Fixes Starlight’s autogenerated `<meta name="twitter:site">` tags when a Twitter link is set in `social` config. Previously these incorrectly rendered `content="/username"` and now correctly render `content="@username"`.
|
|
81
|
+
|
|
3
82
|
## 0.32.6
|
|
4
83
|
|
|
5
84
|
### Patch Changes
|
package/components/Head.astro
CHANGED
|
@@ -1,100 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
import context from 'virtual:starlight/project-context';
|
|
4
|
-
import config from 'virtual:starlight/user-config';
|
|
5
|
-
import { version } from '../package.json';
|
|
6
|
-
import type { HeadConfigSchema } from '../schemas/head';
|
|
7
|
-
import { fileWithBase } from '../utils/base';
|
|
8
|
-
import { createHead } from '../utils/head';
|
|
9
|
-
import { localizedUrl } from '../utils/localizedUrl';
|
|
10
|
-
|
|
11
|
-
const { entry, lang, siteTitle } = Astro.locals.starlightRoute;
|
|
12
|
-
const { data } = entry;
|
|
13
|
-
|
|
14
|
-
const canonical = Astro.site ? new URL(Astro.url.pathname, Astro.site) : undefined;
|
|
15
|
-
const description = data.description || config.description;
|
|
16
|
-
|
|
17
|
-
const headDefaults: z.input<ReturnType<typeof HeadConfigSchema>> = [
|
|
18
|
-
{ tag: 'meta', attrs: { charset: 'utf-8' } },
|
|
19
|
-
{
|
|
20
|
-
tag: 'meta',
|
|
21
|
-
attrs: { name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
|
22
|
-
},
|
|
23
|
-
{ tag: 'title', content: `${data.title} ${config.titleDelimiter} ${siteTitle}` },
|
|
24
|
-
{ tag: 'link', attrs: { rel: 'canonical', href: canonical?.href } },
|
|
25
|
-
{ tag: 'meta', attrs: { name: 'generator', content: Astro.generator } },
|
|
26
|
-
{
|
|
27
|
-
tag: 'meta',
|
|
28
|
-
attrs: { name: 'generator', content: `Starlight v${version}` },
|
|
29
|
-
},
|
|
30
|
-
// Favicon
|
|
31
|
-
{
|
|
32
|
-
tag: 'link',
|
|
33
|
-
attrs: {
|
|
34
|
-
rel: 'shortcut icon',
|
|
35
|
-
href: fileWithBase(config.favicon.href),
|
|
36
|
-
type: config.favicon.type,
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
// OpenGraph Tags
|
|
40
|
-
{ tag: 'meta', attrs: { property: 'og:title', content: data.title } },
|
|
41
|
-
{ tag: 'meta', attrs: { property: 'og:type', content: 'article' } },
|
|
42
|
-
{ tag: 'meta', attrs: { property: 'og:url', content: canonical?.href } },
|
|
43
|
-
{ tag: 'meta', attrs: { property: 'og:locale', content: lang } },
|
|
44
|
-
{ tag: 'meta', attrs: { property: 'og:description', content: description } },
|
|
45
|
-
{ tag: 'meta', attrs: { property: 'og:site_name', content: siteTitle } },
|
|
46
|
-
// Twitter Tags
|
|
47
|
-
{
|
|
48
|
-
tag: 'meta',
|
|
49
|
-
attrs: { name: 'twitter:card', content: 'summary_large_image' },
|
|
50
|
-
},
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
if (description)
|
|
54
|
-
headDefaults.push({
|
|
55
|
-
tag: 'meta',
|
|
56
|
-
attrs: { name: 'description', content: description },
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Link to language alternates.
|
|
60
|
-
if (canonical && config.isMultilingual) {
|
|
61
|
-
for (const locale in config.locales) {
|
|
62
|
-
const localeOpts = config.locales[locale];
|
|
63
|
-
if (!localeOpts) continue;
|
|
64
|
-
headDefaults.push({
|
|
65
|
-
tag: 'link',
|
|
66
|
-
attrs: {
|
|
67
|
-
rel: 'alternate',
|
|
68
|
-
hreflang: localeOpts.lang,
|
|
69
|
-
href: localizedUrl(canonical, locale, context.trailingSlash).href,
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Link to sitemap, but only when `site` is set.
|
|
76
|
-
if (Astro.site) {
|
|
77
|
-
headDefaults.push({
|
|
78
|
-
tag: 'link',
|
|
79
|
-
attrs: {
|
|
80
|
-
rel: 'sitemap',
|
|
81
|
-
href: fileWithBase('/sitemap-index.xml'),
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Link to Twitter account if set in Starlight config.
|
|
87
|
-
if (config.social?.twitter) {
|
|
88
|
-
headDefaults.push({
|
|
89
|
-
tag: 'meta',
|
|
90
|
-
attrs: {
|
|
91
|
-
name: 'twitter:site',
|
|
92
|
-
content: new URL(config.social.twitter.url).pathname,
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const head = createHead(headDefaults, config.head, data.head);
|
|
2
|
+
const { head } = Astro.locals.starlightRoute;
|
|
98
3
|
---
|
|
99
4
|
|
|
100
5
|
{head.map(({ tag: Tag, attrs, content }) => <Tag {...attrs} set:html={content} />)}
|
|
@@ -9,7 +9,8 @@ import Icon from '../user-components/Icon.astro';
|
|
|
9
9
|
aria-controls="starlight__sidebar"
|
|
10
10
|
class="sl-flex md:sl-hidden"
|
|
11
11
|
>
|
|
12
|
-
<Icon name="bars" />
|
|
12
|
+
<Icon name="bars" class="open-menu" />
|
|
13
|
+
<Icon name="close" class="close-menu" />
|
|
13
14
|
</button>
|
|
14
15
|
</starlight-menu-button>
|
|
15
16
|
|
|
@@ -71,6 +72,14 @@ import Icon from '../user-components/Icon.astro';
|
|
|
71
72
|
box-shadow: none;
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
[aria-expanded='true'] button .open-menu {
|
|
76
|
+
display: none;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
:not([aria-expanded='true']) button .close-menu {
|
|
80
|
+
display: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
74
83
|
:global([data-theme='light']) button {
|
|
75
84
|
background-color: var(--sl-color-black);
|
|
76
85
|
color: var(--sl-color-white);
|
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
import config from 'virtual:starlight/user-config';
|
|
3
3
|
import Icon from '../user-components/Icon.astro';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
type SocialConfig = NonNullable<NonNullable<typeof config.social>[Platform]>;
|
|
7
|
-
const links = Object.entries(config.social || {}) as [Platform, SocialConfig][];
|
|
5
|
+
const links = config.social || [];
|
|
8
6
|
---
|
|
9
7
|
|
|
10
8
|
{
|
|
11
9
|
links.length > 0 && (
|
|
12
10
|
<>
|
|
13
|
-
{links.map((
|
|
14
|
-
<a href
|
|
11
|
+
{links.map(({ label, href, icon }) => (
|
|
12
|
+
<a {href} rel="me" class="sl-flex">
|
|
15
13
|
<span class="sr-only">{label}</span>
|
|
16
|
-
<Icon name={
|
|
14
|
+
<Icon name={icon} />
|
|
17
15
|
</a>
|
|
18
16
|
))}
|
|
19
17
|
</>
|
|
@@ -10,7 +10,7 @@ export type StarlightPageProps = Props;
|
|
|
10
10
|
|
|
11
11
|
await attachRouteDataAndRunMiddleware(
|
|
12
12
|
Astro,
|
|
13
|
-
await generateStarlightPageRouteData({ props: Astro.props,
|
|
13
|
+
await generateStarlightPageRouteData({ props: Astro.props, context: Astro })
|
|
14
14
|
);
|
|
15
15
|
---
|
|
16
16
|
|
package/package.json
CHANGED
package/schemas/hero.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { z } from 'astro/zod';
|
|
2
2
|
import type { SchemaContext } from 'astro:content';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
const iconNames = Object.keys(Icons) as [StarlightIcon, ...StarlightIcon[]];
|
|
3
|
+
import { IconSchema } from './icon';
|
|
6
4
|
|
|
7
5
|
export const HeroSchema = ({ image }: SchemaContext) =>
|
|
8
6
|
z.object({
|
|
@@ -55,9 +53,9 @@ export const HeroSchema = ({ image }: SchemaContext) =>
|
|
|
55
53
|
* Can be an inline `<svg>` or the name of one of Starlight’s built-in icons.
|
|
56
54
|
*/
|
|
57
55
|
icon: z
|
|
58
|
-
.union([
|
|
56
|
+
.union([IconSchema(), z.string().startsWith('<svg')])
|
|
59
57
|
.transform((icon) => {
|
|
60
|
-
const parsedIcon =
|
|
58
|
+
const parsedIcon = IconSchema().safeParse(icon);
|
|
61
59
|
return parsedIcon.success
|
|
62
60
|
? ({ type: 'icon', name: parsedIcon.data } as const)
|
|
63
61
|
: ({ type: 'raw', html: icon } as const);
|
package/schemas/icon.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { z } from 'astro/zod';
|
|
2
|
+
import { Icons, type StarlightIcon } from '../components/Icons';
|
|
3
|
+
|
|
4
|
+
const iconNames = Object.keys(Icons) as [StarlightIcon, ...StarlightIcon[]];
|
|
5
|
+
|
|
6
|
+
/** String that matches the name of one of Starlight’s built-in icons. */
|
|
7
|
+
export const IconSchema = () => z.enum(iconNames);
|
package/schemas/social.ts
CHANGED
|
@@ -1,111 +1,22 @@
|
|
|
1
1
|
import { z } from 'astro/zod';
|
|
2
|
+
import { IconSchema } from './icon';
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
'gitlab',
|
|
8
|
-
'bitbucket',
|
|
9
|
-
'discord',
|
|
10
|
-
'gitter',
|
|
11
|
-
'codeberg',
|
|
12
|
-
'codePen',
|
|
13
|
-
'youtube',
|
|
14
|
-
'threads',
|
|
15
|
-
'linkedin',
|
|
16
|
-
'twitch',
|
|
17
|
-
'azureDevOps',
|
|
18
|
-
'microsoftTeams',
|
|
19
|
-
'instagram',
|
|
20
|
-
'stackOverflow',
|
|
21
|
-
'x.com',
|
|
22
|
-
'telegram',
|
|
23
|
-
'rss',
|
|
24
|
-
'facebook',
|
|
25
|
-
'email',
|
|
26
|
-
'reddit',
|
|
27
|
-
'patreon',
|
|
28
|
-
'signal',
|
|
29
|
-
'slack',
|
|
30
|
-
'matrix',
|
|
31
|
-
'openCollective',
|
|
32
|
-
'hackerOne',
|
|
33
|
-
'blueSky',
|
|
34
|
-
'discourse',
|
|
35
|
-
'zulip',
|
|
36
|
-
'pinterest',
|
|
37
|
-
'tiktok',
|
|
38
|
-
'nostr',
|
|
39
|
-
'backstage',
|
|
40
|
-
'farcaster',
|
|
41
|
-
'confluence',
|
|
42
|
-
'jira',
|
|
43
|
-
'storybook',
|
|
44
|
-
'npm',
|
|
45
|
-
'sourcehut',
|
|
46
|
-
'substack',
|
|
47
|
-
] as const;
|
|
4
|
+
const LinksSchema = z
|
|
5
|
+
.object({ icon: IconSchema(), label: z.string().min(1), href: z.string() })
|
|
6
|
+
.array()
|
|
7
|
+
.optional();
|
|
48
8
|
|
|
49
9
|
export const SocialLinksSchema = () =>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
github: 'GitHub',
|
|
64
|
-
gitlab: 'GitLab',
|
|
65
|
-
bitbucket: 'Bitbucket',
|
|
66
|
-
discord: 'Discord',
|
|
67
|
-
gitter: 'Gitter',
|
|
68
|
-
twitter: 'Twitter',
|
|
69
|
-
mastodon: 'Mastodon',
|
|
70
|
-
codeberg: 'Codeberg',
|
|
71
|
-
codePen: 'CodePen',
|
|
72
|
-
youtube: 'YouTube',
|
|
73
|
-
threads: 'Threads',
|
|
74
|
-
linkedin: 'LinkedIn',
|
|
75
|
-
twitch: 'Twitch',
|
|
76
|
-
azureDevOps: 'Azure DevOps',
|
|
77
|
-
microsoftTeams: 'Microsoft Teams',
|
|
78
|
-
instagram: 'Instagram',
|
|
79
|
-
stackOverflow: 'Stack Overflow',
|
|
80
|
-
'x.com': 'X',
|
|
81
|
-
telegram: 'Telegram',
|
|
82
|
-
rss: 'RSS',
|
|
83
|
-
facebook: 'Facebook',
|
|
84
|
-
email: 'Email',
|
|
85
|
-
reddit: 'Reddit',
|
|
86
|
-
patreon: 'Patreon',
|
|
87
|
-
signal: 'Signal',
|
|
88
|
-
slack: 'Slack',
|
|
89
|
-
matrix: 'Matrix',
|
|
90
|
-
openCollective: 'Open Collective',
|
|
91
|
-
hackerOne: 'Hacker One',
|
|
92
|
-
blueSky: 'BlueSky',
|
|
93
|
-
discourse: 'Discourse',
|
|
94
|
-
zulip: 'Zulip',
|
|
95
|
-
pinterest: 'Pinterest',
|
|
96
|
-
tiktok: 'TikTok',
|
|
97
|
-
nostr: 'Nostr',
|
|
98
|
-
backstage: 'Backstage',
|
|
99
|
-
farcaster: 'Farcaster',
|
|
100
|
-
confluence: 'Confluence',
|
|
101
|
-
jira: 'Jira',
|
|
102
|
-
storybook: 'Storybook',
|
|
103
|
-
npm: 'npm',
|
|
104
|
-
sourcehut: 'SourceHut',
|
|
105
|
-
substack: 'Substack',
|
|
106
|
-
}[key];
|
|
107
|
-
labelledLinks[key] = { label, url };
|
|
108
|
-
}
|
|
109
|
-
return labelledLinks;
|
|
110
|
-
})
|
|
111
|
-
.optional();
|
|
10
|
+
// Add a more specific error message to help people migrate from the old object syntax.
|
|
11
|
+
// TODO: remove once most people have updated to v0.33 or higher (e.g. when releasing Starlight v1)
|
|
12
|
+
z.preprocess((value, ctx) => {
|
|
13
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
14
|
+
ctx.addIssue({
|
|
15
|
+
code: z.ZodIssueCode.custom,
|
|
16
|
+
message:
|
|
17
|
+
'Starlight v0.33.0 changed the `social` configuration syntax. Please specify an array of link items instead of an object.\n' +
|
|
18
|
+
'See the Starlight changelog for details: https://github.com/withastro/starlight/blob/main/packages/starlight/CHANGELOG.md#0330\n',
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}, LinksSchema) as unknown as typeof LinksSchema;
|
package/style/markdown.css
CHANGED
|
@@ -24,7 +24,29 @@
|
|
|
24
24
|
|
|
25
25
|
.sl-markdown-content
|
|
26
26
|
li
|
|
27
|
-
> :
|
|
27
|
+
> :is(
|
|
28
|
+
:last-child:not(
|
|
29
|
+
li,
|
|
30
|
+
ul,
|
|
31
|
+
ol,
|
|
32
|
+
a,
|
|
33
|
+
strong,
|
|
34
|
+
em,
|
|
35
|
+
del,
|
|
36
|
+
span,
|
|
37
|
+
input,
|
|
38
|
+
code,
|
|
39
|
+
br,
|
|
40
|
+
script,
|
|
41
|
+
:where(.not-content *)
|
|
42
|
+
),
|
|
43
|
+
/**
|
|
44
|
+
* For list items ending with 1 or multiple script elements (`:has(~ script:last-child)`), we
|
|
45
|
+
* need to style the last non-script element (`:not(script)`) that doesn't have a subsequent
|
|
46
|
+
* sibling that is not a script (`:not(:has(~ :not(script)))`).
|
|
47
|
+
*/
|
|
48
|
+
:not(script):has(~ script:last-child):not(:has(~ :not(script)))
|
|
49
|
+
) {
|
|
28
50
|
margin-bottom: 1.25rem;
|
|
29
51
|
}
|
|
30
52
|
|
|
@@ -16,9 +16,14 @@ const {
|
|
|
16
16
|
Astro.props,
|
|
17
17
|
'Invalid prop passed to the `<Badge/>` component.'
|
|
18
18
|
);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The fragment around the element is used as a workaround to avoid a trailing whitespace in the output.
|
|
22
|
+
* @see https://github.com/withastro/compiler/issues/1003
|
|
23
|
+
*/
|
|
19
24
|
---
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
<><span class:list={['sl-badge', variant, size, customClass]} {...attrs}>{text}</span></>
|
|
22
27
|
|
|
23
28
|
<style>
|
|
24
29
|
:global(:root) {
|
|
@@ -11,17 +11,24 @@ interface Props {
|
|
|
11
11
|
|
|
12
12
|
const { name, label, size = '1em', color } = Astro.props;
|
|
13
13
|
const a11yAttrs = label ? ({ 'aria-label': label } as const) : ({ 'aria-hidden': 'true' } as const);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The fragment around the element is used as a workaround to avoid a trailing whitespace in the output.
|
|
17
|
+
* @see https://github.com/withastro/compiler/issues/1003
|
|
18
|
+
*/
|
|
14
19
|
---
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
<>
|
|
22
|
+
<svg
|
|
23
|
+
{...a11yAttrs}
|
|
24
|
+
class={Astro.props.class}
|
|
25
|
+
width="16"
|
|
26
|
+
height="16"
|
|
27
|
+
viewBox="0 0 24 24"
|
|
28
|
+
fill="currentColor"
|
|
29
|
+
set:html={Icons[name]}
|
|
30
|
+
/>
|
|
31
|
+
</>
|
|
25
32
|
|
|
26
33
|
<style define:vars={{ 'sl-icon-color': color, 'sl-icon-size': size }}>
|
|
27
34
|
svg {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AstroConfig } from 'astro';
|
|
2
|
+
import { ensureTrailingSlash, stripTrailingSlash } from './path';
|
|
3
|
+
|
|
4
|
+
export interface FormatCanonicalOptions {
|
|
5
|
+
format: AstroConfig['build']['format'];
|
|
6
|
+
trailingSlash: AstroConfig['trailingSlash'];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const canonicalTrailingSlashStrategies = {
|
|
10
|
+
always: ensureTrailingSlash,
|
|
11
|
+
never: stripTrailingSlash,
|
|
12
|
+
ignore: ensureTrailingSlash,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Format a canonical link based on the project config. */
|
|
16
|
+
export function formatCanonical(href: string, opts: FormatCanonicalOptions) {
|
|
17
|
+
if (opts.format === 'file') return href;
|
|
18
|
+
return canonicalTrailingSlashStrategies[opts.trailingSlash](href);
|
|
19
|
+
}
|
package/utils/head.ts
CHANGED
|
@@ -1,9 +1,94 @@
|
|
|
1
|
+
import config from 'virtual:starlight/user-config';
|
|
2
|
+
import project from 'virtual:starlight/project-context';
|
|
3
|
+
import { version } from '../package.json';
|
|
1
4
|
import { type HeadConfig, HeadConfigSchema, type HeadUserConfig } from '../schemas/head';
|
|
5
|
+
import type { PageProps, RouteDataContext } from './routing/data';
|
|
6
|
+
import { fileWithBase } from './base';
|
|
7
|
+
import { formatCanonical } from './canonical';
|
|
2
8
|
|
|
3
9
|
const HeadSchema = HeadConfigSchema();
|
|
4
10
|
|
|
11
|
+
/** Get the head for the current page. */
|
|
12
|
+
export function getHead(
|
|
13
|
+
{ entry, lang }: PageProps,
|
|
14
|
+
context: RouteDataContext,
|
|
15
|
+
siteTitle: string
|
|
16
|
+
): HeadConfig {
|
|
17
|
+
const { data } = entry;
|
|
18
|
+
|
|
19
|
+
const canonical = context.site ? new URL(context.url.pathname, context.site) : undefined;
|
|
20
|
+
const canonicalHref = canonical?.href
|
|
21
|
+
? formatCanonical(canonical.href, {
|
|
22
|
+
format: project.build.format,
|
|
23
|
+
trailingSlash: project.trailingSlash,
|
|
24
|
+
})
|
|
25
|
+
: undefined;
|
|
26
|
+
const description = data.description || config.description;
|
|
27
|
+
|
|
28
|
+
const headDefaults: HeadUserConfig = [
|
|
29
|
+
{ tag: 'meta', attrs: { charset: 'utf-8' } },
|
|
30
|
+
{
|
|
31
|
+
tag: 'meta',
|
|
32
|
+
attrs: { name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
|
33
|
+
},
|
|
34
|
+
{ tag: 'title', content: `${data.title} ${config.titleDelimiter} ${siteTitle}` },
|
|
35
|
+
{ tag: 'link', attrs: { rel: 'canonical', href: canonicalHref } },
|
|
36
|
+
{ tag: 'meta', attrs: { name: 'generator', content: context.generator } },
|
|
37
|
+
{
|
|
38
|
+
tag: 'meta',
|
|
39
|
+
attrs: { name: 'generator', content: `Starlight v${version}` },
|
|
40
|
+
},
|
|
41
|
+
// Favicon
|
|
42
|
+
{
|
|
43
|
+
tag: 'link',
|
|
44
|
+
attrs: {
|
|
45
|
+
rel: 'shortcut icon',
|
|
46
|
+
href: fileWithBase(config.favicon.href),
|
|
47
|
+
type: config.favicon.type,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
// OpenGraph Tags
|
|
51
|
+
{ tag: 'meta', attrs: { property: 'og:title', content: data.title } },
|
|
52
|
+
{ tag: 'meta', attrs: { property: 'og:type', content: 'article' } },
|
|
53
|
+
{ tag: 'meta', attrs: { property: 'og:url', content: canonicalHref } },
|
|
54
|
+
{ tag: 'meta', attrs: { property: 'og:locale', content: lang } },
|
|
55
|
+
{ tag: 'meta', attrs: { property: 'og:description', content: description } },
|
|
56
|
+
{ tag: 'meta', attrs: { property: 'og:site_name', content: siteTitle } },
|
|
57
|
+
// Twitter Tags
|
|
58
|
+
{
|
|
59
|
+
tag: 'meta',
|
|
60
|
+
attrs: { name: 'twitter:card', content: 'summary_large_image' },
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
// Link to sitemap, but only when `site` is set.
|
|
65
|
+
if (context.site) {
|
|
66
|
+
headDefaults.push({
|
|
67
|
+
tag: 'link',
|
|
68
|
+
attrs: {
|
|
69
|
+
rel: 'sitemap',
|
|
70
|
+
href: fileWithBase('/sitemap-index.xml'),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Link to Twitter account if set in Starlight config.
|
|
76
|
+
const twitterLink = config.social?.find(({ icon }) => icon === 'twitter' || icon === 'x.com');
|
|
77
|
+
if (twitterLink) {
|
|
78
|
+
headDefaults.push({
|
|
79
|
+
tag: 'meta',
|
|
80
|
+
attrs: {
|
|
81
|
+
name: 'twitter:site',
|
|
82
|
+
content: new URL(twitterLink.href).pathname.replace('/', '@'),
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return createHead(headDefaults, config.head, data.head);
|
|
88
|
+
}
|
|
89
|
+
|
|
5
90
|
/** Create a fully parsed, merged, and sorted head entry array from multiple sources. */
|
|
6
|
-
|
|
91
|
+
function createHead(defaults: HeadUserConfig, ...heads: HeadConfig[]) {
|
|
7
92
|
let head = HeadSchema.parse(defaults);
|
|
8
93
|
for (const next of heads) {
|
|
9
94
|
head = mergeHead(head, next);
|
|
@@ -26,7 +111,7 @@ function hasTag(head: HeadConfig, entry: HeadConfig[number]): boolean {
|
|
|
26
111
|
case 'meta':
|
|
27
112
|
return hasOneOf(head, entry, ['name', 'property', 'http-equiv']);
|
|
28
113
|
case 'link':
|
|
29
|
-
return head.some(({ attrs }) => attrs.rel === 'canonical');
|
|
114
|
+
return head.some(({ attrs }) => entry.attrs.rel === 'canonical' && attrs.rel === 'canonical');
|
|
30
115
|
default:
|
|
31
116
|
return false;
|
|
32
117
|
}
|
package/utils/routing/data.ts
CHANGED
|
@@ -17,29 +17,32 @@ import { useTranslations } from '../translations';
|
|
|
17
17
|
import { BuiltInDefaultLocale } from '../i18n';
|
|
18
18
|
import { getEntry, render } from 'astro:content';
|
|
19
19
|
import { getCollectionPathFromRoot } from '../collection';
|
|
20
|
+
import { getHead } from '../head';
|
|
20
21
|
|
|
21
22
|
export interface PageProps extends Route {
|
|
22
23
|
headings: MarkdownHeading[];
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
export type RouteDataContext = Pick<APIContext, 'generator' | 'site' | 'url'>;
|
|
27
|
+
|
|
25
28
|
export async function useRouteData(context: APIContext): Promise<StarlightRouteData> {
|
|
26
29
|
const route =
|
|
27
30
|
('slug' in context.params && getRouteBySlugParam(context.params.slug)) ||
|
|
28
31
|
(await get404Route(context.locals));
|
|
29
32
|
const { Content, headings } = await render(route.entry);
|
|
30
|
-
const routeData = generateRouteData({ props: { ...route, headings },
|
|
33
|
+
const routeData = generateRouteData({ props: { ...route, headings }, context });
|
|
31
34
|
return { ...routeData, Content };
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
export function generateRouteData({
|
|
35
38
|
props,
|
|
36
|
-
|
|
39
|
+
context,
|
|
37
40
|
}: {
|
|
38
41
|
props: PageProps;
|
|
39
|
-
|
|
42
|
+
context: RouteDataContext;
|
|
40
43
|
}): StarlightRouteData {
|
|
41
44
|
const { entry, locale, lang } = props;
|
|
42
|
-
const sidebar = getSidebar(url.pathname, locale);
|
|
45
|
+
const sidebar = getSidebar(context.url.pathname, locale);
|
|
43
46
|
const siteTitle = getSiteTitle(lang);
|
|
44
47
|
return {
|
|
45
48
|
...props,
|
|
@@ -51,6 +54,7 @@ export function generateRouteData({
|
|
|
51
54
|
toc: getToC(props),
|
|
52
55
|
lastUpdated: getLastUpdated(props),
|
|
53
56
|
editUrl: getEditUrl(props),
|
|
57
|
+
head: getHead(props, context, siteTitle),
|
|
54
58
|
};
|
|
55
59
|
}
|
|
56
60
|
|
package/utils/routing/types.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { CollectionEntry, RenderResult } from 'astro:content';
|
|
|
3
3
|
import type { TocItem } from '../generateToC';
|
|
4
4
|
import type { LinkHTMLAttributes } from '../../schemas/sidebar';
|
|
5
5
|
import type { Badge } from '../../schemas/badge';
|
|
6
|
+
import type { HeadConfig } from '../../schemas/head';
|
|
6
7
|
|
|
7
8
|
export interface LocaleData {
|
|
8
9
|
/** Writing direction. */
|
|
@@ -93,4 +94,6 @@ export interface StarlightRouteData extends Route {
|
|
|
93
94
|
editUrl: URL | undefined;
|
|
94
95
|
/** An Astro component to render the current page’s content if this route is a Markdown page. */
|
|
95
96
|
Content?: RenderResult['Content'];
|
|
97
|
+
/** Array of tags to include in the `<head>` of the current page. */
|
|
98
|
+
head: HeadConfig;
|
|
96
99
|
}
|
package/utils/starlight-page.ts
CHANGED
|
@@ -5,7 +5,13 @@ import config from 'virtual:starlight/user-config';
|
|
|
5
5
|
import { getCollectionPathFromRoot } from './collection';
|
|
6
6
|
import { parseWithFriendlyErrors, parseAsyncWithFriendlyErrors } from './error-map';
|
|
7
7
|
import { stripLeadingAndTrailingSlashes } from './path';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
getSiteTitle,
|
|
10
|
+
getSiteTitleHref,
|
|
11
|
+
getToC,
|
|
12
|
+
type PageProps,
|
|
13
|
+
type RouteDataContext,
|
|
14
|
+
} from './routing/data';
|
|
9
15
|
import type { StarlightDocsEntry, StarlightRouteData } from './routing/types';
|
|
10
16
|
import { slugToLocaleData, urlToSlug } from './slugs';
|
|
11
17
|
import { getPrevNextLinks, getSidebar, getSidebarFromConfig } from './navigation';
|
|
@@ -13,6 +19,7 @@ import { docsSchema } from '../schema';
|
|
|
13
19
|
import type { Prettify, RemoveIndexSignature } from './types';
|
|
14
20
|
import { SidebarItemSchema } from '../schemas/sidebar';
|
|
15
21
|
import type { StarlightConfig, StarlightUserConfig } from './user-config';
|
|
22
|
+
import { getHead } from './head';
|
|
16
23
|
|
|
17
24
|
/**
|
|
18
25
|
* The frontmatter schema for Starlight pages derived from the default schema for Starlight’s
|
|
@@ -100,12 +107,13 @@ type StarlightPageDocsEntry = Omit<StarlightDocsEntry, 'id' | 'render'> & {
|
|
|
100
107
|
|
|
101
108
|
export async function generateStarlightPageRouteData({
|
|
102
109
|
props,
|
|
103
|
-
|
|
110
|
+
context,
|
|
104
111
|
}: {
|
|
105
112
|
props: StarlightPageProps;
|
|
106
|
-
|
|
113
|
+
context: RouteDataContext;
|
|
107
114
|
}): Promise<StarlightRouteData> {
|
|
108
115
|
const { frontmatter, ...routeProps } = props;
|
|
116
|
+
const { url } = context;
|
|
109
117
|
const slug = urlToSlug(url);
|
|
110
118
|
const pageFrontmatter = await getStarlightPageFrontmatter(frontmatter);
|
|
111
119
|
const id = project.legacyCollections ? `${stripLeadingAndTrailingSlashes(slug)}.md` : slug;
|
|
@@ -137,6 +145,17 @@ export async function generateStarlightPageRouteData({
|
|
|
137
145
|
const editUrl = pageFrontmatter.editUrl ? new URL(pageFrontmatter.editUrl) : undefined;
|
|
138
146
|
const lastUpdated =
|
|
139
147
|
pageFrontmatter.lastUpdated instanceof Date ? pageFrontmatter.lastUpdated : undefined;
|
|
148
|
+
const pageProps: PageProps = {
|
|
149
|
+
...routeProps,
|
|
150
|
+
...localeData,
|
|
151
|
+
entry,
|
|
152
|
+
entryMeta,
|
|
153
|
+
headings,
|
|
154
|
+
id,
|
|
155
|
+
locale: localeData.locale,
|
|
156
|
+
slug,
|
|
157
|
+
};
|
|
158
|
+
const siteTitle = getSiteTitle(localeData.lang);
|
|
140
159
|
const routeData: StarlightRouteData = {
|
|
141
160
|
...routeProps,
|
|
142
161
|
...localeData,
|
|
@@ -145,23 +164,15 @@ export async function generateStarlightPageRouteData({
|
|
|
145
164
|
entry,
|
|
146
165
|
entryMeta,
|
|
147
166
|
hasSidebar: props.hasSidebar ?? entry.data.template !== 'splash',
|
|
167
|
+
head: getHead(pageProps, context, siteTitle),
|
|
148
168
|
headings,
|
|
149
169
|
lastUpdated,
|
|
150
170
|
pagination: getPrevNextLinks(sidebar, config.pagination, entry.data),
|
|
151
171
|
sidebar,
|
|
152
|
-
siteTitle
|
|
172
|
+
siteTitle,
|
|
153
173
|
siteTitleHref: getSiteTitleHref(localeData.locale),
|
|
154
174
|
slug,
|
|
155
|
-
toc: getToC(
|
|
156
|
-
...routeProps,
|
|
157
|
-
...localeData,
|
|
158
|
-
entry,
|
|
159
|
-
entryMeta,
|
|
160
|
-
headings,
|
|
161
|
-
id,
|
|
162
|
-
locale: localeData.locale,
|
|
163
|
-
slug,
|
|
164
|
-
}),
|
|
175
|
+
toc: getToC(pageProps),
|
|
165
176
|
};
|
|
166
177
|
return routeData;
|
|
167
178
|
}
|