main
 1import { defineCollection } from "astro:content";
 2import { glob } from "astro/loaders";
 3import { z } from "astro/zod";
 4import { slug as slugify } from "github-slugger";
 5import type { Locale } from "intlayer";
 6
 7import { SITE_CONFIG } from "@/config.ts";
 8
 9const typCollection = (base: string) =>
10  defineCollection({
11    loader: glob({
12      pattern: ["**/[^_]*.{typ,typst}", "!**/_*/**"],
13      base: `./src/contents/${base}`,
14      generateId: ({ entry, data }) => {
15        const slugFromData = data.slug as string | undefined;
16        const filename = entry.split("/").at(-1);
17        const isFlatEntry =
18          filename.split(".").length > 2 &&
19          SITE_CONFIG.locales.indexOf(filename.split(".").at(-2) as Locale) !==
20            -1;
21        const locale = filename.split(".").at(-2);
22        const slugFromEntry = isFlatEntry
23          ? filename.split(".").slice(0, -2).join("-")
24          : entry.split("/").at(-2);
25        return `${locale}/${slugify(slugFromData || slugFromEntry)}`;
26      },
27    }),
28    schema: z.object({
29      title: z.string(),
30      slug: z.string().optional(),
31      author: z.string().default(SITE_CONFIG.author),
32      desc: z.any().optional(),
33      date: z.string().transform((str) => {
34        const match = str.match(
35          /datetime\(\s*year:\s*(\d+),?\s*month:\s*(\d+),?\s*day:\s*(\d+)(?:,?\s*hour:\s*(\d+))?(?:,?\s*minute:\s*(\d+))?(?:,?\s*second:\s*(\d+))?,?\s*\)/,
36        );
37        if (match) {
38          const year = parseInt(match[1], 10);
39          const month = parseInt(match[2], 10);
40          const day = parseInt(match[3], 10);
41          const hour = match[4] ? parseInt(match[4], 10) : 0;
42          const minute = match[5] ? parseInt(match[5], 10) : 0;
43          const second = match[6] ? parseInt(match[6], 10) : 0;
44
45          return Temporal.ZonedDateTime.from({
46            year,
47            month,
48            day,
49            hour,
50            minute,
51            second,
52            timeZone: SITE_CONFIG.timeZone,
53          }).toString();
54        }
55        return z.NEVER;
56      }),
57      license: z
58        .xor([
59          z.object({
60            name: z.string(),
61            link: z.url(),
62          }),
63          z.string(),
64        ])
65        .default({
66          name: "CC BY-NC-SA 4.0",
67          link: "https://creativecommons.org/licenses/by-nc-sa/4.0/",
68        }),
69    }),
70  });
71
72export const collections = {
73  posts: typCollection("posts"),
74};