v0.12.1 1453d21

messaging.ts RawHistoryFolder

1/**
2 * Omnibox Message Bridge
3 *
4 * TypeScript interface for Go-JavaScript communication
5 */
6
7import type { OmniboxMessage, OmniboxMessageBridge, Suggestion, SearchShortcut } from "./types";
8import { omniboxStore } from "./stores.svelte.ts";
9
10export class OmniboxBridge implements OmniboxMessageBridge {
11  // Forward messages to the native WebKit message handler when available
12  // Fallback: for navigate, perform direct location change
13  postMessage(msg: OmniboxMessage): void {
14    console.log("📤 [DEBUG] Posting message to backend:", msg);
15    // NOTE: Do NOT detach postMessage from its receiver. In WKWebView,
16    // postMessage must be called on the UserMessageHandler instance.
17    const bridge = window.webkit?.messageHandlers?.dumber;
18    if (bridge && typeof bridge.postMessage === "function") {
19      try {
20        console.log("📱 [DEBUG] Using webkit message handler");
21        bridge.postMessage(JSON.stringify(msg));
22        return;
23      } catch (e) {
24        console.warn(
25          "postMessage to native handler failed, using fallback:",
26          e,
27        );
28      }
29    }
30    console.log("⚠️ [DEBUG] No webkit bridge available");
31    // Fallback navigation if no native bridge is available
32    if (msg.type === "navigate" && typeof msg.url === "string" && msg.url) {
33      try {
34        window.location.href = msg.url;
35      } catch (e) {
36        console.error("Fallback navigation failed:", e);
37      }
38    }
39  }
40
41  /**
42   * Update suggestions from Go backend
43   */
44  setSuggestions(suggestions: Suggestion[]): void {
45    console.log("📝 [DEBUG] Received suggestions from backend:", suggestions);
46    omniboxStore.updateSuggestions(suggestions);
47  }
48
49  /**
50   * Update search shortcuts from Go backend
51   */
52  setSearchShortcuts(shortcuts: Record<string, SearchShortcut>): void {
53    console.log("📝 [DEBUG] Received search shortcuts from backend:", shortcuts);
54    omniboxStore.updateSearchShortcuts(shortcuts);
55  }
56
57  /**
58   * Handle navigation request
59   */
60  navigate(url: string): void {
61    console.log("🚀 Omnibox navigate called with:", url);
62    this.postMessage({ type: "navigate", url });
63  }
64
65  /**
66   * Handle search query
67   */
68  query(searchTerm: string, limit?: number): void {
69    const q = searchTerm ?? "";
70    const lim = limit || omniboxStore.config.defaultLimit;
71    console.log("🔍 [DEBUG] Sending query to backend:", { q, limit: lim });
72    // Send to native handler; Go will compute suggestions and call setSuggestions
73    this.postMessage({ type: "query", q, limit: lim });
74  }
75
76  /**
77   * Fetch search shortcuts from backend via messaging bridge
78   */
79  async fetchSearchShortcuts(): Promise<void> {
80    return new Promise((resolve, reject) => {
81      // Set up one-time response handler
82      const originalCallback = (window as any).__dumber_search_shortcuts;
83      (window as any).__dumber_search_shortcuts = (data: unknown) => {
84        try {
85          // Type guard
86          if (typeof data !== "object" || data === null) {
87            reject(new Error("Invalid shortcuts data"));
88            return;
89          }
90          const dataObj = data as Record<string, unknown>;
91          // Normalize to match our SearchShortcut type
92          const normalized: Record<string, SearchShortcut> = {};
93          for (const [key, value] of Object.entries(dataObj)) {
94            const v = value as Record<string, unknown>;
95            normalized[key] = {
96              url: (v.url ?? v.URL ?? "") as string,
97              description: (v.description ?? v.Description ?? "") as string,
98            };
99          }
100          this.setSearchShortcuts(normalized);
101
102          // Restore original callback if it existed
103          if (originalCallback) {
104            (window as any).__dumber_search_shortcuts = originalCallback;
105          }
106
107          resolve();
108        } catch (error) {
109          console.error("Failed to process search shortcuts:", error);
110          reject(error);
111        }
112      };
113
114      // Send message to Go backend
115      const bridge = window.webkit?.messageHandlers?.dumber;
116      if (bridge && typeof bridge.postMessage === "function") {
117        bridge.postMessage(
118          JSON.stringify({
119            type: "get_search_shortcuts",
120          }),
121        );
122      } else {
123        reject(new Error("WebKit message handler not available"));
124      }
125    });
126  }
127
128  // Suggestions are returned via setSuggestions() when native handler responds
129}
130
131// Singleton instance
132export const omniboxBridge = new OmniboxBridge();
133
134/**
135 * Debounced query function for search input
136 */
137export function debouncedQuery(searchTerm: string): void {
138  omniboxStore.clearDebounceTimer();
139
140  const timerId = window.setTimeout(() => {
141    omniboxBridge.query(searchTerm);
142  }, omniboxStore.config.debounceDelay);
143
144  omniboxStore.setDebounceTimer(timerId);
145}
146
147// Extend global window interface for Go bridge compatibility
148declare global {
149  interface Window {
150    webkit?: {
151      messageHandlers?: {
152        dumber?: {
153          postMessage: (message: string) => void;
154        };
155      };
156    };
157    // Omnibox API for Go bridge
158    __dumber_omnibox?: {
159      setSuggestions: (suggestions: Suggestion[]) => void;
160      toggle: () => void;
161      open: (mode?: string, query?: string) => void;
162      close: () => void;
163      findQuery: (query: string) => void;
164      setActive: (active: boolean) => void;
165    };
166  }
167}

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