main f6d8405

away-window.svelte RawHistoryFolder

1<script lang="ts">
2	import { onMount } from 'svelte';
3	import { api } from '../../../convex/_generated/api';
4	import XpWindow from './xp-window.svelte';
5	import { desktop } from '$lib/states/desktop.svelte';
6	import { authQuery, useAimClient } from '$lib/aim/client';
7
8	const presets = [
9		'Je suis parti(e) manger',
10		'Au téléphone…',
11		'Parti(e) faire un tour, je reviens',
12		"En cours, j'ai pas le droit d'être là 😅",
13		'Je dors, laisse un message'
14	];
15	const me = authQuery(api.aim.getMe, () => ({}));
16	const client = useAimClient();
17	let text = $state('');
18	let custom = $state('');
19	let selected = $state('custom');
20	let pending = $state(false);
21	let error = $state('');
22	let initialized = $state(false);
23
24	onMount(() => {
25		custom = localStorage.getItem('pdraim-away-last') ?? '';
26	});
27	$effect(() => {
28		const data = me?.data;
29		if (!initialized && data) {
30			const current = data.awayMessage ?? '';
31			text = current || custom;
32			selected = presets.includes(current) ? current : 'custom';
33			initialized = true;
34		}
35	});
36
37	function selectPreset(event: Event) {
38		selected = (event.currentTarget as HTMLSelectElement).value;
39		text = selected === 'custom' ? custom : selected;
40	}
41	function editText() {
42		selected = 'custom';
43		custom = text;
44		localStorage.setItem('pdraim-away-last', custom);
45	}
46	async function apply(value?: string) {
47		pending = true;
48		error = '';
49		try {
50			await client.setAwayMessage(value);
51			if (value && selected === 'custom') localStorage.setItem('pdraim-away-last', value);
52			desktop.close('away');
53		} catch (err) {
54			error = err instanceof Error ? err.message : 'Impossible de modifier le message.';
55		} finally {
56			pending = false;
57		}
58	}
59</script>
60
61<XpWindow id="away" width={360} height={340} resizableWindow={false}>
62	<div class="content">
63		<label for="away-preset">Messages d’absence enregistrés</label>
64		<!-- Inline list box (like AIM) so the choices always stay inside the window -->
65		<select
66			id="away-preset"
67			class="preset-list"
68			size={presets.length + 1}
69			value={selected}
70			onchange={selectPreset}
71		>
72			{#each presets as preset (preset)}<option value={preset}>{preset}</option>{/each}
73			<option value="custom">Personnalisé…</option>
74		</select>
75		<label for="away-text">Votre message</label>
76		<textarea id="away-text" maxlength="300" bind:value={text} oninput={editText}></textarea>
77		<small>{text.length}/300</small>
78		{#if error}<span role="alert">{error}</span>{/if}
79		<div class="actions">
80			<button disabled={pending || !text.trim()} onclick={() => apply(text)}
81				>Je suis absent(e)</button
82			>
83			<button disabled={pending} onclick={() => apply(undefined)}>Je suis de retour</button>
84			<button onclick={() => desktop.close('away')}>Annuler</button>
85		</div>
86	</div>
87</XpWindow>
88
89<style>
90	.content {
91		display: flex;
92		flex-direction: column;
93		gap: 7px;
94		height: 100%;
95		font:
96			11px Tahoma,
97			'Pixelated MS Sans Serif',
98			sans-serif;
99	}
100	select,
101	textarea {
102		width: 100%;
103		box-sizing: border-box;
104		font: inherit;
105	}
106	.preset-list {
107		height: auto;
108		padding: 1px;
109		background-image: none;
110		border: 1px solid #7f9db9;
111		overflow: hidden;
112	}
113	.preset-list:focus {
114		background-color: #fff;
115		color: inherit;
116	}
117	.preset-list option {
118		padding: 1px 3px;
119		overflow: hidden;
120		text-overflow: ellipsis;
121		white-space: nowrap;
122	}
123	.preset-list option:checked {
124		background: #316ac5;
125		color: #fff;
126	}
127	textarea {
128		flex: 1;
129		min-height: 60px;
130		resize: none;
131		border: 1px solid #7f9db9;
132	}
133	small {
134		align-self: flex-end;
135	}
136	.actions {
137		display: flex;
138		gap: 4px;
139		flex-wrap: wrap;
140		justify-content: flex-end;
141		margin-top: auto;
142	}
143	[role='alert'] {
144		color: #a00;
145	}
146</style>

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