text-formatting.ts RawHistoryFolder
1import { z } from 'zod';
2import type { TextStyle } from '$lib/types/text-formatting';
3import { RETRO_FONTS } from '$lib/types/text-formatting';
4
5// Text style constraints (different from BBCode - style picker has smaller max)
6export const TEXT_STYLE_CONSTRAINTS = {
7 minFontSize: 8,
8 maxFontSize: 18,
9 maxGradientColors: 3
10};
11
12// Hex color schema
13const hexColorSchema = z.string().regex(/^#[0-9A-Fa-f]{6}$/, 'Invalid hex color format');
14
15// Font family schema - validates against RETRO_FONTS keys
16const fontFamilyKeys = Object.keys(RETRO_FONTS) as [string, ...string[]];
17const fontFamilySchema = z.enum(fontFamilyKeys);
18
19// Font size schema with clamping
20const fontSizeSchema = z.coerce
21 .number()
22 .int()
23 .transform((val) =>
24 Math.max(TEXT_STYLE_CONSTRAINTS.minFontSize, Math.min(TEXT_STYLE_CONSTRAINTS.maxFontSize, val))
25 );
26
27// Gradient schema - array of 1-3 hex colors
28const gradientSchema = z
29 .array(hexColorSchema)
30 .min(1)
31 .max(TEXT_STYLE_CONSTRAINTS.maxGradientColors)
32 .optional();
33
34// Full TextStyle schema
35const textStyleSchema = z.object({
36 fontFamily: fontFamilySchema.default('pixelated'),
37 fontSize: fontSizeSchema.default(14),
38 color: hexColorSchema.optional(),
39 gradient: gradientSchema,
40 bold: z.boolean().default(false),
41 italic: z.boolean().default(false),
42 underline: z.boolean().default(false),
43 strikethrough: z.boolean().default(false)
44});
45
46// Legacy helper functions (for backwards compatibility)
47export function validateHexColor(color: string): boolean {
48 return hexColorSchema.safeParse(color).success;
49}
50
51export function validateFontFamily(fontFamily: string): boolean {
52 return fontFamilySchema.safeParse(fontFamily).success;
53}
54
55export function validateFontSize(size: number): number {
56 const result = fontSizeSchema.safeParse(size);
57 return result.success ? result.data : 14;
58}
59
60export function validateGradient(gradient: unknown): string[] | undefined {
61 const result = gradientSchema.safeParse(gradient);
62 return result.success ? result.data : undefined;
63}
64
65export function validateTextStyle(style: unknown): TextStyle | null {
66 if (!style || typeof style !== 'object') return null;
67
68 const result = textStyleSchema.safeParse(style);
69 if (!result.success) return null;
70
71 return result.data as TextStyle;
72}
73
74export function sanitizeStyleData(styleData: unknown): TextStyle | undefined {
75 if (!styleData) return undefined;
76
77 // Handle JSON string input
78 let parsedData: unknown = styleData;
79 if (typeof styleData === 'string') {
80 try {
81 parsedData = JSON.parse(styleData);
82 } catch {
83 return undefined;
84 }
85 }
86
87 const validated = validateTextStyle(parsedData);
88 return validated || undefined;
89}