mirror of
https://github.com/sern-handler/website
synced 2026-06-16 12:52:20 +00:00
174 lines
5.8 KiB
TypeScript
174 lines
5.8 KiB
TypeScript
import { z } from 'astro/zod';
|
||
import type { SchemaContext } from 'astro:content';
|
||
import { HeadConfigSchema } from './schemas/head';
|
||
import { PrevNextLinkConfigSchema } from './schemas/prevNextLink';
|
||
import { TableOfContentsSchema } from './schemas/tableOfContents';
|
||
import { BadgeConfigSchema } from './schemas/badge';
|
||
import { HeroSchema } from './schemas/hero';
|
||
import { SidebarLinkItemHTMLAttributesSchema } from './schemas/sidebar';
|
||
export { i18nSchema } from './schemas/i18n';
|
||
|
||
/** Default content collection schema for Starlight’s `docs` collection. */
|
||
const StarlightFrontmatterSchema = (context: SchemaContext) =>
|
||
z.object({
|
||
/** The title of the current page. Required. */
|
||
title: z.string(),
|
||
|
||
/**
|
||
* A short description of the current page’s content. Optional, but recommended.
|
||
* A good description is 150–160 characters long and outlines the key content
|
||
* of the page in a clear and engaging way.
|
||
*/
|
||
description: z.string().optional(),
|
||
|
||
/**
|
||
* Custom URL where a reader can edit this page.
|
||
* Overrides the `editLink.baseUrl` global config if set.
|
||
*
|
||
* Can also be set to `false` to disable showing an edit link on this page.
|
||
*/
|
||
editUrl: z.union([z.string().url(), z.boolean()]).optional().default(true),
|
||
|
||
/** Set custom `<head>` tags just for this page. */
|
||
head: HeadConfigSchema(),
|
||
|
||
/** Override global table of contents configuration for this page. */
|
||
tableOfContents: TableOfContentsSchema().optional(),
|
||
|
||
/**
|
||
* Set the layout style for this page.
|
||
* Can be `'doc'` (the default) or `'splash'` for a wider layout without any sidebars.
|
||
*/
|
||
template: z.enum(['doc', 'splash']).default('doc'),
|
||
|
||
/** Display a hero section on this page. */
|
||
hero: HeroSchema(context).optional(),
|
||
|
||
/**
|
||
* The last update date of the current page.
|
||
* Overrides the `lastUpdated` global config or the date generated from the Git history.
|
||
*/
|
||
lastUpdated: z.union([z.date(), z.boolean()]).optional(),
|
||
|
||
/**
|
||
* The previous navigation link configuration.
|
||
* Overrides the `pagination` global config or the link text and/or URL.
|
||
*/
|
||
prev: PrevNextLinkConfigSchema(),
|
||
/**
|
||
* The next navigation link configuration.
|
||
* Overrides the `pagination` global config or the link text and/or URL.
|
||
*/
|
||
next: PrevNextLinkConfigSchema(),
|
||
|
||
sidebar: z
|
||
.object({
|
||
/**
|
||
* The order of this page in the navigation.
|
||
* Pages are sorted by this value in ascending order. Then by slug.
|
||
* If not provided, pages will be sorted alphabetically by slug.
|
||
* If two pages have the same order value, they will be sorted alphabetically by slug.
|
||
*/
|
||
order: z.number().optional(),
|
||
|
||
/**
|
||
* The label for this page in the navigation.
|
||
* Defaults to the page `title` if not set.
|
||
*/
|
||
label: z.string().optional(),
|
||
|
||
/**
|
||
* Prevents this page from being included in autogenerated sidebar groups.
|
||
*/
|
||
hidden: z.boolean().default(false),
|
||
/**
|
||
* Adds a badge to the sidebar link.
|
||
* Can be a string or an object with a variant and text.
|
||
* Variants include 'note', 'tip', 'caution', 'danger', 'success', and 'default'.
|
||
* Passing only a string defaults to the 'default' variant which uses the site accent color.
|
||
*/
|
||
badge: BadgeConfigSchema(),
|
||
/** HTML attributes to add to the sidebar link. */
|
||
attrs: SidebarLinkItemHTMLAttributesSchema(),
|
||
})
|
||
.default({}),
|
||
|
||
/** Display an announcement banner at the top of this page. */
|
||
banner: z
|
||
.object({
|
||
/** The content of the banner. Supports HTML syntax. */
|
||
content: z.string(),
|
||
})
|
||
.optional(),
|
||
|
||
/** Pagefind indexing for this page - set to false to disable. */
|
||
pagefind: z.boolean().default(true),
|
||
|
||
/**
|
||
* Indicates that this page is a draft and will not be included in production builds.
|
||
* Note that the page will still be available when running Astro in development mode.
|
||
*/
|
||
draft: z.boolean().default(false),
|
||
});
|
||
/** Type of Starlight’s default frontmatter schema. */
|
||
type DefaultSchema = ReturnType<typeof StarlightFrontmatterSchema>;
|
||
|
||
/** Plain object, union, and intersection Zod types. */
|
||
type BaseSchemaWithoutEffects =
|
||
| z.AnyZodObject
|
||
| z.ZodUnion<[BaseSchemaWithoutEffects, ...BaseSchemaWithoutEffects[]]>
|
||
| z.ZodDiscriminatedUnion<string, z.AnyZodObject[]>
|
||
| z.ZodIntersection<BaseSchemaWithoutEffects, BaseSchemaWithoutEffects>;
|
||
/** Base subset of Zod types that we support passing to the `extend` option. */
|
||
type BaseSchema = BaseSchemaWithoutEffects | z.ZodEffects<BaseSchemaWithoutEffects>;
|
||
|
||
/** Type that extends Starlight’s default schema with an optional, user-defined schema. */
|
||
type ExtendedSchema<T extends BaseSchema | never = never> = [T] extends [never]
|
||
? DefaultSchema
|
||
: T extends BaseSchema
|
||
? z.ZodIntersection<DefaultSchema, T>
|
||
: DefaultSchema;
|
||
|
||
interface DocsSchemaOpts<T extends BaseSchema> {
|
||
/**
|
||
* Extend Starlight’s schema with additional fields.
|
||
*
|
||
* @example
|
||
* // Extend the built-in schema with a Zod schema.
|
||
* docsSchema({
|
||
* extend: z.object({
|
||
* // Add a new field to the schema.
|
||
* category: z.enum(['tutorial', 'guide', 'reference']).optional(),
|
||
* }),
|
||
* })
|
||
*
|
||
* // Use the Astro image helper.
|
||
* docsSchema({
|
||
* extend: ({ image }) => {
|
||
* return z.object({
|
||
* cover: image(),
|
||
* });
|
||
* },
|
||
* })
|
||
*/
|
||
extend?: T | ((context: SchemaContext) => T);
|
||
}
|
||
|
||
/** Content collection schema for Starlight’s `docs` collection. */
|
||
export function docsSchema<T extends BaseSchema | never = never>(
|
||
...args: [DocsSchemaOpts<T>?]
|
||
): (context: SchemaContext) => ExtendedSchema<T> {
|
||
const [options = {}] = args;
|
||
const { extend } = options;
|
||
|
||
return (context: SchemaContext) => {
|
||
const UserSchema = typeof extend === 'function' ? extend(context) : extend;
|
||
|
||
return (
|
||
UserSchema
|
||
? StarlightFrontmatterSchema(context).and(UserSchema)
|
||
: StarlightFrontmatterSchema(context)
|
||
) as ExtendedSchema<T>;
|
||
};
|
||
}
|