@astrojs/starlight 0.18.0 → 0.19.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 +22 -0
- package/components/CallToAction.astro +4 -1
- package/components/StarlightPage.astro +13 -0
- package/components.ts +1 -0
- package/integrations/asides.ts +47 -2
- package/integrations/virtual-user-config.ts +5 -0
- package/package.json +7 -2
- package/schemas/hero.ts +2 -0
- package/translations/ko.json +11 -11
- package/user-components/Aside.astro +38 -0
- package/utils/error-map.ts +4 -0
- package/utils/plugins.ts +1 -5
- package/utils/route-data.ts +2 -2
- package/utils/slugs.ts +18 -0
- package/utils/starlight-page.ts +209 -0
- package/virtual.d.ts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @astrojs/starlight
|
|
2
2
|
|
|
3
|
+
## 0.19.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1485](https://github.com/withastro/starlight/pull/1485) [`2cb3578`](https://github.com/withastro/starlight/commit/2cb35782dace67c7c418a31005419fa95493b3d3) Thanks [@timokoessler](https://github.com/timokoessler)! - Add support for setting html attributes of hero action links
|
|
8
|
+
|
|
9
|
+
- [#1175](https://github.com/withastro/starlight/pull/1175) [`dd11b95`](https://github.com/withastro/starlight/commit/dd11b9538abdf4b5ba2ef70e07c0edda03e95add) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Adds a new `<StarlightPage>` component to use the Starlight layout in custom pages.
|
|
10
|
+
|
|
11
|
+
To learn more about this new feature, check out the new [“Using Starlight’s design in custom pages” guide](https://starlight.astro.build/guides/pages/#using-starlights-design-in-custom-pages).
|
|
12
|
+
|
|
13
|
+
- [#1499](https://github.com/withastro/starlight/pull/1499) [`97bf523`](https://github.com/withastro/starlight/commit/97bf523923fb9678c12f58fcdbe36757f0e56ceb) Thanks [@delucis](https://github.com/delucis)! - Adds a new `<Aside>` component
|
|
14
|
+
|
|
15
|
+
The new component is in addition to the existing custom Markdown syntax.
|
|
16
|
+
|
|
17
|
+
## 0.18.1
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- [#1487](https://github.com/withastro/starlight/pull/1487) [`6a72bda`](https://github.com/withastro/starlight/commit/6a72bda8c5569e2eda68fdf258ae9b1dc8b320d6) Thanks [@NavyStack](https://github.com/NavyStack)! - Improves Korean UI translations
|
|
22
|
+
|
|
23
|
+
- [#1489](https://github.com/withastro/starlight/pull/1489) [`b0d36de`](https://github.com/withastro/starlight/commit/b0d36de3398d4895603a787b612b1f0747defbdc) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes a potential text rendering issue with text containing colons.
|
|
24
|
+
|
|
3
25
|
## 0.18.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types';
|
|
2
3
|
import Icon from '../user-components/Icon.astro';
|
|
3
4
|
import type { Icons } from './Icons';
|
|
4
5
|
|
|
@@ -6,12 +7,14 @@ interface Props {
|
|
|
6
7
|
variant: 'primary' | 'secondary' | 'minimal';
|
|
7
8
|
link: string;
|
|
8
9
|
icon?: undefined | { type: 'icon'; name: keyof typeof Icons } | { type: 'raw'; html: string };
|
|
10
|
+
attrs?: Omit<HTMLAttributes<'a'>, 'href'>;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
const { link, variant, icon } = Astro.props;
|
|
14
|
+
const { class: customClass, ...attrs } = Astro.props.attrs || {};
|
|
12
15
|
---
|
|
13
16
|
|
|
14
|
-
<a class:list={['sl-flex action', variant]} href={link}>
|
|
17
|
+
<a class:list={['sl-flex action', variant, customClass]} href={link} {...attrs}>
|
|
15
18
|
<slot />
|
|
16
19
|
{icon?.type === 'icon' && <Icon name={icon.name} size="1.5rem" />}
|
|
17
20
|
{icon?.type === 'raw' && <Fragment set:html={icon.html} />}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
import {
|
|
3
|
+
generateStarlightPageRouteData,
|
|
4
|
+
type StarlightPageProps as Props,
|
|
5
|
+
} from '../utils/starlight-page';
|
|
6
|
+
import Page from './Page.astro';
|
|
7
|
+
|
|
8
|
+
export type StarlightPageProps = Props;
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<Page {...await generateStarlightPageRouteData({ props: Astro.props, url: Astro.url })}>
|
|
12
|
+
<slot />
|
|
13
|
+
</Page>
|
package/components.ts
CHANGED
package/integrations/asides.ts
CHANGED
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import type { AstroConfig, AstroUserConfig } from 'astro';
|
|
4
4
|
import { h as _h, s as _s, type Properties } from 'hastscript';
|
|
5
|
-
import type { Paragraph as P, Root } from 'mdast';
|
|
5
|
+
import type { Node, Paragraph as P, Parent, Root } from 'mdast';
|
|
6
|
+
import {
|
|
7
|
+
type Directives,
|
|
8
|
+
directiveToMarkdown,
|
|
9
|
+
type TextDirective,
|
|
10
|
+
type LeafDirective,
|
|
11
|
+
} from 'mdast-util-directive';
|
|
12
|
+
import { toMarkdown } from 'mdast-util-to-markdown';
|
|
6
13
|
import remarkDirective from 'remark-directive';
|
|
7
14
|
import type { Plugin, Transformer } from 'unified';
|
|
8
15
|
import { remove } from 'unist-util-remove';
|
|
@@ -37,6 +44,40 @@ function s(el: string, attrs: Properties = {}, children: any[] = []): P {
|
|
|
37
44
|
};
|
|
38
45
|
}
|
|
39
46
|
|
|
47
|
+
/** Checks if a node is a directive. */
|
|
48
|
+
function isNodeDirective(node: Node): node is Directives {
|
|
49
|
+
return (
|
|
50
|
+
node.type === 'textDirective' ||
|
|
51
|
+
node.type === 'leafDirective' ||
|
|
52
|
+
node.type === 'containerDirective'
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Transforms back directives not handled by Starlight to avoid breaking user content.
|
|
58
|
+
* For example, a user might write `x:y` in the middle of a sentence, where `:y` would be
|
|
59
|
+
* identified as a text directive, which are not used by Starlight, and we definitely want that
|
|
60
|
+
* text to be rendered verbatim in the output.
|
|
61
|
+
*/
|
|
62
|
+
function transformUnhandledDirective(
|
|
63
|
+
node: TextDirective | LeafDirective,
|
|
64
|
+
index: number,
|
|
65
|
+
parent: Parent
|
|
66
|
+
) {
|
|
67
|
+
const textNode = {
|
|
68
|
+
type: 'text',
|
|
69
|
+
value: toMarkdown(node, { extensions: [directiveToMarkdown()] }),
|
|
70
|
+
} as const;
|
|
71
|
+
if (node.type === 'textDirective') {
|
|
72
|
+
parent.children[index] = textNode;
|
|
73
|
+
} else {
|
|
74
|
+
parent.children[index] = {
|
|
75
|
+
type: 'paragraph',
|
|
76
|
+
children: [textNode],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
40
81
|
/**
|
|
41
82
|
* remark plugin that converts blocks delimited with `:::` into styled
|
|
42
83
|
* asides (a.k.a. “callouts”, “admonitions”, etc.). Depends on the
|
|
@@ -102,7 +143,11 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
|
|
|
102
143
|
const locale = pathToLocale(file.history[0], options);
|
|
103
144
|
const t = options.useTranslations(locale);
|
|
104
145
|
visit(tree, (node, index, parent) => {
|
|
105
|
-
if (!parent || index === undefined || node
|
|
146
|
+
if (!parent || index === undefined || !isNodeDirective(node)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (node.type === 'textDirective' || node.type === 'leafDirective') {
|
|
150
|
+
transformUnhandledDirective(node, index, parent);
|
|
106
151
|
return;
|
|
107
152
|
}
|
|
108
153
|
const variant = node.name;
|
|
@@ -48,6 +48,11 @@ export function vitePluginStarlightUserConfig(
|
|
|
48
48
|
opts.logo.light
|
|
49
49
|
)}; export const logos = { dark, light };`
|
|
50
50
|
: 'export const logos = {};',
|
|
51
|
+
'virtual:starlight/collection-config': `let userCollections;
|
|
52
|
+
try {
|
|
53
|
+
userCollections = (await import('/src/content/config.ts')).collections;
|
|
54
|
+
} catch {}
|
|
55
|
+
export const collections = userCollections;`,
|
|
51
56
|
...virtualComponentModules,
|
|
52
57
|
} satisfies Record<string, string>;
|
|
53
58
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrojs/starlight",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Build beautiful, high-performance documentation websites with Astro",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -98,6 +98,10 @@
|
|
|
98
98
|
"types": "./components/Page.astro.tsx",
|
|
99
99
|
"import": "./components/Page.astro"
|
|
100
100
|
},
|
|
101
|
+
"./components/StarlightPage.astro": {
|
|
102
|
+
"types": "./components/StarlightPage.astro.tsx",
|
|
103
|
+
"import": "./components/StarlightPage.astro"
|
|
104
|
+
},
|
|
101
105
|
"./components/Footer.astro": {
|
|
102
106
|
"types": "./components/Footer.astro.tsx",
|
|
103
107
|
"import": "./components/Footer.astro"
|
|
@@ -169,7 +173,7 @@
|
|
|
169
173
|
"@astrojs/markdown-remark": "^4.2.1",
|
|
170
174
|
"@types/node": "^18.16.19",
|
|
171
175
|
"@vitest/coverage-v8": "^1.2.2",
|
|
172
|
-
"astro": "^4.3.
|
|
176
|
+
"astro": "^4.3.5",
|
|
173
177
|
"vitest": "^1.2.2"
|
|
174
178
|
},
|
|
175
179
|
"dependencies": {
|
|
@@ -183,6 +187,7 @@
|
|
|
183
187
|
"hast-util-select": "^6.0.2",
|
|
184
188
|
"hastscript": "^8.0.0",
|
|
185
189
|
"mdast-util-directive": "^3.0.0",
|
|
190
|
+
"mdast-util-to-markdown": "^2.1.0",
|
|
186
191
|
"pagefind": "^1.0.3",
|
|
187
192
|
"rehype": "^13.0.1",
|
|
188
193
|
"remark-directive": "^3.0.0",
|
package/schemas/hero.ts
CHANGED
|
@@ -64,6 +64,8 @@ export const HeroSchema = ({ image }: SchemaContext) =>
|
|
|
64
64
|
: ({ type: 'raw', html: icon } as const);
|
|
65
65
|
})
|
|
66
66
|
.optional(),
|
|
67
|
+
/** HTML attributes to add to the link */
|
|
68
|
+
attrs: z.record(z.union([z.string(), z.number(), z.boolean()])).optional(),
|
|
67
69
|
})
|
|
68
70
|
.array()
|
|
69
71
|
.default([]),
|
package/translations/ko.json
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
{
|
|
2
|
-
"skipLink.label": "컨텐츠로
|
|
2
|
+
"skipLink.label": "컨텐츠로 건너뛰기",
|
|
3
3
|
"search.label": "검색",
|
|
4
|
-
"search.shortcutLabel": "(
|
|
4
|
+
"search.shortcutLabel": "(검색하려면 / 를 누르세요)",
|
|
5
5
|
"search.cancelLabel": "취소",
|
|
6
|
-
"search.devWarning": "검색 기능은
|
|
6
|
+
"search.devWarning": "검색 기능은 운영 환경에서만 사용할 수 있습니다. \n로컬에서 테스트하려면 사이트를 빌드하고 미리보기를 해보세요.",
|
|
7
7
|
"themeSelect.accessibleLabel": "테마 선택",
|
|
8
|
-
"themeSelect.dark": "
|
|
9
|
-
"themeSelect.light": "
|
|
8
|
+
"themeSelect.dark": "다크",
|
|
9
|
+
"themeSelect.light": "라이트",
|
|
10
10
|
"themeSelect.auto": "자동",
|
|
11
11
|
"languageSelect.accessibleLabel": "언어 선택",
|
|
12
12
|
"menuButton.accessibleLabel": "메뉴",
|
|
13
13
|
"sidebarNav.accessibleLabel": "메인",
|
|
14
|
-
"tableOfContents.onThisPage": "
|
|
14
|
+
"tableOfContents.onThisPage": "이 페이지에서는",
|
|
15
15
|
"tableOfContents.overview": "개요",
|
|
16
|
-
"i18n.untranslatedContent": "이
|
|
16
|
+
"i18n.untranslatedContent": "이 내용은 아직 번역본이 없습니다.",
|
|
17
17
|
"page.editLink": "페이지 수정",
|
|
18
|
-
"page.lastUpdated": "
|
|
19
|
-
"page.previousLink": "이전
|
|
20
|
-
"page.nextLink": "다음
|
|
21
|
-
"404.text": "페이지를 찾을 수 없습니다. URL을 확인하거나
|
|
18
|
+
"page.lastUpdated": "마지막 업데이트:",
|
|
19
|
+
"page.previousLink": "이전",
|
|
20
|
+
"page.nextLink": "다음",
|
|
21
|
+
"404.text": "페이지를 찾을 수 없습니다. URL을 확인하거나 검색 막대를 사용해 보세요.",
|
|
22
22
|
"aside.note": "노트",
|
|
23
23
|
"aside.tip": "팁",
|
|
24
24
|
"aside.caution": "주의",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { AstroError } from 'astro/errors';
|
|
3
|
+
import { slugToLocaleData, urlToSlug } from '../utils/slugs';
|
|
4
|
+
import { useTranslations } from '../utils/translations';
|
|
5
|
+
import Icon from './Icon.astro';
|
|
6
|
+
|
|
7
|
+
const asideVariants = ['note', 'tip', 'caution', 'danger'] as const;
|
|
8
|
+
const icons = { note: 'information', tip: 'rocket', caution: 'warning', danger: 'error' } as const;
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
type?: (typeof asideVariants)[number];
|
|
12
|
+
title?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let { type = 'note', title } = Astro.props;
|
|
16
|
+
|
|
17
|
+
if (!asideVariants.includes(type)) {
|
|
18
|
+
throw new AstroError(
|
|
19
|
+
'Invalid `type` prop passed to the `<Aside>` component.\n',
|
|
20
|
+
`Received: ${JSON.stringify(type)}\n` +
|
|
21
|
+
`Expected one of ${asideVariants.map((i) => JSON.stringify(i)).join(', ')}`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!title) {
|
|
26
|
+
const { locale } = slugToLocaleData(urlToSlug(Astro.url));
|
|
27
|
+
title = useTranslations(locale)(`aside.${type}`);
|
|
28
|
+
}
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
<aside aria-label={title} class={`starlight-aside starlight-aside--${type}`}>
|
|
32
|
+
<p class="starlight-aside__title" aria-hidden="true">
|
|
33
|
+
<Icon name={icons[type]} class="starlight-aside__icon" />{title}
|
|
34
|
+
</p>
|
|
35
|
+
<section class="starlight-aside__content">
|
|
36
|
+
<slot />
|
|
37
|
+
</section>
|
|
38
|
+
</aside>
|
package/utils/error-map.ts
CHANGED
|
@@ -11,6 +11,10 @@ type TypeOrLiteralErrByPathEntry = {
|
|
|
11
11
|
expected: unknown[];
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
export function throwValidationError(error: z.ZodError, message: string): never {
|
|
15
|
+
throw new Error(`${message}\n${error.issues.map((i) => i.message).join('\n')}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
export const errorMap: z.ZodErrorMap = (baseError, ctx) => {
|
|
15
19
|
const baseErrorPath = flattenErrorPath(baseError.path);
|
|
16
20
|
if (baseError.code === 'invalid_union') {
|
package/utils/plugins.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AstroIntegration } from 'astro';
|
|
2
2
|
import { z } from 'astro/zod';
|
|
3
3
|
import { StarlightConfigSchema, type StarlightUserConfig } from '../utils/user-config';
|
|
4
|
-
import { errorMap } from '../utils/error-map';
|
|
4
|
+
import { errorMap, throwValidationError } from '../utils/error-map';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Runs Starlight plugins in the order that they are configured after validating the user-provided
|
|
@@ -82,10 +82,6 @@ export async function runPlugins(
|
|
|
82
82
|
return { integrations, starlightConfig: starlightConfig.data };
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
function throwValidationError(error: z.ZodError, message: string): never {
|
|
86
|
-
throw new Error(`${message}\n${error.issues.map((i) => i.message).join('\n')}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
85
|
// https://github.com/withastro/astro/blob/910eb00fe0b70ca80bd09520ae100e8c78b675b5/packages/astro/src/core/config/schema.ts#L113
|
|
90
86
|
const astroIntegrationSchema = z.object({
|
|
91
87
|
name: z.string(),
|
package/utils/route-data.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { Route } from './routing';
|
|
|
10
10
|
import { localizedId } from './slugs';
|
|
11
11
|
import { useTranslations } from './translations';
|
|
12
12
|
|
|
13
|
-
interface PageProps extends Route {
|
|
13
|
+
export interface PageProps extends Route {
|
|
14
14
|
headings: MarkdownHeading[];
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -54,7 +54,7 @@ export function generateRouteData({
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
function getToC({ entry, locale, headings }: PageProps) {
|
|
57
|
+
export function getToC({ entry, locale, headings }: PageProps) {
|
|
58
58
|
const tocConfig =
|
|
59
59
|
entry.data.template === 'splash'
|
|
60
60
|
? false
|
package/utils/slugs.ts
CHANGED
|
@@ -101,3 +101,21 @@ export function localizedId(id: string, locale: string | undefined): string {
|
|
|
101
101
|
return id;
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
|
|
105
|
+
/** Extract the slug from a URL. */
|
|
106
|
+
export function urlToSlug(url: URL): string {
|
|
107
|
+
let pathname = url.pathname;
|
|
108
|
+
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
|
|
109
|
+
if (pathname.startsWith(base)) pathname = pathname.replace(base, '');
|
|
110
|
+
const segments = pathname.split('/');
|
|
111
|
+
const htmlExt = '.html';
|
|
112
|
+
if (segments.at(-1) === 'index.html') {
|
|
113
|
+
// Remove trailing `index.html`.
|
|
114
|
+
segments.pop();
|
|
115
|
+
} else if (segments.at(-1)?.endsWith(htmlExt)) {
|
|
116
|
+
// Remove trailing `.html`.
|
|
117
|
+
const last = segments.pop();
|
|
118
|
+
if (last) segments.push(last.slice(0, -1 * htmlExt.length));
|
|
119
|
+
}
|
|
120
|
+
return segments.filter(Boolean).join('/');
|
|
121
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { z } from 'astro/zod';
|
|
2
|
+
import { type ContentConfig, type SchemaContext } from 'astro:content';
|
|
3
|
+
import config from 'virtual:starlight/user-config';
|
|
4
|
+
import { errorMap, throwValidationError } from './error-map';
|
|
5
|
+
import { stripLeadingAndTrailingSlashes } from './path';
|
|
6
|
+
import { getToC, type PageProps, type StarlightRouteData } from './route-data';
|
|
7
|
+
import type { StarlightDocsEntry } from './routing';
|
|
8
|
+
import { slugToLocaleData, urlToSlug } from './slugs';
|
|
9
|
+
import { getPrevNextLinks, getSidebar } from './navigation';
|
|
10
|
+
import { useTranslations } from './translations';
|
|
11
|
+
import { docsSchema } from '../schema';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The frontmatter schema for Starlight pages derived from the default schema for Starlight’s
|
|
15
|
+
* `docs` content collection.
|
|
16
|
+
* The frontmatter schema for Starlight pages cannot include some properties which will be omitted
|
|
17
|
+
* and some others needs to be refined to a stricter type.
|
|
18
|
+
*/
|
|
19
|
+
const StarlightPageFrontmatterSchema = async (context: SchemaContext) => {
|
|
20
|
+
const userDocsSchema = await getUserDocsSchema();
|
|
21
|
+
const schema = typeof userDocsSchema === 'function' ? userDocsSchema(context) : userDocsSchema;
|
|
22
|
+
|
|
23
|
+
return schema.transform((frontmatter) => {
|
|
24
|
+
/**
|
|
25
|
+
* Starlight pages can only be edited if an edit URL is explicitly provided.
|
|
26
|
+
* The `sidebar` frontmatter prop only works for pages in an autogenerated links group.
|
|
27
|
+
* Starlight pages edit links cannot be autogenerated.
|
|
28
|
+
*
|
|
29
|
+
* These changes to the schema are done using a transformer and not using the usual `omit`
|
|
30
|
+
* method because when the frontmatter schema is extended by the user, an intersection between
|
|
31
|
+
* the default schema and the user schema is created using the `and` method. Intersections in
|
|
32
|
+
* Zod returns a `ZodIntersection` object which does not have some methods like `omit` or
|
|
33
|
+
* `pick`.
|
|
34
|
+
*
|
|
35
|
+
* This transformer only sets the `editUrl` default value and removes the `sidebar` property
|
|
36
|
+
* from the validated output but does not appply any changes to the input schema type itself so
|
|
37
|
+
* this needs to be done manually.
|
|
38
|
+
*
|
|
39
|
+
* @see StarlightPageFrontmatter
|
|
40
|
+
* @see https://github.com/colinhacks/zod#intersections
|
|
41
|
+
*/
|
|
42
|
+
const { editUrl, sidebar, ...others } = frontmatter;
|
|
43
|
+
const pageEditUrl = editUrl === undefined || editUrl === true ? false : editUrl;
|
|
44
|
+
return { ...others, editUrl: pageEditUrl };
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Type of Starlight pages frontmatter schema.
|
|
50
|
+
* We manually refines the `editUrl` type and omit the `sidebar` property as it's not possible to
|
|
51
|
+
* do that on the schema itself using Zod but the proper validation is still using a transformer.
|
|
52
|
+
* @see StarlightPageFrontmatterSchema
|
|
53
|
+
*/
|
|
54
|
+
type StarlightPageFrontmatter = Omit<
|
|
55
|
+
z.input<Awaited<ReturnType<typeof StarlightPageFrontmatterSchema>>>,
|
|
56
|
+
'editUrl' | 'sidebar'
|
|
57
|
+
> & { editUrl?: string | false };
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The props accepted by the `<StarlightPage/>` component.
|
|
61
|
+
*/
|
|
62
|
+
export type StarlightPageProps = Prettify<
|
|
63
|
+
// Remove the index signature from `Route`, omit undesired properties and make the rest optional.
|
|
64
|
+
Partial<Omit<RemoveIndexSignature<PageProps>, 'entry' | 'entryMeta' | 'id' | 'locale' | 'slug'>> &
|
|
65
|
+
// Add the sidebar definitions for a Starlight page.
|
|
66
|
+
Partial<Pick<StarlightRouteData, 'hasSidebar' | 'sidebar'>> & {
|
|
67
|
+
// And finally add the Starlight page frontmatter properties in a `frontmatter` property.
|
|
68
|
+
frontmatter: StarlightPageFrontmatter;
|
|
69
|
+
}
|
|
70
|
+
>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* A docs entry used for Starlight pages meant to be rendered by plugins and which is safe to cast
|
|
74
|
+
* to a `StarlightDocsEntry`.
|
|
75
|
+
* A Starlight page docs entry cannot be rendered like a content collection entry.
|
|
76
|
+
*/
|
|
77
|
+
type StarlightPageDocsEntry = Omit<StarlightDocsEntry, 'id' | 'render'> & {
|
|
78
|
+
/**
|
|
79
|
+
* The unique ID for this Starlight page which cannot be inferred from codegen like content
|
|
80
|
+
* collection entries.
|
|
81
|
+
*/
|
|
82
|
+
id: string;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export async function generateStarlightPageRouteData({
|
|
86
|
+
props,
|
|
87
|
+
url,
|
|
88
|
+
}: {
|
|
89
|
+
props: StarlightPageProps;
|
|
90
|
+
url: URL;
|
|
91
|
+
}): Promise<StarlightRouteData> {
|
|
92
|
+
const { isFallback, frontmatter, ...routeProps } = props;
|
|
93
|
+
const slug = urlToSlug(url);
|
|
94
|
+
const pageFrontmatter = await getStarlightPageFrontmatter(frontmatter);
|
|
95
|
+
const id = `${stripLeadingAndTrailingSlashes(slug)}.md`;
|
|
96
|
+
const localeData = slugToLocaleData(slug);
|
|
97
|
+
const sidebar = props.sidebar ?? getSidebar(url.pathname, localeData.locale);
|
|
98
|
+
const headings = props.headings ?? [];
|
|
99
|
+
const pageDocsEntry: StarlightPageDocsEntry = {
|
|
100
|
+
id,
|
|
101
|
+
slug,
|
|
102
|
+
body: '',
|
|
103
|
+
collection: 'docs',
|
|
104
|
+
data: {
|
|
105
|
+
...pageFrontmatter,
|
|
106
|
+
sidebar: {
|
|
107
|
+
attrs: {},
|
|
108
|
+
hidden: false,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
const entry = pageDocsEntry as StarlightDocsEntry;
|
|
113
|
+
const entryMeta: StarlightRouteData['entryMeta'] = {
|
|
114
|
+
dir: props.dir ?? localeData.dir,
|
|
115
|
+
lang: props.lang ?? localeData.lang,
|
|
116
|
+
locale: localeData.locale,
|
|
117
|
+
};
|
|
118
|
+
const editUrl = pageFrontmatter.editUrl ? new URL(pageFrontmatter.editUrl) : undefined;
|
|
119
|
+
const lastUpdated =
|
|
120
|
+
pageFrontmatter.lastUpdated instanceof Date ? pageFrontmatter.lastUpdated : undefined;
|
|
121
|
+
const routeData: StarlightRouteData = {
|
|
122
|
+
...routeProps,
|
|
123
|
+
...localeData,
|
|
124
|
+
id,
|
|
125
|
+
editUrl,
|
|
126
|
+
entry,
|
|
127
|
+
entryMeta,
|
|
128
|
+
hasSidebar: props.hasSidebar ?? entry.data.template !== 'splash',
|
|
129
|
+
headings,
|
|
130
|
+
labels: useTranslations(localeData.locale).all(),
|
|
131
|
+
lastUpdated,
|
|
132
|
+
pagination: getPrevNextLinks(sidebar, config.pagination, entry.data),
|
|
133
|
+
sidebar,
|
|
134
|
+
slug,
|
|
135
|
+
toc: getToC({
|
|
136
|
+
...routeProps,
|
|
137
|
+
...localeData,
|
|
138
|
+
entry,
|
|
139
|
+
entryMeta,
|
|
140
|
+
headings,
|
|
141
|
+
id,
|
|
142
|
+
locale: localeData.locale,
|
|
143
|
+
slug,
|
|
144
|
+
}),
|
|
145
|
+
};
|
|
146
|
+
if (isFallback) {
|
|
147
|
+
routeData.isFallback = true;
|
|
148
|
+
}
|
|
149
|
+
return routeData;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Validates the Starlight page frontmatter properties from the props received by a Starlight page. */
|
|
153
|
+
async function getStarlightPageFrontmatter(frontmatter: StarlightPageFrontmatter) {
|
|
154
|
+
// This needs to be in sync with ImageMetadata.
|
|
155
|
+
// https://github.com/withastro/astro/blob/cf993bc263b58502096f00d383266cd179f331af/packages/astro/src/assets/types.ts#L32
|
|
156
|
+
const schema = await StarlightPageFrontmatterSchema({
|
|
157
|
+
image: () =>
|
|
158
|
+
z.object({
|
|
159
|
+
src: z.string(),
|
|
160
|
+
width: z.number(),
|
|
161
|
+
height: z.number(),
|
|
162
|
+
format: z.union([
|
|
163
|
+
z.literal('png'),
|
|
164
|
+
z.literal('jpg'),
|
|
165
|
+
z.literal('jpeg'),
|
|
166
|
+
z.literal('tiff'),
|
|
167
|
+
z.literal('webp'),
|
|
168
|
+
z.literal('gif'),
|
|
169
|
+
z.literal('svg'),
|
|
170
|
+
z.literal('avif'),
|
|
171
|
+
]),
|
|
172
|
+
}),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const pageFrontmatter = schema.safeParse(frontmatter, { errorMap });
|
|
176
|
+
|
|
177
|
+
if (!pageFrontmatter.success) {
|
|
178
|
+
throwValidationError(
|
|
179
|
+
pageFrontmatter.error,
|
|
180
|
+
'Invalid frontmatter props passed to the `<StarlightPage/>` component.'
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return pageFrontmatter.data;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Returns the user docs schema and falls back to the default schema if needed. */
|
|
188
|
+
async function getUserDocsSchema(): Promise<
|
|
189
|
+
NonNullable<ContentConfig['collections']['docs']['schema']>
|
|
190
|
+
> {
|
|
191
|
+
const userCollections = (await import('virtual:starlight/collection-config')).collections;
|
|
192
|
+
return userCollections?.docs.schema ?? docsSchema();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// https://stackoverflow.com/a/66252656/1945960
|
|
196
|
+
type RemoveIndexSignature<T> = {
|
|
197
|
+
[K in keyof T as string extends K
|
|
198
|
+
? never
|
|
199
|
+
: number extends K
|
|
200
|
+
? never
|
|
201
|
+
: symbol extends K
|
|
202
|
+
? never
|
|
203
|
+
: K]: T[K];
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// https://www.totaltypescript.com/concepts/the-prettify-helper
|
|
207
|
+
type Prettify<T> = {
|
|
208
|
+
[K in keyof T]: T[K];
|
|
209
|
+
} & {};
|
package/virtual.d.ts
CHANGED
|
@@ -24,6 +24,10 @@ declare module 'virtual:starlight/user-images' {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
declare module 'virtual:starlight/collection-config' {
|
|
28
|
+
export const collections: import('astro:content').ContentConfig['collections'] | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
27
31
|
declare module 'virtual:starlight/components/Banner' {
|
|
28
32
|
const Banner: typeof import('./components/Banner.astro').default;
|
|
29
33
|
export default Banner;
|