main f6d8405

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 8

↑↓ moveenter openesc close