v0.12.1 1453d21

browser-controls.ts RawHistoryFolder

1import { NotificationService } from "./notifications.js";
2import { DebugConsoleService } from "./debug-console.js";
3
4export class BrowserControlsService {
5  private currentURL = "";
6  private notifications = new NotificationService();
7  private debugConsole = new DebugConsoleService();
8
9  constructor() {
10    // Debug logging with enhanced details
11    this.debugConsole.addLog(
12      "info",
13      "frontend",
14      "init",
15      "BrowserControlsService initializing...",
16    );
17    this.debugConsole.addLog(
18      "info",
19      "frontend",
20      "init",
21      "Running without Wails bindings",
22    );
23
24    this.initializeEventListeners();
25    this.debugConsole.addLog(
26      "info",
27      "frontend",
28      "init",
29      "BrowserControlsService initialized ✓",
30    );
31  }
32
33  private initializeEventListeners(): void {
34    // Keyboard event handler for browser controls
35    document.addEventListener("keydown", this.handleKeyboardEvent.bind(this));
36
37    // Mouse button handler for navigation
38    document.addEventListener("mousedown", this.handleMouseEvent.bind(this));
39
40    // URL change listener (for SPA navigation)
41    window.addEventListener("popstate", this.handleURLChange.bind(this));
42  }
43
44  private async handleKeyboardEvent(event: KeyboardEvent): Promise<void> {
45    // Skip F12 (debug console toggle)
46    if (event.key === "F12") {
47      return;
48    }
49
50    // Log all keyboard events to debug console
51    this.debugConsole.logKeyboardEvent(event);
52
53    const { ctrlKey, metaKey, shiftKey, altKey, key } = event;
54    const isCmd = ctrlKey || metaKey;
55
56    // Zoom controls handled natively by GTK; don't intercept
57    if (isCmd && (key === "+" || key === "=" || key === "-" || key === "0")) {
58      return; // allow native handler to process
59    }
60
61    // Copy URL
62    if (isCmd && shiftKey && key === "C") {
63      this.debugConsole.addLog(
64        "info",
65        "frontend",
66        "action",
67        "Copy URL triggered",
68      );
69      event.preventDefault();
70      await this.copyCurrentURL();
71      return;
72    }
73
74    // Navigation
75    if (altKey && key === "ArrowLeft") {
76      this.debugConsole.addLog(
77        "info",
78        "frontend",
79        "action",
80        "Go Back triggered",
81      );
82      event.preventDefault();
83      history.back();
84      return;
85    }
86
87    if (altKey && key === "ArrowRight") {
88      this.debugConsole.addLog(
89        "info",
90        "frontend",
91        "action",
92        "Go Forward triggered",
93      );
94      event.preventDefault();
95      history.forward();
96      return;
97    }
98  }
99
100  private async handleMouseEvent(event: MouseEvent): Promise<void> {
101    // Log all mouse events to debug console
102    this.debugConsole.logMouseEvent(event);
103
104    // Mouse button 3 (back button)
105    if (event.button === 3) {
106      this.debugConsole.addLog(
107        "info",
108        "frontend",
109        "action",
110        "Mouse Back button triggered",
111      );
112      event.preventDefault();
113      await this.goBack();
114      return;
115    }
116
117    // Mouse button 4 (forward button)
118    if (event.button === 4) {
119      this.debugConsole.addLog(
120        "info",
121        "frontend",
122        "action",
123        "Mouse Forward button triggered",
124      );
125      event.preventDefault();
126      await this.goForward();
127      return;
128    }
129  }
130
131  private handleURLChange(): void {
132    this.setCurrentURL(window.location.href);
133    this.loadSavedZoom();
134  }
135
136  // Zoom is handled natively; no JS-based zoom adjustments needed.
137
138  async copyCurrentURL(): Promise<void> {
139    try {
140      const url = this.getCurrentURL();
141      await navigator.clipboard.writeText(url);
142
143      // URL copied successfully - no backend notification needed
144
145      this.notifications.show("URL copied to clipboard!", "success");
146    } catch (error) {
147      console.error("Failed to copy URL:", error);
148      this.notifications.show("Failed to copy URL", "error");
149    }
150  }
151
152  async goBack(): Promise<void> {
153    this.debugConsole.addLog("info", "frontend", "service", "Calling GoBack");
154
155    try {
156      // await BrowserService.GoBack(); // TODO: Implement BrowserService
157      this.debugConsole.logServiceCall("BrowserService", "GoBack", []);
158      history.back();
159      this.debugConsole.addLog(
160        "info",
161        "frontend",
162        "navigation",
163        "Browser navigated back",
164      );
165    } catch (error) {
166      this.debugConsole.logServiceCall(
167        "BrowserService",
168        "GoBack",
169        [],
170        null,
171        error as Error,
172      );
173      console.error("Failed to go back:", error);
174    }
175  }
176
177  async goForward(): Promise<void> {
178    this.debugConsole.addLog(
179      "info",
180      "frontend",
181      "service",
182      "Calling GoForward",
183    );
184
185    try {
186      // await BrowserService.GoForward(); // TODO: Implement BrowserService
187      this.debugConsole.logServiceCall("BrowserService", "GoForward", []);
188      history.forward();
189      this.debugConsole.addLog(
190        "info",
191        "frontend",
192        "navigation",
193        "Browser navigated forward",
194      );
195    } catch (error) {
196      this.debugConsole.logServiceCall(
197        "BrowserService",
198        "GoForward",
199        [],
200        null,
201        error as Error,
202      );
203      console.error("Failed to go forward:", error);
204    }
205  }
206
207  getCurrentURL(): string {
208    return this.currentURL || window.location.href;
209  }
210
211  setCurrentURL(url: string): void {
212    this.currentURL = url;
213    this.updateWindowTitle(url);
214  }
215
216  private async updateWindowTitle(url: string): Promise<void> {
217    try {
218      // Extract domain or path for title
219      let title = "Dumber Browser";
220      if (url && url !== "/") {
221        const urlObj = new URL(url);
222        title = `Dumber - ${urlObj.hostname}`;
223      }
224
225      document.title = title;
226
227      // Backend title updates handled natively; no frontend call
228    } catch (error) {
229      console.error("Failed to update title:", error);
230    }
231  }
232
233  async loadSavedZoom(): Promise<void> {
234    // Zoom persistence applied natively on page load
235  }
236
237  async initialize(): Promise<void> {
238    // Check if there's an initial URL from backend (direct navigation)
239    // Set initial URL from browser location (homepage)
240    this.setCurrentURL(window.location.href);
241
242    // Load saved zoom level (only if we're staying on homepage)
243    // Zoom handled natively
244  }
245}

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