v0.7.0 31c7fae

handler.go RawHistoryFolder

1package messaging
2
3import (
4	"context"
5	"encoding/json"
6	"log"
7	neturl "net/url"
8
9	"github.com/bnema/dumber/internal/app/constants"
10	"github.com/bnema/dumber/pkg/webkit"
11	"github.com/bnema/dumber/services"
12)
13
14// Handler processes script messages from the WebView
15type Handler struct {
16	parserService  *services.ParserService
17	browserService *services.BrowserService
18	webView        *webkit.WebView
19	lastTheme      string
20}
21
22// Message represents a script message from the WebView
23type Message struct {
24	Type  string `json:"type"`
25	URL   string `json:"url"`
26	Q     string `json:"q"`
27	Limit int    `json:"limit"`
28	Value string `json:"value"`
29	// Wails fetch bridge
30	ID      string          `json:"id"`
31	Payload json.RawMessage `json:"payload"`
32}
33
34// NewHandler creates a new message handler
35func NewHandler(parserService *services.ParserService, browserService *services.BrowserService) *Handler {
36	return &Handler{
37		parserService:  parserService,
38		browserService: browserService,
39	}
40}
41
42// Handle processes incoming script messages
43func (h *Handler) Handle(payload string) {
44	var msg Message
45	if err := json.Unmarshal([]byte(payload), &msg); err != nil {
46		return
47	}
48
49	switch msg.Type {
50	case "navigate":
51		h.handleNavigation(msg)
52	case "query":
53		h.handleQuery(msg)
54	case "wails":
55		h.handleWailsBridge(msg)
56	case "theme":
57		h.handleTheme(msg)
58	}
59}
60
61// SetWebView sets the WebView reference (needed for script injection)
62func (h *Handler) SetWebView(webView *webkit.WebView) {
63	h.webView = webView
64}
65
66// handleNavigation processes navigation requests from the frontend
67func (h *Handler) handleNavigation(msg Message) {
68	ctx := context.Background()
69	res, err := h.parserService.ParseInput(ctx, msg.URL)
70	if err == nil {
71		if _, navErr := h.browserService.Navigate(ctx, res.URL); navErr != nil {
72			log.Printf("Warning: failed to navigate to %s: %v", res.URL, navErr)
73		}
74		if h.webView != nil {
75			_ = h.webView.LoadURL(res.URL)
76			if z, zerr := h.browserService.GetZoomLevel(ctx, res.URL); zerr == nil {
77				_ = h.webView.SetZoom(z)
78			}
79		}
80	}
81}
82
83// handleQuery processes history search queries from the frontend
84func (h *Handler) handleQuery(msg Message) {
85	ctx := context.Background()
86	limit := msg.Limit
87	if limit <= 0 || limit > 25 {
88		limit = 10
89	}
90	entries, err := h.browserService.SearchHistory(ctx, msg.Q, limit)
91	if err != nil {
92		return
93	}
94
95	// Map to lightweight items for frontend
96	type item struct {
97		URL     string `json:"url"`
98		Favicon string `json:"favicon"`
99	}
100
101	buildFavicon := func(raw string) string {
102		u, err := h.parserService.ParseInput(ctx, raw)
103		if err != nil || u.URL == "" {
104			return ""
105		}
106		parsed, perr := neturl.Parse(u.URL)
107		if perr != nil || parsed.Host == "" {
108			return ""
109		}
110		scheme := parsed.Scheme
111		if scheme == "" {
112			scheme = "https"
113		}
114		return scheme + "://" + parsed.Host + "/favicon.ico"
115	}
116
117	items := make([]item, 0, len(entries))
118	for _, e := range entries {
119		items = append(items, item{URL: e.URL, Favicon: buildFavicon(e.URL)})
120	}
121	b, _ := json.Marshal(items)
122	// Update suggestions in page
123	if h.webView != nil {
124		_ = h.webView.InjectScript("window.__dumber_setSuggestions && window.__dumber_setSuggestions(" + string(b) + ")")
125	}
126}
127
128// handleWailsBridge processes Wails runtime bridge calls for homepage
129func (h *Handler) handleWailsBridge(msg Message) {
130	// Payload contains { methodID, methodName?, args }
131	var p struct {
132		MethodID   uint32          `json:"methodID"`
133		MethodName string          `json:"methodName"`
134		Args       json.RawMessage `json:"args"`
135	}
136	if err := json.Unmarshal(msg.Payload, &p); err != nil {
137		return
138	}
139
140	// Only implement the IDs we need
141	switch p.MethodID {
142	case constants.HashGetRecentHistory: // BrowserService.GetRecentHistory(limit)
143		var args []interface{}
144		_ = json.Unmarshal(p.Args, &args)
145		limit := 50
146		if len(args) > 0 {
147			if f, ok := args[0].(float64); ok {
148				limit = int(f)
149			}
150		}
151		ctx := context.Background()
152		entries, err := h.browserService.GetRecentHistory(ctx, limit)
153		if err != nil {
154			return
155		}
156		resp, _ := json.Marshal(entries)
157		if h.webView != nil {
158			_ = h.webView.InjectScript("window.__dumber_wails_resolve('" + msg.ID + "', " + string(resp) + ")")
159		}
160	case constants.HashGetSearchShortcuts: // BrowserService.GetSearchShortcuts()
161		ctx := context.Background()
162		shortcuts, err := h.browserService.GetSearchShortcuts(ctx)
163		if err != nil {
164			return
165		}
166		resp, _ := json.Marshal(shortcuts)
167		if h.webView != nil {
168			_ = h.webView.InjectScript("window.__dumber_wails_resolve('" + msg.ID + "', " + string(resp) + ")")
169		}
170	default:
171		// Return empty JSON to avoid breaking UI
172		if h.webView != nil {
173			_ = h.webView.InjectScript("window.__dumber_wails_resolve('" + msg.ID + "', '{}')")
174		}
175	}
176}
177
178// handleTheme processes theme-related messages
179func (h *Handler) handleTheme(msg Message) {
180	if msg.Value != "" && msg.Value != h.lastTheme {
181		log.Printf("[theme] color-scheme changed: %s", msg.Value)
182		h.lastTheme = msg.Value
183	}
184}

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