diff --git a/src/schemas/soapbox/soapbox-config.ts b/src/schemas/soapbox/soapbox-config.ts new file mode 100644 index 000000000..97cda600c --- /dev/null +++ b/src/schemas/soapbox/soapbox-config.ts @@ -0,0 +1,74 @@ +/* eslint sort-keys: "error" */ +import { z } from 'zod'; + +import { hexColorSchema } from '../utils'; + +export const cryptoAddressSchema = z.object({ + address: z.string(), + note: z.string().optional(), + ticker: z.string(), +}); + +export const footerItemSchema = z.object({ + title: z.string(), + url: z.string().url(), +}); + +export const promoItemSchema = z.object({ + icon: z.string(), + text: z.string(), + textLocales: z.record(z.string()).optional(), + url: z.string().url(), +}); + +/** + * Soapbox Config schema. + * All values must be optional. Defaults are set in a separate step. + */ +export const soapboxConfigSchema = z.object({ + accentColor: hexColorSchema.optional().catch(undefined), + allowedEmoji: z.string().array().optional().catch(undefined), + appleAppId: z.string().optional().catch(undefined), + authProvider: z.string().optional().catch(undefined), + authenticatedProfile: z.boolean().optional().catch(undefined), + brandColor: hexColorSchema.optional().catch(undefined), + copyright: z.string().optional().catch(undefined), + cryptoAddresses: cryptoAddressSchema.array().optional().catch(undefined), + cryptoDonatePanel: z.object({ + limit: z.number().nonnegative().optional().catch(undefined), + }).optional().catch(undefined), + defaultSettings: z.record(z.string(), z.unknown()).optional().catch(undefined), + displayCta: z.boolean().optional().catch(undefined), + displayFqn: z.boolean().optional().catch(undefined), + extensions: z.object({ + patron: z.object({ + enabled: z.boolean().optional().catch(undefined), + }).optional().catch(undefined), + }).optional().catch(undefined), + /** Whether to inject suggested profiles into the Home feed. */ + feedInjection: z.boolean().optional().catch(undefined), + gdpr: z.boolean().optional().catch(undefined), + gdprUrl: z.string().optional().catch(undefined), + greentext: z.boolean().optional().catch(undefined), + linkFooterMessage: z.boolean().optional().catch(undefined), + links: z.record(z.string(), z.string()).optional().catch(undefined), + logo: z.string().url().optional().catch(undefined), + logoDarkMode: z.string().url().optional().catch(undefined), + /** + * Whether to use the preview URL for media thumbnails. + * On some platforms this can be too blurry without additional configuration. + */ + mediaPreview: z.boolean().optional().catch(undefined), + navlinks: z.object({ + homeFooter: footerItemSchema.array().optional().catch(undefined), + }).optional().catch(undefined), + promoPanel: z.object({ + items: z.array(promoItemSchema), + }).optional().catch(undefined), + redirectRootNoLogin: z.string().optional().catch(undefined), + sentryDsn: z.string().url().optional().catch(undefined), + tileServer: z.string().optional().catch(undefined), + tileServerAttribution: z.string().optional().catch(undefined), + verifiedCanEditName: z.boolean().optional().catch(undefined), + verifiedIcon: z.string().url().optional().catch(undefined), +}); \ No newline at end of file diff --git a/src/schemas/utils.ts b/src/schemas/utils.ts index 34312a41d..aabf3d83e 100644 --- a/src/schemas/utils.ts +++ b/src/schemas/utils.ts @@ -47,4 +47,7 @@ function coerceObject(shape: T) { return z.object({}).passthrough().catch({}).pipe(z.object(shape)); } -export { filteredArray, makeCustomEmojiMap, emojiSchema, contentSchema, dateSchema, jsonSchema, mimeSchema, coerceObject }; \ No newline at end of file +/** Validates a hex color code. */ +const hexColorSchema = z.string().regex(/^#([a-f0-9]{3}|[a-f0-9]{4}|[a-f0-9]{6}|[a-f0-9]{8})$/i); + +export { filteredArray, hexColorSchema, makeCustomEmojiMap, emojiSchema, contentSchema, dateSchema, jsonSchema, mimeSchema, coerceObject }; \ No newline at end of file