@grove-dev/starlight 0.7.0 → 0.8.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/components/custom/ContainerSection.astro +1 -1
- package/components/custom/LinkButton.astro +7 -7
- package/components/custom/dropdown/Dropdown.astro +5 -5
- package/components/custom/dropdown/DropdownContent.astro +26 -26
- package/components/custom/dropdown/DropdownItem.astro +8 -8
- package/components/custom/dropdown/DropdownLabel.astro +4 -4
- package/components/custom/dropdown/DropdownTrigger.astro +12 -12
- package/components/custom/dropdown/index.ts +14 -14
- package/components/overrides/Footer.astro +3 -3
- package/components/overrides/Header.astro +5 -6
- package/components/overrides/Hero.astro +17 -17
- package/components/overrides/PageFrame.astro +11 -11
- package/components/overrides/PageTitle.astro +3 -3
- package/components/overrides/Search.astro +6 -6
- package/components/overrides/parts/Drawer.astro +5 -5
- package/components/overrides/parts/NavBar.astro +5 -5
- package/components/overrides/parts/SidebarSublist.astro +2 -2
- package/components/overrides/parts/toc/TableOfContentsList.astro +4 -4
- package/components/overrides/parts/toc/starlight-toc.ts +96 -98
- package/core/config/docs-schema.ts +20 -20
- package/core/config/expresive-code.ts +49 -51
- package/core/config/override.ts +36 -36
- package/core/config/schemas.ts +59 -59
- package/core/config/vite.ts +12 -12
- package/core/i18n.ts +113 -113
- package/core/plugin.ts +42 -42
- package/core/sidebar.ts +4 -4
- package/global.d.ts +3 -3
- package/package.json +1 -1
- package/schema.ts +39 -39
- package/styles/base.css +866 -907
- package/styles/theme.css +61 -61
- package/user-components.ts +1 -1
- package/virtual.d.ts +27 -27
package/core/config/vite.ts
CHANGED
|
@@ -2,19 +2,19 @@ import type { ViteUserConfig } from 'astro';
|
|
|
2
2
|
import type { GroveStarlightConfig } from './schemas';
|
|
3
3
|
|
|
4
4
|
export function vitePlugin(config: GroveStarlightConfig): VitePlugin {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const moduleId = 'virtual:grove-starlight-config';
|
|
6
|
+
const resolvedModuleId = `\0${moduleId}`;
|
|
7
|
+
const moduleContent = `export default ${JSON.stringify(config)}`;
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
return {
|
|
10
|
+
name: 'vite-plugin-grove-starlight',
|
|
11
|
+
load(id) {
|
|
12
|
+
return id === resolvedModuleId ? moduleContent : undefined;
|
|
13
|
+
},
|
|
14
|
+
resolveId(id) {
|
|
15
|
+
return id === moduleId ? resolvedModuleId : undefined;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
type VitePlugin = NonNullable<ViteUserConfig['plugins']>[number];
|
package/core/i18n.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export type LocaleLookup = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
/** BCP-47 tag from the active locale (`locales[x].lang`), e.g. `en`. */
|
|
3
|
+
lang?: string;
|
|
4
|
+
/** Locale path key (`Astro.currentLocale`), e.g. `en`. */
|
|
5
|
+
locale?: string;
|
|
6
|
+
/** Default locale BCP-47 tag or path used as fallback. */
|
|
7
|
+
defaultLang: string;
|
|
8
|
+
/** Default locale path key, e.g. `en`. */
|
|
9
|
+
defaultLocale?: string;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -18,63 +18,63 @@ export type LocaleLookup = {
|
|
|
18
18
|
* (`es-es`), without letting the default locale shadow an active case-insensitive hit.
|
|
19
19
|
*/
|
|
20
20
|
export function pickLocalized(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
dictionary: Record<string, string> | undefined,
|
|
22
|
+
candidates: Array<string | undefined>,
|
|
23
|
+
fallbacks: Array<string | undefined> = [],
|
|
24
24
|
): string | undefined {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
if (!dictionary) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const lowerMap = Object.fromEntries(
|
|
30
|
+
Object.entries(dictionary).map(([key, value]) => [key.toLowerCase(), value]),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const pickExact = (keys: Array<string | undefined>) => {
|
|
34
|
+
for (const key of keys) {
|
|
35
|
+
if (key && dictionary[key]) {
|
|
36
|
+
return dictionary[key];
|
|
37
|
+
}
|
|
27
38
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return undefined;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
pickExact(candidates) ??
|
|
58
|
-
pickCaseInsensitive(candidates) ??
|
|
59
|
-
pickExact(fallbacks) ??
|
|
60
|
-
pickCaseInsensitive(fallbacks)
|
|
61
|
-
);
|
|
39
|
+
return undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const pickCaseInsensitive = (keys: Array<string | undefined>) => {
|
|
43
|
+
for (const key of keys) {
|
|
44
|
+
if (!key) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const match = lowerMap[key.toLowerCase()];
|
|
49
|
+
if (match) {
|
|
50
|
+
return match;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
pickExact(candidates) ??
|
|
58
|
+
pickCaseInsensitive(candidates) ??
|
|
59
|
+
pickExact(fallbacks) ??
|
|
60
|
+
pickCaseInsensitive(fallbacks)
|
|
61
|
+
);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/** @deprecated Prefer `pickLocalized`. Kept for existing call sites/tests. */
|
|
65
65
|
export function pickLang(
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
dictionary: Record<string, string> | undefined,
|
|
67
|
+
lang: string,
|
|
68
68
|
): string | undefined {
|
|
69
|
-
|
|
69
|
+
return pickLocalized(dictionary, [lang]);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function activeKeys({ lang, locale }: LocaleLookup): Array<string | undefined> {
|
|
73
|
-
|
|
73
|
+
return [lang, locale];
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function fallbackKeys({ defaultLang, defaultLocale }: LocaleLookup): Array<string | undefined> {
|
|
77
|
-
|
|
77
|
+
return [defaultLang, defaultLocale];
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
@@ -85,25 +85,25 @@ function fallbackKeys({ defaultLang, defaultLocale }: LocaleLookup): Array<strin
|
|
|
85
85
|
* - Locale map style: `label: Record<BCP-47 | locale-path, string>`
|
|
86
86
|
*/
|
|
87
87
|
export function resolveNavLabel(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
label: string | Record<string, string>,
|
|
89
|
+
translations: Record<string, string> | undefined,
|
|
90
|
+
keys: LocaleLookup,
|
|
91
91
|
): string {
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
const primary = activeKeys(keys);
|
|
93
|
+
const fallback = fallbackKeys(keys);
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
if (typeof label === 'string') {
|
|
96
|
+
return pickLocalized(translations, primary, fallback) || label;
|
|
97
|
+
}
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
const resolved = pickLocalized(label, primary, fallback);
|
|
100
|
+
if (resolved) {
|
|
101
|
+
return resolved;
|
|
102
|
+
}
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Localized label must include a key for the default language "${keys.defaultLang}".`,
|
|
106
|
+
);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
/**
|
|
@@ -111,15 +111,15 @@ export function resolveNavLabel(
|
|
|
111
111
|
* Accepts a LocaleLookup or a bare lang string for convenience.
|
|
112
112
|
*/
|
|
113
113
|
export function resolveLabel(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
label: string,
|
|
115
|
+
translations: Record<string, string> | undefined,
|
|
116
|
+
langOrKeys: string | LocaleLookup,
|
|
117
117
|
): string {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
if (typeof langOrKeys === 'string') {
|
|
119
|
+
return pickLocalized(translations, [langOrKeys]) || label;
|
|
120
|
+
}
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
return resolveNavLabel(label, translations, langOrKeys);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/**
|
|
@@ -127,49 +127,49 @@ export function resolveLabel(
|
|
|
127
127
|
* Prefers the active language/locale, then falls back to the default language.
|
|
128
128
|
*/
|
|
129
129
|
export function resolveLocalizedString(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
value: string | Record<string, string>,
|
|
131
|
+
langOrKeys: string | LocaleLookup,
|
|
132
|
+
defaultLang?: string,
|
|
133
133
|
): string {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
134
|
+
if (typeof value === 'string') {
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const keys: LocaleLookup =
|
|
139
|
+
typeof langOrKeys === 'string'
|
|
140
|
+
? { lang: langOrKeys, defaultLang: defaultLang ?? langOrKeys }
|
|
141
|
+
: langOrKeys;
|
|
142
|
+
|
|
143
|
+
const resolved = pickLocalized(value, activeKeys(keys), fallbackKeys(keys));
|
|
144
|
+
if (resolved) {
|
|
145
|
+
return resolved;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Localized string must include a key for the default language "${keys.defaultLang}".`,
|
|
150
|
+
);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
/** Build a LocaleLookup from Starlight + Astro locale values. */
|
|
154
154
|
export function createLocaleLookup(options: {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
lang?: string;
|
|
156
|
+
locale?: string;
|
|
157
|
+
defaultLang?: string;
|
|
158
|
+
defaultLocale?: string;
|
|
159
159
|
}): LocaleLookup {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
160
|
+
const result: LocaleLookup = {
|
|
161
|
+
defaultLang: options.defaultLang || options.defaultLocale || 'en',
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
if (options.lang !== undefined) {
|
|
165
|
+
result.lang = options.lang;
|
|
166
|
+
}
|
|
167
|
+
if (options.locale !== undefined) {
|
|
168
|
+
result.locale = options.locale;
|
|
169
|
+
}
|
|
170
|
+
if (options.defaultLocale !== undefined) {
|
|
171
|
+
result.defaultLocale = options.defaultLocale;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return result;
|
|
175
175
|
}
|
package/core/plugin.ts
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
1
|
import type { StarlightPlugin } from '@astrojs/starlight/types';
|
|
2
|
-
import { override, COMPONENT_OVERRIDES } from './config/override';
|
|
3
2
|
import { expressiveCode } from './config/expresive-code';
|
|
4
|
-
import {
|
|
3
|
+
import { COMPONENT_OVERRIDES, override } from './config/override';
|
|
5
4
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
type GroveStarlightConfig,
|
|
6
|
+
GroveStarlightConfigSchema,
|
|
7
|
+
type GroveStarlightUserConfig,
|
|
9
8
|
} from './config/schemas';
|
|
9
|
+
import { vitePlugin } from './config/vite';
|
|
10
10
|
|
|
11
11
|
const parseConfig = (userConfig?: GroveStarlightUserConfig): GroveStarlightConfig => {
|
|
12
|
-
|
|
12
|
+
const parsedConfig = GroveStarlightConfigSchema.safeParse(userConfig ?? {});
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
if (!parsedConfig.success) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`The provided plugin configuration for grove-starlight is invalid.\n${parsedConfig.error.issues.map((issue) => issue.message).join('\n')}`,
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
return parsedConfig.data;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
const plugin = (userConfig: GroveStarlightUserConfig = {}): StarlightPlugin =>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
({
|
|
25
|
+
name: 'grove-starlight',
|
|
26
|
+
hooks: {
|
|
27
|
+
'config:setup': ({ config, logger, updateConfig, addIntegration }) => {
|
|
28
|
+
const pluginConfig = parseConfig(userConfig);
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
updateConfig({
|
|
31
|
+
components: override(config, pluginConfig, COMPONENT_OVERRIDES, logger),
|
|
32
|
+
customCss: [
|
|
33
|
+
...(config.customCss ?? []),
|
|
34
|
+
'@grove-dev/starlight/styles/layers',
|
|
35
|
+
'@grove-dev/starlight/styles/theme',
|
|
36
|
+
'@grove-dev/starlight/styles/base',
|
|
37
|
+
],
|
|
38
|
+
expressiveCode: expressiveCode(config),
|
|
39
|
+
});
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
});
|
|
41
|
+
addIntegration({
|
|
42
|
+
name: 'grove-starlight-integration',
|
|
43
|
+
hooks: {
|
|
44
|
+
'astro:config:setup': ({ updateConfig }) => {
|
|
45
|
+
updateConfig({
|
|
46
|
+
vite: { plugins: [vitePlugin(pluginConfig)] },
|
|
47
|
+
});
|
|
51
48
|
},
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
// 'i18n:setup': function ({ injectTranslations }) {
|
|
53
|
+
// injectTranslations(translations);
|
|
54
|
+
// },
|
|
55
|
+
},
|
|
56
|
+
}) satisfies StarlightPlugin;
|
|
57
57
|
|
|
58
58
|
export { plugin };
|
package/core/sidebar.ts
CHANGED
|
@@ -6,9 +6,9 @@ export type SidebarLink = Extract<SidebarEntry, { type: 'link' }>;
|
|
|
6
6
|
|
|
7
7
|
/** Every link reachable from `entries`, at any depth. */
|
|
8
8
|
export function flattenSidebar(entries: SidebarEntry[]): SidebarLink[] {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
return entries.flatMap((entry) =>
|
|
10
|
+
entry.type === 'group' ? flattenSidebar(entry.entries) : [entry],
|
|
11
|
+
);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -18,5 +18,5 @@ export function flattenSidebar(entries: SidebarEntry[]): SidebarLink[] {
|
|
|
18
18
|
* the reader would land on a page with no idea where they are in the tree.
|
|
19
19
|
*/
|
|
20
20
|
export function isSidebarGroupOpen(group: SidebarGroup): boolean {
|
|
21
|
-
|
|
21
|
+
return !group.collapsed || flattenSidebar(group.entries).some((link) => link.isCurrent);
|
|
22
22
|
}
|
package/global.d.ts
CHANGED
package/package.json
CHANGED
package/schema.ts
CHANGED
|
@@ -6,50 +6,50 @@ import { markDocsSchemaLoaded } from './core/config/docs-schema';
|
|
|
6
6
|
markDocsSchemaLoaded();
|
|
7
7
|
|
|
8
8
|
export const heroLayoutSchema = z
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
.enum(['centered', 'centered-top', 'split-left', 'split-right', 'banner'])
|
|
10
|
+
.default('centered')
|
|
11
|
+
.describe(
|
|
12
|
+
'The layout of the hero section. "centered" places the image below the text, "centered-top" places it above, "split-left" places text left and image right, "split-right" places text right and image left.',
|
|
13
|
+
);
|
|
14
14
|
|
|
15
15
|
export type HeroLayout = z.infer<typeof heroLayoutSchema>;
|
|
16
16
|
|
|
17
17
|
export const ExtendDocsSchema = z.object({
|
|
18
|
-
|
|
18
|
+
hero: z
|
|
19
|
+
.object({
|
|
20
|
+
layout: heroLayoutSchema,
|
|
21
|
+
announcement: z
|
|
19
22
|
.object({
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
.object({
|
|
23
|
-
text: z.string(),
|
|
24
|
-
link: z.string(),
|
|
25
|
-
})
|
|
26
|
-
.optional(),
|
|
27
|
-
actions: z
|
|
28
|
-
.array(
|
|
29
|
-
z.object({
|
|
30
|
-
/**
|
|
31
|
-
* Button style to use. Starlight's own `primary` and `minimal` are accepted
|
|
32
|
-
* as aliases of `default` and `ghost` so existing frontmatter keeps working.
|
|
33
|
-
*
|
|
34
|
-
* Requires `@astrojs/starlight >= 0.41.4`, which is the first version whose
|
|
35
|
-
* `docsSchema({ extend })` deep-merges instead of intersecting — an
|
|
36
|
-
* intersection cannot widen an enum Starlight already declares.
|
|
37
|
-
*/
|
|
38
|
-
variant: z
|
|
39
|
-
.enum([
|
|
40
|
-
'default',
|
|
41
|
-
'link',
|
|
42
|
-
'secondary',
|
|
43
|
-
'outline',
|
|
44
|
-
'ghost',
|
|
45
|
-
'destructive',
|
|
46
|
-
'primary',
|
|
47
|
-
'minimal',
|
|
48
|
-
])
|
|
49
|
-
.default('default'),
|
|
50
|
-
})
|
|
51
|
-
)
|
|
52
|
-
.default([]),
|
|
23
|
+
text: z.string(),
|
|
24
|
+
link: z.string(),
|
|
53
25
|
})
|
|
54
26
|
.optional(),
|
|
27
|
+
actions: z
|
|
28
|
+
.array(
|
|
29
|
+
z.object({
|
|
30
|
+
/**
|
|
31
|
+
* Button style to use. Starlight's own `primary` and `minimal` are accepted
|
|
32
|
+
* as aliases of `default` and `ghost` so existing frontmatter keeps working.
|
|
33
|
+
*
|
|
34
|
+
* Requires `@astrojs/starlight >= 0.41.4`, which is the first version whose
|
|
35
|
+
* `docsSchema({ extend })` deep-merges instead of intersecting — an
|
|
36
|
+
* intersection cannot widen an enum Starlight already declares.
|
|
37
|
+
*/
|
|
38
|
+
variant: z
|
|
39
|
+
.enum([
|
|
40
|
+
'default',
|
|
41
|
+
'link',
|
|
42
|
+
'secondary',
|
|
43
|
+
'outline',
|
|
44
|
+
'ghost',
|
|
45
|
+
'destructive',
|
|
46
|
+
'primary',
|
|
47
|
+
'minimal',
|
|
48
|
+
])
|
|
49
|
+
.default('default'),
|
|
50
|
+
}),
|
|
51
|
+
)
|
|
52
|
+
.default([]),
|
|
53
|
+
})
|
|
54
|
+
.optional(),
|
|
55
55
|
});
|