Skip to content
File

Blob: src/shared/sites/island-schemas.ts

typescript88 lines
1import { z } from "zod/mini";
2import { MAX_IMAGE_BRIGHTNESS, MIN_IMAGE_BRIGHTNESS } from "@/shared/editor/schema/image-model";
3 
4const OutlineLevelSchema = z.union([
5 z.literal(1),
6 z.literal(2),
7 z.literal(3),
8 z.literal(4),
9 z.literal(5),
10 z.literal(6),
11]);
12 
13export const OutlineItemSchema = z.object({
14 id: z.string(),
15 text: z.string(),
16 level: OutlineLevelSchema,
17 href: z.optional(z.string()),
18});
19 
20export const SitesImagePropsSchema = z.object({
21 bid: z.optional(z.nullable(z.string())),
22 src: z.string().check(z.minLength(1)),
23 alt: z.optional(z.nullable(z.string())),
24 title: z.optional(z.nullable(z.string())),
25 align: z._default(z.enum(["left", "center", "right"]), "left"),
26 width: z.optional(z.nullable(z.number().check(z.positive()))),
27 naturalWidth: z.optional(z.nullable(z.number().check(z.positive()))),
28 naturalHeight: z.optional(z.nullable(z.number().check(z.positive()))),
29 brightness: z.optional(z.number().check(z.minimum(MIN_IMAGE_BRIGHTNESS), z.maximum(MAX_IMAGE_BRIGHTNESS))),
30});
31 
32export type SitesImageProps = z.infer<typeof SitesImagePropsSchema>;
33 
34export const CopyCodeButtonPropsSchema = z.strictObject({});
35 
36export type CopyCodeButtonProps = z.infer<typeof CopyCodeButtonPropsSchema>;
37 
38export const SiteOutlineControllerPropsSchema = z.object({
39 items: z.array(OutlineItemSchema),
40});
41 
42export type SiteOutlineControllerProps = z.infer<typeof SiteOutlineControllerPropsSchema>;
43 
44export const ISLAND_PROP_SCHEMAS = {
45 "sites-image": SitesImagePropsSchema,
46 "copy-code": CopyCodeButtonPropsSchema,
47 "site-outline-controller": SiteOutlineControllerPropsSchema,
48} as const;
49 
50export type IslandName = keyof typeof ISLAND_PROP_SCHEMAS;
51export type IslandPropsByName = {
52 [K in IslandName]: z.infer<(typeof ISLAND_PROP_SCHEMAS)[K]>;
53};
54 
55export function isIslandName(name: string | undefined): name is IslandName {
56 return name === "sites-image" || name === "copy-code" || name === "site-outline-controller";
57}
58 
59type SafeParseSchema<T> = {
60 safeParse(value: unknown): { success: true; data: T } | { success: false };
61};
62 
63function parseWithSchema<T>(schema: SafeParseSchema<T>, value: unknown): T | null {
64 const parsed = schema.safeParse(value);
65 return parsed.success ? parsed.data : null;
66}
67 
68export function readIslandProps(name: "sites-image", text: string): SitesImageProps | null;
69export function readIslandProps(name: "copy-code", text: string): CopyCodeButtonProps | null;
70export function readIslandProps(name: "site-outline-controller", text: string): SiteOutlineControllerProps | null;
71export function readIslandProps(name: IslandName, text: string): IslandPropsByName[IslandName] | null {
72 let value: unknown;
73 try {
74 value = JSON.parse(text);
75 } catch {
76 return null;
77 }
78 
79 switch (name) {
80 case "sites-image":
81 return parseWithSchema(SitesImagePropsSchema, value);
82 case "copy-code":
83 return parseWithSchema(CopyCodeButtonPropsSchema, value);
84 case "site-outline-controller":
85 return parseWithSchema(SiteOutlineControllerPropsSchema, value);
86 }
87}