v0.23.1 6c872d5

HistorySearch.svelte RawHistoryFolder

1<script lang="ts">
2  import { onMount } from 'svelte';
3  import { homepageState } from '../state.svelte';
4  import { handleSearchInput } from '../keyboard';
5
6  interface Props {
7    placeholder?: string;
8    autofocus?: boolean;
9  }
10
11  let { placeholder = 'Search history...', autofocus = false }: Props = $props();
12
13  let inputRef = $state<HTMLInputElement | null>(null);
14
15  // Auto-focus on mount if requested
16  onMount(() => {
17    if (autofocus && inputRef) {
18      inputRef.focus();
19    }
20  });
21
22  const handleInput = (e: Event) => {
23    const target = e.target as HTMLInputElement;
24    handleSearchInput(target.value);
25  };
26
27  const handleKeyDown = (e: KeyboardEvent) => {
28    switch (e.key) {
29      case 'Escape':
30        e.preventDefault();
31        e.stopPropagation();
32        if (homepageState.historySearchQuery) {
33          // First Esc: clear search
34          homepageState.setHistorySearchQuery('');
35        } else {
36          // Second Esc (or if no query): blur input
37          inputRef?.blur();
38        }
39        break;
40      case 'ArrowDown':
41      case 'j':
42        if (e.key === 'j' && !e.ctrlKey) break; // Only Ctrl+j or ArrowDown
43        e.preventDefault();
44        homepageState.focusNext();
45        inputRef?.blur();
46        break;
47      case 'ArrowUp':
48      case 'k':
49        if (e.key === 'k' && !e.ctrlKey) break; // Only Ctrl+k or ArrowUp
50        e.preventDefault();
51        // Don't move focus - already at top
52        break;
53    }
54  };
55
56  // Focus input when focusedIndex becomes -1 (user pressed up from first item)
57  $effect(() => {
58    if (homepageState.focusedIndex === -1 && homepageState.activePanel === 'history') {
59      inputRef?.focus();
60    }
61  });
62
63  const clearSearch = () => {
64    homepageState.setHistorySearchQuery('');
65    inputRef?.focus();
66  };
67</script>
68
69<div class="history-search">
70  <div class="search-icon"></div>
71  <input
72    bind:this={inputRef}
73    type="text"
74    class="search-input"
75    {placeholder}
76    value={homepageState.historySearchQuery}
77    oninput={handleInput}
78    onkeydown={handleKeyDown}
79  />
80  {#if homepageState.historySearching}
81    <div class="search-spinner"></div>
82  {:else if homepageState.historySearchQuery}
83    <button
84      class="clear-btn"
85      type="button"
86      onclick={clearSearch}
87      title="Clear search (Esc)"
88    >
89      ×
90    </button>
91  {:else}
92    <kbd class="search-hint">/</kbd>
93  {/if}
94</div>
95
96<style>
97  .history-search {
98    display: flex;
99    align-items: center;
100    gap: 0.6rem;
101    padding: 0.5rem 0.75rem;
102    border-width: 1px;
103    border-style: solid;
104    border-color: var(--border);
105    background: var(--background);
106    transition: border-color 150ms ease;
107  }
108
109  .history-search:focus-within {
110    border-color: var(--primary, #4ade80);
111  }
112
113  .search-icon::before {
114    content: '';
115    font-size: 0.85rem;
116    color: var(--muted-foreground);
117  }
118
119  .search-input {
120    flex: 1;
121    border: none;
122    background: transparent;
123    color: var(--foreground);
124    font-family: inherit;
125    font-size: 0.8rem;
126    outline: none;
127  }
128
129  .search-input::placeholder {
130    color: var(--muted-foreground);
131    letter-spacing: 0.05em;
132  }
133
134  .search-spinner {
135    width: 14px;
136    height: 14px;
137    border-width: 2px;
138    border-style: solid;
139    border-color: var(--border);
140    border-top-color: var(--primary, #4ade80);
141    border-radius: 50%;
142    animation: spin 0.8s linear infinite;
143  }
144
145  @keyframes spin {
146    to { transform: rotate(360deg); }
147  }
148
149  .clear-btn {
150    width: 20px;
151    height: 20px;
152    display: flex;
153    align-items: center;
154    justify-content: center;
155    border-width: 1px;
156    border-style: solid;
157    border-color: var(--border);
158    background: transparent;
159    color: var(--muted-foreground);
160    font-size: 0.9rem;
161    cursor: pointer;
162    transition: all 100ms ease;
163  }
164
165  .clear-btn:hover {
166    color: var(--foreground);
167    border-color: var(--foreground);
168  }
169
170  .search-hint {
171    font-size: 0.6rem;
172    padding: 0.15rem 0.35rem;
173    border-width: 1px;
174    border-style: solid;
175    border-color: var(--border);
176    background: color-mix(in srgb, var(--card) 30%, transparent);
177    color: var(--muted-foreground);
178  }
179</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 8

Tags 42

↑↓ moveenter openesc close