v0.8.0 0fa6785

app.ts RawHistoryFolder

1import type { HistoryEntry, SearchShortcut } from '../types/generated.js';
2
3export class AppService {
4  private history: HistoryEntry[] = [];
5  private shortcuts: Record<string, SearchShortcut> = {};
6
7  constructor() {}
8
9  async initialize(): Promise<void> {
10    try {
11      await Promise.all([
12        this.loadHistory(),
13        this.loadShortcuts()
14      ]);
15    } catch (error) {
16      console.error('Failed to initialize app:', error);
17    }
18  }
19
20  async loadHistory(): Promise<void> {
21    try {
22      // Use message bridge instead of fetch
23      return new Promise((resolve, reject) => {
24        // Set up response handler
25        window.__dumber_history_recent = (data: HistoryEntry[]) => {
26          this.history = Array.isArray(data) ? data : [];
27          resolve();
28        };
29
30        window.__dumber_history_error = (error: string) => {
31          console.error('Failed to load history:', error);
32          reject(new Error(error));
33        };
34
35        // Send message to Go backend
36        const bridge = window.webkit?.messageHandlers?.dumber;
37        if (bridge && typeof bridge.postMessage === 'function') {
38          bridge.postMessage(JSON.stringify({
39            type: 'history_recent',
40            limit: 50,
41            offset: 0
42          }));
43        } else {
44          reject(new Error('WebKit message handler not available'));
45        }
46      });
47    } catch (error) {
48      console.error('Failed to load history:', error);
49      // Mock history for development/fallback
50      this.history = [
51        { 
52          id: 1, 
53          url: 'https://github.com/wailsapp/wails', 
54          title: 'Wails Framework', 
55          visit_count: 3,
56          last_visited: new Date().toISOString(),
57          created_at: new Date().toISOString()
58        },
59        { 
60          id: 2, 
61          url: 'https://go.dev', 
62          title: 'The Go Programming Language', 
63          visit_count: 2,
64          last_visited: new Date().toISOString(),
65          created_at: new Date().toISOString()
66        },
67        { 
68          id: 3, 
69          url: 'https://developer.mozilla.org', 
70          title: 'MDN Web Docs', 
71          visit_count: 1,
72          last_visited: new Date().toISOString(),
73          created_at: new Date().toISOString()
74        }
75      ];
76    }
77  }
78
79  async loadShortcuts(): Promise<void> {
80    try {
81      const cfg = await fetch('/api/config').then(r => r.json());
82      const raw = cfg?.search_shortcuts || {};
83      // Normalize field casing from backend (supports URL/Description and url/description)
84      const normalized: Record<string, SearchShortcut> = {};
85      for (const [key, value] of Object.entries(raw)) {
86        const v = value as Record<string, unknown>;
87        normalized[key] = {
88          id: 0,
89          shortcut: key,
90          url_template: (v.url ?? v.URL ?? '') as string,
91          description: (v.description ?? v.Description ?? '') as string,
92          created_at: null
93        };
94      }
95      this.shortcuts = normalized;
96    } catch (error) {
97      console.error('Failed to load shortcuts:', error);
98      // Mock shortcuts for development/fallback
99      this.shortcuts = {
100        'g': { id: 0, shortcut: 'g', url_template: 'https://google.com/search?q={query}', description: 'Google Search', created_at: null },
101        'gh': { id: 0, shortcut: 'gh', url_template: 'https://github.com/search?q={query}', description: 'GitHub Search', created_at: null },
102        'so': { id: 0, shortcut: 'so', url_template: 'https://stackoverflow.com/search?q={query}', description: 'Stack Overflow', created_at: null },
103        'w': { id: 0, shortcut: 'w', url_template: 'https://en.wikipedia.org/wiki/Special:Search?search={query}', description: 'Wikipedia', created_at: null }
104      };
105    }
106  }
107
108  getHistory(): HistoryEntry[] {
109    return this.history;
110  }
111
112  getShortcuts(): Record<string, SearchShortcut> {
113    return this.shortcuts;
114  }
115
116  async copyToClipboard(text: string): Promise<void> {
117    try {
118      await navigator.clipboard.writeText(text);
119      console.log('Copied to clipboard:', text);
120    } catch (err) {
121      console.error('Failed to copy:', err);
122    }
123  }
124}

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