main f6d8405

client.ts RawHistoryFolder

1/**
2 * API Client for SvelteKit endpoints
3 * Provides typed helper functions for all API calls
4 */
5
6import type { SendMessageRequest, SendMessageResponse } from '$lib/types/payloads';
7import type { TextStyle, UserTextPreferences } from '$lib/types/text-formatting';
8
9// ============ TYPES ============
10
11export interface ApiResponse<T> {
12	success: boolean;
13	data?: T;
14	error?: string;
15}
16
17export interface TextPreferencesResponse {
18	success: boolean;
19	preferences: UserTextPreferences | null;
20}
21
22// ============ BASE CLIENT ============
23
24async function request<T>(endpoint: string, options: RequestInit = {}): Promise<ApiResponse<T>> {
25	try {
26		const response = await fetch(endpoint, {
27			...options,
28			credentials: 'include',
29			headers: {
30				'Content-Type': 'application/json',
31				...(options.headers ?? {})
32			}
33		});
34
35		const data = await response.json();
36
37		if (!response.ok) {
38			return {
39				success: false,
40				error: data.error || `Request failed with status ${response.status}`,
41				data
42			};
43		}
44
45		return { success: true, data };
46	} catch (error) {
47		return {
48			success: false,
49			error: error instanceof Error ? error.message : 'Unknown error'
50		};
51	}
52}
53
54// ============ TEXT PREFERENCES ============
55
56export const textPreferences = {
57	/**
58	 * Get current user's text preferences
59	 */
60	async get(): Promise<UserTextPreferences | null> {
61		const result = await request<TextPreferencesResponse>('/api/user/text-preferences');
62		if (result.success && result.data?.preferences) {
63			return result.data.preferences;
64		}
65		return null;
66	},
67
68	/**
69	 * Save user's text preferences
70	 */
71	async save(preferences: {
72		defaultStyle: TextStyle;
73		allowFormatting: boolean;
74		maxMessageLength: number;
75	}): Promise<boolean> {
76		const result = await request('/api/user/text-preferences', {
77			method: 'POST',
78			body: JSON.stringify(preferences)
79		});
80		return result.success;
81	}
82};
83
84// ============ MENTIONS ============
85
86export const mentions = {
87	/**
88	 * Update the timestamp of the last read mention
89	 */
90	async markRead(timestamp: number): Promise<boolean> {
91		const result = await request('/api/chat/mention-read', {
92			method: 'POST',
93			body: JSON.stringify({ timestamp })
94		});
95		return result.success;
96	}
97};
98
99// ============ SESSION ============
100
101export interface LoginResponse {
102	success: boolean;
103	user?: {
104		id: string;
105		nickname: string;
106		status: string;
107	};
108	error?: string;
109}
110
111export const session = {
112	/**
113	 * Login with username and password
114	 */
115	async login(username: string, password: string): Promise<LoginResponse> {
116		const result = await request<LoginResponse>('/api/session/login', {
117			method: 'POST',
118			body: JSON.stringify({ username, password })
119		});
120		return result.data ?? { success: false, error: result.error };
121	},
122
123	/**
124	 * Register a new user
125	 */
126	async register(
127		username: string,
128		password: string,
129		confirmPassword: string,
130		captchaAnswer: string,
131		turnstileToken?: string
132	): Promise<LoginResponse> {
133		const result = await request<LoginResponse>('/api/register', {
134			method: 'POST',
135			body: JSON.stringify({
136				suUsername: username,
137				suPassword: password,
138				suConfirmPassword: confirmPassword,
139				captchaAnswer,
140				...(turnstileToken ? { turnstileToken } : {})
141			})
142		});
143		return result.data ?? { success: false, error: result.error };
144	}
145};
146
147// ============ CHAT ============
148
149export interface SendMessageParams {
150	content: string;
151	userId: string;
152	chatRoomId?: string;
153	type?: SendMessageRequest['type'];
154	styleData?: string;
155}
156
157export const chat = {
158	/**
159	 * Send a message to a chat room
160	 */
161	async sendMessage(params: SendMessageParams): Promise<SendMessageResponse> {
162		const result = await request<SendMessageResponse>('/api/chat/messages', {
163			method: 'POST',
164			body: JSON.stringify(params)
165		});
166		if (result.data) {
167			return result.data;
168		}
169		return {
170			success: false,
171			error: result.error ?? 'Failed to send message'
172		};
173	}
174};
175
176// ============ EXPORT ============
177
178export const api = {
179	textPreferences,
180	mentions,
181	session,
182	chat
183};
184
185export default api;

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