v0.27.1 c78ce83

ShortcutsTable.svelte RawHistoryFolder

1<script lang="ts">
2  import { Check, Plus, Trash2, X } from "@lucide/svelte";
3  import { Button } from "$lib/components/ui/button";
4  import { Input } from "$lib/components/ui/input";
5  import * as Table from "$lib/components/ui/table";
6
7  type SearchShortcut = {
8    url: string;
9    description: string;
10  };
11
12  interface Props {
13    shortcuts: Record<string, SearchShortcut>;
14    onUpdate: (shortcuts: Record<string, SearchShortcut>) => void;
15  }
16
17  let { shortcuts, onUpdate }: Props = $props();
18
19  let editingKey = $state<string | null>(null);
20  let draftKey = $state("");
21  let draftUrl = $state("");
22  let draftDescription = $state("");
23
24  let newKey = $state("");
25  let newUrl = $state("");
26  let newDescription = $state("");
27
28  const entries = $derived(
29    Object.entries(shortcuts ?? {}).sort(([a], [b]) => a.localeCompare(b)),
30  );
31
32  function startEdit(key: string, shortcut: SearchShortcut) {
33    editingKey = key;
34    draftKey = key;
35    draftUrl = shortcut.url;
36    draftDescription = shortcut.description;
37  }
38
39  function cancelEdit() {
40    editingKey = null;
41    draftKey = "";
42    draftUrl = "";
43    draftDescription = "";
44  }
45
46  function commitEdit() {
47    if (!editingKey) return;
48    const nextKey = draftKey.trim();
49    const nextUrl = draftUrl.trim();
50    const nextDescription = draftDescription.trim();
51
52    const updated: Record<string, SearchShortcut> = { ...shortcuts };
53    delete updated[editingKey];
54    if (nextKey) {
55      updated[nextKey] = { url: nextUrl, description: nextDescription };
56    }
57    onUpdate(updated);
58    cancelEdit();
59  }
60
61  function removeShortcut(key: string) {
62    const updated: Record<string, SearchShortcut> = { ...shortcuts };
63    delete updated[key];
64    onUpdate(updated);
65  }
66
67  function addShortcut() {
68    const key = newKey.trim();
69    if (!key) return;
70    const updated: Record<string, SearchShortcut> = { ...shortcuts };
71    updated[key] = { url: newUrl.trim(), description: newDescription.trim() };
72    onUpdate(updated);
73    newKey = "";
74    newUrl = "";
75    newDescription = "";
76  }
77</script>
78
79<div class="space-y-4">
80  <Table.Root class="rounded-md border border-border">
81    <Table.Header>
82      <Table.Row>
83        <Table.Head class="w-[140px]">Prefix</Table.Head>
84        <Table.Head>URL Template</Table.Head>
85        <Table.Head>Description</Table.Head>
86        <Table.Head class="w-[160px] text-right">Actions</Table.Head>
87      </Table.Row>
88    </Table.Header>
89    <Table.Body>
90      {#if entries.length === 0}
91        <Table.Row>
92          <Table.Cell colspan={4} class="py-8 text-center text-sm text-muted-foreground">
93            No shortcuts yet. Add one below.
94          </Table.Cell>
95        </Table.Row>
96      {/if}
97      {#each entries as [key, shortcut] (key)}
98        <Table.Row>
99          {#if editingKey === key}
100            <Table.Cell>
101              <Input bind:value={draftKey} class="h-8" />
102            </Table.Cell>
103            <Table.Cell>
104              <Input bind:value={draftUrl} class="h-8" />
105            </Table.Cell>
106            <Table.Cell>
107              <Input bind:value={draftDescription} class="h-8" />
108            </Table.Cell>
109            <Table.Cell class="text-right">
110              <div class="flex justify-end gap-2">
111                <Button variant="ghost" size="icon" onclick={commitEdit} type="button" aria-label="Save">
112                  <Check size={16} />
113                </Button>
114                <Button variant="ghost" size="icon" onclick={cancelEdit} type="button" aria-label="Cancel">
115                  <X size={16} />
116                </Button>
117              </div>
118            </Table.Cell>
119          {:else}
120            <Table.Cell class="font-mono text-sm">{key}</Table.Cell>
121            <Table.Cell class="truncate max-w-[360px]">{shortcut.url}</Table.Cell>
122            <Table.Cell class="text-muted-foreground">{shortcut.description}</Table.Cell>
123            <Table.Cell class="text-right">
124              <div class="flex justify-end gap-2">
125                <Button
126                  variant="outline"
127                  size="sm"
128                  onclick={() => startEdit(key, shortcut)}
129                  type="button"
130                >
131                  Edit
132                </Button>
133                <Button
134                  variant="ghost"
135                  size="icon"
136                  onclick={() => removeShortcut(key)}
137                  type="button"
138                  aria-label={`Delete ${key}`}
139                >
140                  <Trash2 size={16} />
141                </Button>
142              </div>
143            </Table.Cell>
144          {/if}
145        </Table.Row>
146      {/each}
147    </Table.Body>
148  </Table.Root>
149
150  <div class="grid gap-3 md:grid-cols-[140px_1fr_1fr_140px] items-end">
151    <div class="space-y-1">
152      <span class="text-xs font-semibold uppercase tracking-[0.2em] text-muted-foreground">Prefix</span>
153      <Input bind:value={newKey} placeholder="g" />
154    </div>
155    <div class="space-y-1">
156      <span class="text-xs font-semibold uppercase tracking-[0.2em] text-muted-foreground">URL Template</span>
157      <Input bind:value={newUrl} placeholder="https://www.google.com/search?q=%s" />
158    </div>
159    <div class="space-y-1">
160      <span class="text-xs font-semibold uppercase tracking-[0.2em] text-muted-foreground">Description</span>
161      <Input bind:value={newDescription} placeholder="Google" />
162    </div>
163    <div class="flex md:justify-end">
164      <Button variant="default" onclick={addShortcut} type="button" class="w-full md:w-auto">
165        <Plus size={16} />
166        Add Shortcut
167      </Button>
168    </div>
169  </div>
170</div>

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 8

Tags 42

↑↓ moveenter openesc close