@astrojs/starlight 0.11.1 → 0.12.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 +37 -0
- package/components/Hero.astro +22 -8
- package/components/MarkdownContent.astro +2 -2
- package/components/MobileMenuFooter.astro +15 -1
- package/components/MobileTableOfContents.astro +1 -0
- package/components/Pagination.astro +8 -2
- package/components/SidebarSublist.astro +13 -3
- package/components/SiteTitle.astro +2 -8
- package/index.ts +2 -0
- package/package.json +1 -1
- package/schema.ts +3 -55
- package/schemas/hero.ts +70 -0
- package/schemas/i18n.ts +1 -0
- package/schemas/sidebar.ts +4 -3
- package/schemas/social.ts +27 -25
- package/style/util.css +6 -0
- package/translations/gl.json +22 -0
- package/translations/index.ts +2 -0
- package/utils/createTranslationSystem.ts +79 -0
- package/utils/navigation.ts +4 -0
- package/utils/route-data.ts +5 -3
- package/utils/translations-fs.ts +44 -0
- package/utils/translations.ts +5 -53
- package/utils/user-config.ts +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @astrojs/starlight
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#995](https://github.com/withastro/starlight/pull/995) [`5bf4457`](https://github.com/withastro/starlight/commit/5bf44577634935b9fa6d50b040abcd680035075f) Thanks [@kevinzunigacuellar](https://github.com/kevinzunigacuellar)! - Adds support for adding sidebar badges to group headings
|
|
8
|
+
|
|
9
|
+
- [#988](https://github.com/withastro/starlight/pull/988) [`977fe13`](https://github.com/withastro/starlight/commit/977fe135a74661300589898abe98aec73cad9ed3) Thanks [@magicDGS](https://github.com/magicDGS)! - Include social icon links in mobile menu
|
|
10
|
+
|
|
11
|
+
- [#280](https://github.com/withastro/starlight/pull/280) [`72cca2d`](https://github.com/withastro/starlight/commit/72cca2d07644f00595da6ebf7d603adb282f359d) Thanks [@cbontems](https://github.com/cbontems)! - Support light & dark variants of the hero image.
|
|
12
|
+
|
|
13
|
+
⚠️ **Potentially breaking change:** The `hero.image` schema is now slightly stricter than previously.
|
|
14
|
+
|
|
15
|
+
The `hero.image.html` property can no longer be used alongside the `hero.image.alt` or `hero.image.file` properties.
|
|
16
|
+
Previously, `html` was ignored when used with `file` and `alt` was ignored when used with `html`.
|
|
17
|
+
Now, those combinations will throw errors.
|
|
18
|
+
If you encounter errors, remove the `image.hero` property that is not in use.
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- [#1004](https://github.com/withastro/starlight/pull/1004) [`7f92213`](https://github.com/withastro/starlight/commit/7f92213a0b93de5a844816841a6bc9cdd371de0c) Thanks [@nunhes](https://github.com/nunhes)! - Add Galician language support
|
|
23
|
+
|
|
24
|
+
- [#1003](https://github.com/withastro/starlight/pull/1003) [`f1fdb50`](https://github.com/withastro/starlight/commit/f1fdb50daebe79548c7789d3f7dd968b261d2da7) Thanks [@delucis](https://github.com/delucis)! - Internal: refactor translation string loading to make translations available to Starlight integration code
|
|
25
|
+
|
|
26
|
+
## 0.11.2
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- [#944](https://github.com/withastro/starlight/pull/944) [`7a6446e`](https://github.com/withastro/starlight/commit/7a6446ebc61ba9cc36d1dcfd13db15c1533751ab) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fix issue with sidebar autogenerated groups configured with a directory containing leading or trailing slash
|
|
31
|
+
|
|
32
|
+
- [#985](https://github.com/withastro/starlight/pull/985) [`92b3b57`](https://github.com/withastro/starlight/commit/92b3b575404d0dc34f720c0ba29d8ed50be98f58) Thanks [@delucis](https://github.com/delucis)! - Fix edit URLs for pages displaying fallback content
|
|
33
|
+
|
|
34
|
+
- [#986](https://github.com/withastro/starlight/pull/986) [`0470734`](https://github.com/withastro/starlight/commit/0470734e0dc323f9945e06bee4338c2f777ba0d6) Thanks [@dreyfus92](https://github.com/dreyfus92)! - Prevent overscrolling on mobile table of contents by setting 'overscroll-behavior: contain'.
|
|
35
|
+
|
|
36
|
+
- [#924](https://github.com/withastro/starlight/pull/924) [`39d6302`](https://github.com/withastro/starlight/commit/39d6302db42ee1105d690bbd3a66053e6b21e15a) Thanks [@kevinzunigacuellar](https://github.com/kevinzunigacuellar)! - Remove extra margin from markdown lists that uses inline code
|
|
37
|
+
|
|
38
|
+
- [#814](https://github.com/withastro/starlight/pull/814) [`1e517d9`](https://github.com/withastro/starlight/commit/1e517d92a4cf146d2dcae58f7c4299d6f25ea73e) Thanks [@julien-deramond](https://github.com/julien-deramond)! - Prevent text from overflowing pagination items
|
|
39
|
+
|
|
3
40
|
## 0.11.1
|
|
4
41
|
|
|
5
42
|
### Patch Changes
|
package/components/Hero.astro
CHANGED
|
@@ -14,20 +14,34 @@ const imageAttrs = {
|
|
|
14
14
|
height: 400,
|
|
15
15
|
alt: image?.alt || '',
|
|
16
16
|
};
|
|
17
|
+
|
|
18
|
+
let darkImage: ImageMetadata | undefined;
|
|
19
|
+
let lightImage: ImageMetadata | undefined;
|
|
20
|
+
let rawHtml: string | undefined;
|
|
21
|
+
if (image) {
|
|
22
|
+
if ('file' in image) {
|
|
23
|
+
darkImage = image.file;
|
|
24
|
+
} else if ('dark' in image) {
|
|
25
|
+
darkImage = image.dark;
|
|
26
|
+
lightImage = image.light;
|
|
27
|
+
} else {
|
|
28
|
+
rawHtml = image.html;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
17
31
|
---
|
|
18
32
|
|
|
19
33
|
<div class="hero">
|
|
20
34
|
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
) : (
|
|
28
|
-
image?.html && <div class="hero-html sl-flex" set:html={image.html} />
|
|
35
|
+
darkImage && (
|
|
36
|
+
<Image
|
|
37
|
+
src={darkImage}
|
|
38
|
+
{...imageAttrs}
|
|
39
|
+
class:list={{ 'light:sl-hidden': Boolean(lightImage) }}
|
|
40
|
+
/>
|
|
29
41
|
)
|
|
30
42
|
}
|
|
43
|
+
{lightImage && <Image src={lightImage} {...imageAttrs} class="dark:sl-hidden" />}
|
|
44
|
+
{rawHtml && <div class="hero-html sl-flex" set:html={rawHtml} />}
|
|
31
45
|
<div class="sl-flex stack">
|
|
32
46
|
<div class="sl-flex copy">
|
|
33
47
|
<h1 id={PAGE_TITLE_ID} data-page-title set:html={title} />
|
|
@@ -7,8 +7,8 @@ import type { Props } from '../props';
|
|
|
7
7
|
<style>
|
|
8
8
|
.content
|
|
9
9
|
:global(
|
|
10
|
-
:not(a, strong, em, del, span, input)
|
|
11
|
-
+ :not(a, strong, em, del, span, input, :where(.not-content *))
|
|
10
|
+
:not(a, strong, em, del, span, input, code)
|
|
11
|
+
+ :not(a, strong, em, del, span, input, code, :where(.not-content *))
|
|
12
12
|
) {
|
|
13
13
|
margin-top: 1.5rem;
|
|
14
14
|
}
|
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { LanguageSelect, ThemeSelect } from 'virtual:starlight/components';
|
|
2
|
+
import { LanguageSelect, ThemeSelect, SocialIcons } from 'virtual:starlight/components';
|
|
3
3
|
import type { Props } from '../props';
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
<div class="mobile-preferences sl-flex">
|
|
7
|
+
<div class="sl-flex social-icons">
|
|
8
|
+
<SocialIcons {...Astro.props} />
|
|
9
|
+
</div>
|
|
7
10
|
<ThemeSelect {...Astro.props} />
|
|
8
11
|
<LanguageSelect {...Astro.props} />
|
|
9
12
|
</div>
|
|
10
13
|
|
|
11
14
|
<style>
|
|
15
|
+
.social-icons {
|
|
16
|
+
margin-inline-end: auto;
|
|
17
|
+
gap: 1rem;
|
|
18
|
+
align-items: center;
|
|
19
|
+
padding-block: 1rem;
|
|
20
|
+
}
|
|
21
|
+
.social-icons:empty {
|
|
22
|
+
display: none;
|
|
23
|
+
}
|
|
12
24
|
.mobile-preferences {
|
|
13
25
|
justify-content: space-between;
|
|
26
|
+
flex-wrap: wrap;
|
|
14
27
|
border-top: 1px solid var(--sl-color-gray-6);
|
|
28
|
+
column-gap: 1rem;
|
|
15
29
|
padding: 0.5rem 0;
|
|
16
30
|
}
|
|
17
31
|
</style>
|
|
@@ -9,7 +9,7 @@ const isRtl = dir === 'rtl';
|
|
|
9
9
|
const t = useTranslations(locale);
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
<div class="pagination-links
|
|
12
|
+
<div class="pagination-links" dir={dir}>
|
|
13
13
|
{
|
|
14
14
|
prev && (
|
|
15
15
|
<a href={prev.href} rel="prev">
|
|
@@ -38,7 +38,8 @@ const t = useTranslations(locale);
|
|
|
38
38
|
|
|
39
39
|
<style>
|
|
40
40
|
.pagination-links {
|
|
41
|
-
|
|
41
|
+
display: grid;
|
|
42
|
+
grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
|
|
42
43
|
gap: 1rem;
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -56,6 +57,7 @@ const t = useTranslations(locale);
|
|
|
56
57
|
text-decoration: none;
|
|
57
58
|
color: var(--sl-color-gray-2);
|
|
58
59
|
box-shadow: var(--sl-shadow-md);
|
|
60
|
+
overflow-wrap: anywhere;
|
|
59
61
|
}
|
|
60
62
|
[rel='next'] {
|
|
61
63
|
justify-content: end;
|
|
@@ -71,4 +73,8 @@ const t = useTranslations(locale);
|
|
|
71
73
|
font-size: var(--sl-text-2xl);
|
|
72
74
|
line-height: var(--sl-line-height-headings);
|
|
73
75
|
}
|
|
76
|
+
|
|
77
|
+
svg {
|
|
78
|
+
flex-shrink: 0;
|
|
79
|
+
}
|
|
74
80
|
</style>
|
|
@@ -36,7 +36,15 @@ interface Props {
|
|
|
36
36
|
open={flattenSidebar(entry.entries).some((i) => i.isCurrent) || !entry.collapsed}
|
|
37
37
|
>
|
|
38
38
|
<summary>
|
|
39
|
-
<
|
|
39
|
+
<div class="group-label">
|
|
40
|
+
<span class="large">{entry.label}</span>
|
|
41
|
+
{entry.badge && (
|
|
42
|
+
<>
|
|
43
|
+
{' '}
|
|
44
|
+
<Badge text={entry.badge.text} variant={entry.badge.variant} />
|
|
45
|
+
</>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
40
48
|
<Icon name="right-caret" class="caret" size="1.25rem" />
|
|
41
49
|
</summary>
|
|
42
50
|
<Astro.self sublist={entry.entries} nested />
|
|
@@ -78,7 +86,8 @@ interface Props {
|
|
|
78
86
|
display: flex;
|
|
79
87
|
align-items: center;
|
|
80
88
|
justify-content: space-between;
|
|
81
|
-
padding
|
|
89
|
+
padding: 0.2em var(--sl-sidebar-item-padding-inline);
|
|
90
|
+
line-height: 1.4;
|
|
82
91
|
cursor: pointer;
|
|
83
92
|
user-select: none;
|
|
84
93
|
}
|
|
@@ -120,7 +129,8 @@ interface Props {
|
|
|
120
129
|
background-color: var(--sl-color-text-accent);
|
|
121
130
|
}
|
|
122
131
|
|
|
123
|
-
a > *:not(:last-child)
|
|
132
|
+
a > *:not(:last-child),
|
|
133
|
+
.group-label > *:not(:last-child) {
|
|
124
134
|
margin-inline-end: 0.25em;
|
|
125
135
|
}
|
|
126
136
|
|
|
@@ -12,7 +12,7 @@ const href = pathWithBase(Astro.props.locale || '/');
|
|
|
12
12
|
config.logo && logos.dark && (
|
|
13
13
|
<>
|
|
14
14
|
<img
|
|
15
|
-
class:list={{ '
|
|
15
|
+
class:list={{ 'light:sl-hidden': !('src' in config.logo) }}
|
|
16
16
|
alt={config.logo.alt}
|
|
17
17
|
src={logos.dark.src}
|
|
18
18
|
width={logos.dark.width}
|
|
@@ -21,7 +21,7 @@ const href = pathWithBase(Astro.props.locale || '/');
|
|
|
21
21
|
{/* Show light alternate if a user configure both light and dark logos. */}
|
|
22
22
|
{!('src' in config.logo) && (
|
|
23
23
|
<img
|
|
24
|
-
class="
|
|
24
|
+
class="dark:sl-hidden"
|
|
25
25
|
alt={config.logo.alt}
|
|
26
26
|
src={logos.light?.src}
|
|
27
27
|
width={logos.light?.width}
|
|
@@ -56,10 +56,4 @@ const href = pathWithBase(Astro.props.locale || '/');
|
|
|
56
56
|
object-fit: contain;
|
|
57
57
|
object-position: 0 50%;
|
|
58
58
|
}
|
|
59
|
-
:global([data-theme='light']) .dark-only {
|
|
60
|
-
display: none;
|
|
61
|
-
}
|
|
62
|
-
:global([data-theme='dark']) .light-only {
|
|
63
|
-
display: none;
|
|
64
|
-
}
|
|
65
59
|
</style>
|
package/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { vitePluginStarlightUserConfig } from './integrations/virtual-user-confi
|
|
|
9
9
|
import { errorMap } from './utils/error-map';
|
|
10
10
|
import { StarlightConfigSchema, type StarlightUserConfig } from './utils/user-config';
|
|
11
11
|
import { rehypeRtlCodeSupport } from './integrations/code-rtl-support';
|
|
12
|
+
import { createTranslationSystemFromFs } from './utils/translations-fs';
|
|
12
13
|
|
|
13
14
|
export default function StarlightIntegration(opts: StarlightUserConfig): AstroIntegration {
|
|
14
15
|
const parsedConfig = StarlightConfigSchema.safeParse(opts, { errorMap });
|
|
@@ -26,6 +27,7 @@ export default function StarlightIntegration(opts: StarlightUserConfig): AstroIn
|
|
|
26
27
|
name: '@astrojs/starlight',
|
|
27
28
|
hooks: {
|
|
28
29
|
'astro:config:setup': ({ config, injectRoute, updateConfig }) => {
|
|
30
|
+
const useTranslations = createTranslationSystemFromFs(userConfig, config);
|
|
29
31
|
injectRoute({
|
|
30
32
|
pattern: '404',
|
|
31
33
|
entryPoint: '@astrojs/starlight/404.astro',
|
package/package.json
CHANGED
package/schema.ts
CHANGED
|
@@ -3,16 +3,13 @@ import type { SchemaContext } from 'astro:content';
|
|
|
3
3
|
import { HeadConfigSchema } from './schemas/head';
|
|
4
4
|
import { PrevNextLinkConfigSchema } from './schemas/prevNextLink';
|
|
5
5
|
import { TableOfContentsSchema } from './schemas/tableOfContents';
|
|
6
|
-
import { Icons } from './components/Icons';
|
|
7
6
|
import { BadgeConfigSchema } from './schemas/badge';
|
|
7
|
+
import { HeroSchema } from './schemas/hero';
|
|
8
8
|
import { SidebarLinkItemHTMLAttributesSchema } from './schemas/sidebar';
|
|
9
9
|
export { i18nSchema } from './schemas/i18n';
|
|
10
10
|
|
|
11
|
-
type IconName = keyof typeof Icons;
|
|
12
|
-
const iconNames = Object.keys(Icons) as [IconName, ...IconName[]];
|
|
13
|
-
|
|
14
11
|
export function docsSchema() {
|
|
15
|
-
return (
|
|
12
|
+
return (context: SchemaContext) =>
|
|
16
13
|
z.object({
|
|
17
14
|
/** The title of the current page. Required. */
|
|
18
15
|
title: z.string(),
|
|
@@ -45,56 +42,7 @@ export function docsSchema() {
|
|
|
45
42
|
template: z.enum(['doc', 'splash']).default('doc'),
|
|
46
43
|
|
|
47
44
|
/** Display a hero section on this page. */
|
|
48
|
-
hero:
|
|
49
|
-
.object({
|
|
50
|
-
/**
|
|
51
|
-
* The large title text to show. If not provided, will default to the top-level `title`.
|
|
52
|
-
* Can include HTML.
|
|
53
|
-
*/
|
|
54
|
-
title: z.string().optional(),
|
|
55
|
-
/**
|
|
56
|
-
* A short bit of text about your project.
|
|
57
|
-
* Will be displayed in a smaller size below the title.
|
|
58
|
-
*/
|
|
59
|
-
tagline: z.string().optional(),
|
|
60
|
-
/** The image to use in the hero. You can provide either a relative `file` path or raw `html`. */
|
|
61
|
-
image: z
|
|
62
|
-
.object({
|
|
63
|
-
/** Alt text for screenreaders and other assistive technologies describing your hero image. */
|
|
64
|
-
alt: z.string().default(''),
|
|
65
|
-
/** Relative path to an image file in your repo, e.g. `../../assets/hero.png`. */
|
|
66
|
-
file: image().optional(),
|
|
67
|
-
/** Raw HTML string instead of an image file. Useful for inline SVGs or more complex hero content. */
|
|
68
|
-
html: z.string().optional(),
|
|
69
|
-
})
|
|
70
|
-
.optional(),
|
|
71
|
-
/** An array of call-to-action links displayed at the bottom of the hero. */
|
|
72
|
-
actions: z
|
|
73
|
-
.object({
|
|
74
|
-
/** Text label displayed in the link. */
|
|
75
|
-
text: z.string(),
|
|
76
|
-
/** Value for the link’s `href` attribute, e.g. `/page` or `https://mysite.com`. */
|
|
77
|
-
link: z.string(),
|
|
78
|
-
/** Button style to use. One of `primary`, `secondary`, or `minimal` (the default). */
|
|
79
|
-
variant: z.enum(['primary', 'secondary', 'minimal']).default('minimal'),
|
|
80
|
-
/**
|
|
81
|
-
* An optional icon to display alongside the link text.
|
|
82
|
-
* Can be an inline `<svg>` or the name of one of Starlight’s built-in icons.
|
|
83
|
-
*/
|
|
84
|
-
icon: z
|
|
85
|
-
.union([z.enum(iconNames), z.string().startsWith('<svg')])
|
|
86
|
-
.transform((icon) => {
|
|
87
|
-
const parsedIcon = z.enum(iconNames).safeParse(icon);
|
|
88
|
-
return parsedIcon.success
|
|
89
|
-
? ({ type: 'icon', name: parsedIcon.data } as const)
|
|
90
|
-
: ({ type: 'raw', html: icon } as const);
|
|
91
|
-
})
|
|
92
|
-
.optional(),
|
|
93
|
-
})
|
|
94
|
-
.array()
|
|
95
|
-
.default([]),
|
|
96
|
-
})
|
|
97
|
-
.optional(),
|
|
45
|
+
hero: HeroSchema(context).optional(),
|
|
98
46
|
|
|
99
47
|
/**
|
|
100
48
|
* The last update date of the current page.
|
package/schemas/hero.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from 'astro/zod';
|
|
2
|
+
import type { SchemaContext } from 'astro:content';
|
|
3
|
+
import { Icons } from '../components/Icons';
|
|
4
|
+
|
|
5
|
+
type IconName = keyof typeof Icons;
|
|
6
|
+
const iconNames = Object.keys(Icons) as [IconName, ...IconName[]];
|
|
7
|
+
|
|
8
|
+
export const HeroSchema = ({ image }: SchemaContext) =>
|
|
9
|
+
z.object({
|
|
10
|
+
/**
|
|
11
|
+
* The large title text to show. If not provided, will default to the top-level `title`.
|
|
12
|
+
* Can include HTML.
|
|
13
|
+
*/
|
|
14
|
+
title: z.string().optional(),
|
|
15
|
+
/**
|
|
16
|
+
* A short bit of text about your project.
|
|
17
|
+
* Will be displayed in a smaller size below the title.
|
|
18
|
+
*/
|
|
19
|
+
tagline: z.string().optional(),
|
|
20
|
+
/** The image to use in the hero. You can provide either a relative `file` path or raw `html`. */
|
|
21
|
+
image: z
|
|
22
|
+
.union([
|
|
23
|
+
z.object({
|
|
24
|
+
/** Alt text for screenreaders and other assistive technologies describing your hero image. */
|
|
25
|
+
alt: z.string().default(''),
|
|
26
|
+
/** Relative path to an image file in your repo, e.g. `../../assets/hero.png`. */
|
|
27
|
+
file: image(),
|
|
28
|
+
}),
|
|
29
|
+
z.object({
|
|
30
|
+
/** Alt text for screenreaders and other assistive technologies describing your hero image. */
|
|
31
|
+
alt: z.string().default(''),
|
|
32
|
+
/** Relative path to an image file in your repo to use in dark mode, e.g. `../../assets/hero-dark.png`. */
|
|
33
|
+
dark: image(),
|
|
34
|
+
/** Relative path to an image file in your repo to use in light mode, e.g. `../../assets/hero-light.png`. */
|
|
35
|
+
light: image(),
|
|
36
|
+
}),
|
|
37
|
+
z
|
|
38
|
+
.object({
|
|
39
|
+
/** Raw HTML string instead of an image file. Useful for inline SVGs or more complex hero content. */
|
|
40
|
+
html: z.string(),
|
|
41
|
+
})
|
|
42
|
+
.transform(({ html }) => ({ html, alt: '' })),
|
|
43
|
+
])
|
|
44
|
+
.optional(),
|
|
45
|
+
/** An array of call-to-action links displayed at the bottom of the hero. */
|
|
46
|
+
actions: z
|
|
47
|
+
.object({
|
|
48
|
+
/** Text label displayed in the link. */
|
|
49
|
+
text: z.string(),
|
|
50
|
+
/** Value for the link’s `href` attribute, e.g. `/page` or `https://mysite.com`. */
|
|
51
|
+
link: z.string(),
|
|
52
|
+
/** Button style to use. One of `primary`, `secondary`, or `minimal` (the default). */
|
|
53
|
+
variant: z.enum(['primary', 'secondary', 'minimal']).default('minimal'),
|
|
54
|
+
/**
|
|
55
|
+
* An optional icon to display alongside the link text.
|
|
56
|
+
* Can be an inline `<svg>` or the name of one of Starlight’s built-in icons.
|
|
57
|
+
*/
|
|
58
|
+
icon: z
|
|
59
|
+
.union([z.enum(iconNames), z.string().startsWith('<svg')])
|
|
60
|
+
.transform((icon) => {
|
|
61
|
+
const parsedIcon = z.enum(iconNames).safeParse(icon);
|
|
62
|
+
return parsedIcon.success
|
|
63
|
+
? ({ type: 'icon', name: parsedIcon.data } as const)
|
|
64
|
+
: ({ type: 'raw', html: icon } as const);
|
|
65
|
+
})
|
|
66
|
+
.optional(),
|
|
67
|
+
})
|
|
68
|
+
.array()
|
|
69
|
+
.default([]),
|
|
70
|
+
});
|
package/schemas/i18n.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { z } from 'astro/zod';
|
|
|
3
3
|
export function i18nSchema() {
|
|
4
4
|
return starlightI18nSchema().merge(pagefindI18nSchema());
|
|
5
5
|
}
|
|
6
|
+
export type i18nSchemaOutput = z.output<ReturnType<typeof i18nSchema>>;
|
|
6
7
|
|
|
7
8
|
export function builtinI18nSchema() {
|
|
8
9
|
return starlightI18nSchema().required().strict().merge(pagefindI18nSchema());
|
package/schemas/sidebar.ts
CHANGED
|
@@ -2,12 +2,15 @@ import type { AstroBuiltinAttributes } from 'astro';
|
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types';
|
|
3
3
|
import { z } from 'astro/zod';
|
|
4
4
|
import { BadgeConfigSchema } from './badge';
|
|
5
|
+
import { stripLeadingAndTrailingSlashes } from '../utils/path';
|
|
5
6
|
|
|
6
7
|
const SidebarBaseSchema = z.object({
|
|
7
8
|
/** The visible label for this item in the sidebar. */
|
|
8
9
|
label: z.string(),
|
|
9
10
|
/** Translations of the `label` for each supported language. */
|
|
10
11
|
translations: z.record(z.string()).default({}),
|
|
12
|
+
/** Adds a badge to the link item */
|
|
13
|
+
badge: BadgeConfigSchema(),
|
|
11
14
|
});
|
|
12
15
|
|
|
13
16
|
const SidebarGroupSchema = SidebarBaseSchema.extend({
|
|
@@ -28,8 +31,6 @@ export const SidebarLinkItemHTMLAttributesSchema = () => linkHTMLAttributesSchem
|
|
|
28
31
|
const SidebarLinkItemSchema = SidebarBaseSchema.extend({
|
|
29
32
|
/** The link to this item’s content. Can be a relative link to local files or the full URL of an external page. */
|
|
30
33
|
link: z.string(),
|
|
31
|
-
/** Adds a badge to the link item */
|
|
32
|
-
badge: BadgeConfigSchema(),
|
|
33
34
|
/** HTML attributes to add to the link item. */
|
|
34
35
|
attrs: SidebarLinkItemHTMLAttributesSchema(),
|
|
35
36
|
});
|
|
@@ -39,7 +40,7 @@ const AutoSidebarGroupSchema = SidebarGroupSchema.extend({
|
|
|
39
40
|
/** Enable autogenerating a sidebar category from a specific docs directory. */
|
|
40
41
|
autogenerate: z.object({
|
|
41
42
|
/** The directory to generate sidebar items for. */
|
|
42
|
-
directory: z.string(),
|
|
43
|
+
directory: z.string().transform(stripLeadingAndTrailingSlashes),
|
|
43
44
|
/**
|
|
44
45
|
* Whether the autogenerated subgroups should be collapsed by default.
|
|
45
46
|
* Defaults to the `AutoSidebarGroup` `collapsed` value.
|
package/schemas/social.ts
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
1
1
|
import { z } from 'astro/zod';
|
|
2
2
|
|
|
3
|
+
export const socialLinks = [
|
|
4
|
+
'twitter',
|
|
5
|
+
'mastodon',
|
|
6
|
+
'github',
|
|
7
|
+
'gitlab',
|
|
8
|
+
'bitbucket',
|
|
9
|
+
'discord',
|
|
10
|
+
'gitter',
|
|
11
|
+
'codeberg',
|
|
12
|
+
'codePen',
|
|
13
|
+
'youtube',
|
|
14
|
+
'threads',
|
|
15
|
+
'linkedin',
|
|
16
|
+
'twitch',
|
|
17
|
+
'microsoftTeams',
|
|
18
|
+
'instagram',
|
|
19
|
+
'stackOverflow',
|
|
20
|
+
'x.com',
|
|
21
|
+
'telegram',
|
|
22
|
+
'rss',
|
|
23
|
+
'facebook',
|
|
24
|
+
'email',
|
|
25
|
+
'reddit',
|
|
26
|
+
'patreon',
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
3
29
|
export const SocialLinksSchema = () =>
|
|
4
30
|
z
|
|
5
31
|
.record(
|
|
6
|
-
z.enum(
|
|
7
|
-
'twitter',
|
|
8
|
-
'mastodon',
|
|
9
|
-
'github',
|
|
10
|
-
'gitlab',
|
|
11
|
-
'bitbucket',
|
|
12
|
-
'discord',
|
|
13
|
-
'gitter',
|
|
14
|
-
'codeberg',
|
|
15
|
-
'codePen',
|
|
16
|
-
'youtube',
|
|
17
|
-
'threads',
|
|
18
|
-
'linkedin',
|
|
19
|
-
'twitch',
|
|
20
|
-
'microsoftTeams',
|
|
21
|
-
'instagram',
|
|
22
|
-
'stackOverflow',
|
|
23
|
-
'x.com',
|
|
24
|
-
'telegram',
|
|
25
|
-
'rss',
|
|
26
|
-
'facebook',
|
|
27
|
-
'email',
|
|
28
|
-
'reddit',
|
|
29
|
-
'patreon',
|
|
30
|
-
]),
|
|
32
|
+
z.enum(socialLinks),
|
|
31
33
|
// Link to the respective social profile for this site
|
|
32
34
|
z.string().url()
|
|
33
35
|
)
|
package/style/util.css
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skipLink.label": "Ir ao contido",
|
|
3
|
+
"search.label": "Busca",
|
|
4
|
+
"search.shortcutLabel": "(Preme / para Busca)",
|
|
5
|
+
"search.cancelLabel": "Deixar",
|
|
6
|
+
"search.devWarning": "A busca só está dispoñible nas versións de producción. \nTrata de construir e ollear o sitio para probalo localmente.",
|
|
7
|
+
"themeSelect.accessibleLabel": "Seleciona tema",
|
|
8
|
+
"themeSelect.dark": "Escuro",
|
|
9
|
+
"themeSelect.light": "Claro",
|
|
10
|
+
"themeSelect.auto": "Auto",
|
|
11
|
+
"languageSelect.accessibleLabel": "Seleciona linguaxe",
|
|
12
|
+
"menuButton.accessibleLabel": "Menú",
|
|
13
|
+
"sidebarNav.accessibleLabel": "Principal",
|
|
14
|
+
"tableOfContents.onThisPage": "Nesta paxina",
|
|
15
|
+
"tableOfContents.overview": "Sinopse",
|
|
16
|
+
"i18n.untranslatedContent": "Este contido aínda non está dispoñible no teu idioma.",
|
|
17
|
+
"page.editLink": "Editar paxina",
|
|
18
|
+
"page.lastUpdated": "Última actualización:",
|
|
19
|
+
"page.previousLink": "Anterior",
|
|
20
|
+
"page.nextLink": "Seguinte",
|
|
21
|
+
"404.text": "Paxina non atopada. Comproba a URL ou intenta usar a barra de busca."
|
|
22
|
+
}
|
package/translations/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import ja from './ja.json';
|
|
|
7
7
|
import pt from './pt.json';
|
|
8
8
|
import fa from './fa.json';
|
|
9
9
|
import fr from './fr.json';
|
|
10
|
+
import gl from './gl.json';
|
|
10
11
|
import he from './he.json';
|
|
11
12
|
import id from './id.json';
|
|
12
13
|
import it from './it.json';
|
|
@@ -34,6 +35,7 @@ export default Object.fromEntries(
|
|
|
34
35
|
pt,
|
|
35
36
|
fa,
|
|
36
37
|
fr,
|
|
38
|
+
gl,
|
|
37
39
|
he,
|
|
38
40
|
id,
|
|
39
41
|
it,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { i18nSchemaOutput } from '../schemas/i18n';
|
|
2
|
+
import builtinTranslations from '../translations';
|
|
3
|
+
import type { StarlightConfig } from './user-config';
|
|
4
|
+
|
|
5
|
+
export function createTranslationSystem(
|
|
6
|
+
userTranslations: Record<string, i18nSchemaOutput>,
|
|
7
|
+
config: Pick<StarlightConfig, 'defaultLocale' | 'locales'>
|
|
8
|
+
) {
|
|
9
|
+
/** User-configured default locale. */
|
|
10
|
+
const defaultLocale = config.defaultLocale?.locale || 'root';
|
|
11
|
+
|
|
12
|
+
/** Default map of UI strings based on Starlight and user-configured defaults. */
|
|
13
|
+
const defaults = buildDictionary(
|
|
14
|
+
builtinTranslations.en!,
|
|
15
|
+
userTranslations.en,
|
|
16
|
+
builtinTranslations[defaultLocale] || builtinTranslations[stripLangRegion(defaultLocale)],
|
|
17
|
+
userTranslations[defaultLocale]
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generate a utility function that returns UI strings for the given `locale`.
|
|
22
|
+
* @param {string | undefined} [locale]
|
|
23
|
+
* @example
|
|
24
|
+
* const t = useTranslations('en');
|
|
25
|
+
* const label = t('search.label'); // => 'Search'
|
|
26
|
+
*/
|
|
27
|
+
return function useTranslations(locale: string | undefined) {
|
|
28
|
+
const lang = localeToLang(locale, config.locales, config.defaultLocale);
|
|
29
|
+
const dictionary = buildDictionary(
|
|
30
|
+
defaults,
|
|
31
|
+
builtinTranslations[lang] || builtinTranslations[stripLangRegion(lang)],
|
|
32
|
+
userTranslations[lang]
|
|
33
|
+
);
|
|
34
|
+
const t = <K extends keyof typeof dictionary>(key: K) => dictionary[key];
|
|
35
|
+
t.pick = (startOfKey: string) =>
|
|
36
|
+
Object.fromEntries(Object.entries(dictionary).filter(([k]) => k.startsWith(startOfKey)));
|
|
37
|
+
return t;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Strips the region subtag from a BCP-47 lang string.
|
|
43
|
+
* @param {string} [lang]
|
|
44
|
+
* @example
|
|
45
|
+
* const lang = stripLangRegion('en-GB'); // => 'en'
|
|
46
|
+
*/
|
|
47
|
+
function stripLangRegion(lang: string) {
|
|
48
|
+
return lang.replace(/-[a-zA-Z]{2}/, '');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get the BCP-47 language tag for the given locale.
|
|
53
|
+
* @param locale Locale string or `undefined` for the root locale.
|
|
54
|
+
*/
|
|
55
|
+
function localeToLang(
|
|
56
|
+
locale: string | undefined,
|
|
57
|
+
locales: StarlightConfig['locales'],
|
|
58
|
+
defaultLocale: StarlightConfig['defaultLocale']
|
|
59
|
+
): string {
|
|
60
|
+
const lang = locale ? locales?.[locale]?.lang : locales?.root?.lang;
|
|
61
|
+
const defaultLang = defaultLocale?.lang || defaultLocale?.locale;
|
|
62
|
+
return lang || defaultLang || 'en';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Build a dictionary by layering preferred translation sources. */
|
|
66
|
+
function buildDictionary(
|
|
67
|
+
base: (typeof builtinTranslations)[string],
|
|
68
|
+
...dictionaries: (i18nSchemaOutput | undefined)[]
|
|
69
|
+
) {
|
|
70
|
+
const dictionary = { ...base };
|
|
71
|
+
// Iterate over alternate dictionaries to avoid overwriting preceding values with `undefined`.
|
|
72
|
+
for (const dict of dictionaries) {
|
|
73
|
+
for (const key in dict) {
|
|
74
|
+
const value = dict[key as keyof typeof dict];
|
|
75
|
+
if (value) dictionary[key as keyof typeof dict] = value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return dictionary;
|
|
79
|
+
}
|
package/utils/navigation.ts
CHANGED
|
@@ -30,6 +30,7 @@ interface Group {
|
|
|
30
30
|
label: string;
|
|
31
31
|
entries: (Link | Group)[];
|
|
32
32
|
collapsed: boolean;
|
|
33
|
+
badge: Badge | undefined;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export type SidebarEntry = Link | Group;
|
|
@@ -75,6 +76,7 @@ function configItemToEntry(
|
|
|
75
76
|
label: pickLang(item.translations, localeToLang(locale)) || item.label,
|
|
76
77
|
entries: item.items.map((i) => configItemToEntry(i, currentPathname, locale, routes)),
|
|
77
78
|
collapsed: item.collapsed,
|
|
79
|
+
badge: item.badge,
|
|
78
80
|
};
|
|
79
81
|
}
|
|
80
82
|
}
|
|
@@ -101,6 +103,7 @@ function groupFromAutogenerateConfig(
|
|
|
101
103
|
label: pickLang(item.translations, localeToLang(locale)) || item.label,
|
|
102
104
|
entries: sidebarFromDir(tree, currentPathname, locale, subgroupCollapsed ?? item.collapsed),
|
|
103
105
|
collapsed: item.collapsed,
|
|
106
|
+
badge: item.badge,
|
|
104
107
|
};
|
|
105
108
|
}
|
|
106
109
|
|
|
@@ -231,6 +234,7 @@ function groupFromDir(
|
|
|
231
234
|
label: dirName,
|
|
232
235
|
entries,
|
|
233
236
|
collapsed,
|
|
237
|
+
badge: undefined,
|
|
234
238
|
};
|
|
235
239
|
}
|
|
236
240
|
|
package/utils/route-data.ts
CHANGED
|
@@ -5,9 +5,10 @@ import config from 'virtual:starlight/user-config';
|
|
|
5
5
|
import { generateToC, type TocItem } from './generateToC';
|
|
6
6
|
import { getFileCommitDate } from './git';
|
|
7
7
|
import { getPrevNextLinks, getSidebar, type SidebarEntry } from './navigation';
|
|
8
|
+
import { ensureTrailingSlash } from './path';
|
|
8
9
|
import type { Route } from './routing';
|
|
10
|
+
import { localizedId } from './slugs';
|
|
9
11
|
import { useTranslations } from './translations';
|
|
10
|
-
import { ensureTrailingSlash } from './path';
|
|
11
12
|
|
|
12
13
|
interface PageProps extends Route {
|
|
13
14
|
headings: MarkdownHeading[];
|
|
@@ -79,7 +80,7 @@ function getLastUpdated({ entry, id }: PageProps): Date | undefined {
|
|
|
79
80
|
return;
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
function getEditUrl({ entry, id }: PageProps): URL | undefined {
|
|
83
|
+
function getEditUrl({ entry, id, isFallback }: PageProps): URL | undefined {
|
|
83
84
|
const { editUrl } = entry.data;
|
|
84
85
|
// If frontmatter value is false, editing is disabled for this page.
|
|
85
86
|
if (editUrl === false) return;
|
|
@@ -90,8 +91,9 @@ function getEditUrl({ entry, id }: PageProps): URL | undefined {
|
|
|
90
91
|
url = editUrl;
|
|
91
92
|
} else if (config.editLink.baseUrl) {
|
|
92
93
|
const srcPath = project.srcDir.replace(project.root, '');
|
|
94
|
+
const filePath = isFallback ? localizedId(id, config.defaultLocale.locale) : id;
|
|
93
95
|
// If a base URL was added in Starlight config, synthesize the edit URL from it.
|
|
94
|
-
url = ensureTrailingSlash(config.editLink.baseUrl) + srcPath + 'content/docs/' +
|
|
96
|
+
url = ensureTrailingSlash(config.editLink.baseUrl) + srcPath + 'content/docs/' + filePath;
|
|
95
97
|
}
|
|
96
98
|
return url ? new URL(url) : undefined;
|
|
97
99
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import type { i18nSchemaOutput } from '../schemas/i18n';
|
|
3
|
+
import { createTranslationSystem } from './createTranslationSystem';
|
|
4
|
+
import type { StarlightConfig } from './user-config';
|
|
5
|
+
import type { AstroConfig } from 'astro';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Loads and creates a translation system from the file system.
|
|
9
|
+
* Only for use in integration code.
|
|
10
|
+
* In modules loaded by Vite/Astro, import [`useTranslations`](./translations.ts) instead.
|
|
11
|
+
*
|
|
12
|
+
* @see [`./translations.ts`](./translations.ts)
|
|
13
|
+
*/
|
|
14
|
+
export function createTranslationSystemFromFs(
|
|
15
|
+
opts: Pick<StarlightConfig, 'defaultLocale' | 'locales'>,
|
|
16
|
+
{ srcDir }: Pick<AstroConfig, 'srcDir'>
|
|
17
|
+
) {
|
|
18
|
+
/** All translation data from the i18n collection, keyed by `id`, which matches locale. */
|
|
19
|
+
let userTranslations: Record<string, i18nSchemaOutput> = {};
|
|
20
|
+
try {
|
|
21
|
+
const i18nDir = new URL('content/i18n/', srcDir);
|
|
22
|
+
// Load the user’s i18n directory
|
|
23
|
+
const files = fs.readdirSync(i18nDir, 'utf-8');
|
|
24
|
+
// Load the user’s i18n collection and ignore the error if it doesn’t exist.
|
|
25
|
+
userTranslations = Object.fromEntries(
|
|
26
|
+
files
|
|
27
|
+
.filter((file) => file.endsWith('.json'))
|
|
28
|
+
.map((file) => {
|
|
29
|
+
const id = file.slice(0, -5);
|
|
30
|
+
const data = JSON.parse(fs.readFileSync(new URL(file, i18nDir), 'utf-8'));
|
|
31
|
+
return [id, data] as const;
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
} catch (e: unknown) {
|
|
35
|
+
if (e instanceof Error && 'code' in e && e.code === 'ENOENT') {
|
|
36
|
+
// i18nDir doesn’t exist, so we ignore the error.
|
|
37
|
+
} else {
|
|
38
|
+
// Other errors may be meaningful, e.g. JSON syntax errors, so should be thrown.
|
|
39
|
+
throw e;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return createTranslationSystem(userTranslations, opts);
|
|
44
|
+
}
|
package/utils/translations.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getCollection } from 'astro:content';
|
|
2
2
|
import config from 'virtual:starlight/user-config';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
/** User-configured default locale. */
|
|
7
|
-
const defaultLocale = config.defaultLocale?.locale || 'root';
|
|
3
|
+
import type { i18nSchemaOutput } from '../schemas/i18n';
|
|
4
|
+
import { createTranslationSystem } from './createTranslationSystem';
|
|
8
5
|
|
|
9
6
|
/** All translation data from the i18n collection, keyed by `id`, which matches locale. */
|
|
10
|
-
let userTranslations: Record<string,
|
|
7
|
+
let userTranslations: Record<string, i18nSchemaOutput> = {};
|
|
11
8
|
try {
|
|
12
9
|
// Load the user’s i18n collection and ignore the error if it doesn’t exist.
|
|
13
10
|
userTranslations = Object.fromEntries(
|
|
@@ -15,24 +12,6 @@ try {
|
|
|
15
12
|
);
|
|
16
13
|
} catch {}
|
|
17
14
|
|
|
18
|
-
/** Default map of UI strings based on Starlight and user-configured defaults. */
|
|
19
|
-
const defaults = buildDictionary(
|
|
20
|
-
builtinTranslations.en!,
|
|
21
|
-
userTranslations.en,
|
|
22
|
-
builtinTranslations[defaultLocale] || builtinTranslations[stripLangRegion(defaultLocale)],
|
|
23
|
-
userTranslations[defaultLocale]
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Strips the region subtag from a BCP-47 lang string.
|
|
28
|
-
* @param {string} [lang]
|
|
29
|
-
* @example
|
|
30
|
-
* const lang = stripLangRegion('en-GB'); // => 'en'
|
|
31
|
-
*/
|
|
32
|
-
export function stripLangRegion(lang: string) {
|
|
33
|
-
return lang.replace(/-[a-zA-Z]{2}/, '');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
15
|
/**
|
|
37
16
|
* Generate a utility function that returns UI strings for the given `locale`.
|
|
38
17
|
* @param {string | undefined} [locale]
|
|
@@ -40,31 +19,4 @@ export function stripLangRegion(lang: string) {
|
|
|
40
19
|
* const t = useTranslations('en');
|
|
41
20
|
* const label = t('search.label'); // => 'Search'
|
|
42
21
|
*/
|
|
43
|
-
export
|
|
44
|
-
const lang = localeToLang(locale);
|
|
45
|
-
const dictionary = buildDictionary(
|
|
46
|
-
defaults,
|
|
47
|
-
builtinTranslations[lang] || builtinTranslations[stripLangRegion(lang)],
|
|
48
|
-
userTranslations[lang]
|
|
49
|
-
);
|
|
50
|
-
const t = <K extends keyof typeof dictionary>(key: K) => dictionary[key];
|
|
51
|
-
t.pick = (startOfKey: string) =>
|
|
52
|
-
Object.fromEntries(Object.entries(dictionary).filter(([k]) => k.startsWith(startOfKey)));
|
|
53
|
-
return t;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Build a dictionary by layering preferred translation sources. */
|
|
57
|
-
function buildDictionary(
|
|
58
|
-
base: (typeof builtinTranslations)[string],
|
|
59
|
-
...dictionaries: (CollectionEntry<'i18n'>['data'] | undefined)[]
|
|
60
|
-
) {
|
|
61
|
-
const dictionary = { ...base };
|
|
62
|
-
// Iterate over alternate dictionaries to avoid overwriting preceding values with `undefined`.
|
|
63
|
-
for (const dict of dictionaries) {
|
|
64
|
-
for (const key in dict) {
|
|
65
|
-
const value = dict[key as keyof typeof dict];
|
|
66
|
-
if (value) dictionary[key as keyof typeof dict] = value;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return dictionary;
|
|
70
|
-
}
|
|
22
|
+
export const useTranslations = createTranslationSystem(userTranslations, config);
|
package/utils/user-config.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from 'astro/zod';
|
|
2
2
|
import { parse as bcpParse, stringify as bcpStringify } from 'bcp-47';
|
|
3
|
-
import { BadgeConfigSchema } from '../schemas/badge';
|
|
4
3
|
import { ComponentConfigSchema } from '../schemas/components';
|
|
5
4
|
import { FaviconSchema } from '../schemas/favicon';
|
|
6
5
|
import { HeadConfigSchema } from '../schemas/head';
|