v0.23.2 bfd5eb4

history.go RawHistoryFolder

1package homepage
2
3import (
4	"context"
5	"encoding/json"
6	"time"
7
8	"github.com/bnema/dumber/internal/application/usecase"
9	"github.com/bnema/dumber/internal/domain/entity"
10	"github.com/bnema/dumber/internal/infrastructure/webkit"
11	"github.com/bnema/dumber/internal/logging"
12)
13
14// HistoryHandlers handles history-related messages from the homepage.
15type HistoryHandlers struct {
16	historyUC *usecase.SearchHistoryUseCase
17}
18
19// NewHistoryHandlers creates a new HistoryHandlers instance.
20func NewHistoryHandlers(historyUC *usecase.SearchHistoryUseCase) *HistoryHandlers {
21	return &HistoryHandlers{historyUC: historyUC}
22}
23
24// timelineRequest is the payload for history_timeline messages.
25type timelineRequest struct {
26	RequestID string `json:"requestId"`
27	Limit     int    `json:"limit"`
28	Offset    int    `json:"offset"`
29}
30
31// HandleTimeline handles history_timeline messages.
32func (h *HistoryHandlers) HandleTimeline() webkit.MessageHandler {
33	return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
34		log := logging.FromContext(ctx)
35
36		var req timelineRequest
37		if err := json.Unmarshal(payload, &req); err != nil {
38			return NewErrorResponse("", err), nil
39		}
40
41		log.Debug().
42			Str("request_id", req.RequestID).
43			Int("limit", req.Limit).
44			Int("offset", req.Offset).
45			Msg("handling history_timeline")
46
47		entries, err := h.historyUC.GetRecent(ctx, req.Limit, req.Offset)
48		if err != nil {
49			return NewErrorResponse(req.RequestID, err), nil
50		}
51
52		return NewSuccessResponse(req.RequestID, entries), nil
53	})
54}
55
56// searchRequest is the payload for history_search_fts messages.
57type searchRequest struct {
58	RequestID string `json:"requestId"`
59	Query     string `json:"query"`
60	Limit     int    `json:"limit"`
61}
62
63// HandleSearchFTS handles history_search_fts messages.
64func (h *HistoryHandlers) HandleSearchFTS() webkit.MessageHandler {
65	return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
66		log := logging.FromContext(ctx)
67
68		var req searchRequest
69		if err := json.Unmarshal(payload, &req); err != nil {
70			return NewErrorResponse("", err), nil
71		}
72
73		log.Debug().
74			Str("request_id", req.RequestID).
75			Str("query", req.Query).
76			Msg("handling history_search_fts")
77
78		output, err := h.historyUC.Search(ctx, usecase.SearchInput{
79			Query: req.Query,
80			Limit: req.Limit,
81		})
82		if err != nil {
83			return NewErrorResponse(req.RequestID, err), nil
84		}
85
86		// Convert matches to entries for frontend compatibility
87		entries := make([]*entity.HistoryEntry, 0, len(output.Matches))
88		for _, m := range output.Matches {
89			entries = append(entries, m.Entry)
90		}
91
92		return NewSuccessResponse(req.RequestID, entries), nil
93	})
94}
95
96// deleteEntryRequest is the payload for history_delete_entry messages.
97type deleteEntryRequest struct {
98	RequestID string `json:"requestId"`
99	ID        int64  `json:"id"`
100}
101
102// HandleDeleteEntry handles history_delete_entry messages.
103func (h *HistoryHandlers) HandleDeleteEntry() webkit.MessageHandler {
104	return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
105		log := logging.FromContext(ctx)
106
107		var req deleteEntryRequest
108		if err := json.Unmarshal(payload, &req); err != nil {
109			return NewErrorResponse("", err), nil
110		}
111
112		log.Debug().
113			Str("request_id", req.RequestID).
114			Int64("id", req.ID).
115			Msg("handling history_delete_entry")
116
117		if err := h.historyUC.Delete(ctx, req.ID); err != nil {
118			return NewErrorResponse(req.RequestID, err), nil
119		}
120
121		return NewSuccessResponse(req.RequestID, nil), nil
122	})
123}
124
125// deleteRangeRequest is the payload for history_delete_range messages.
126type deleteRangeRequest struct {
127	RequestID string `json:"requestId"`
128	Range     string `json:"range"` // "hour", "day", "week", "month", "all"
129}
130
131// HandleDeleteRange handles history_delete_range messages.
132func (h *HistoryHandlers) HandleDeleteRange() webkit.MessageHandler {
133	return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
134		log := logging.FromContext(ctx)
135
136		var req deleteRangeRequest
137		if err := json.Unmarshal(payload, &req); err != nil {
138			return NewErrorResponse("", err), nil
139		}
140
141		log.Debug().
142			Str("request_id", req.RequestID).
143			Str("range", req.Range).
144			Msg("handling history_delete_range")
145
146		before := rangeToTime(req.Range)
147		if before.IsZero() {
148			// "all" case - clear all
149			if err := h.historyUC.ClearAll(ctx); err != nil {
150				return NewErrorResponse(req.RequestID, err), nil
151			}
152		} else {
153			if err := h.historyUC.ClearOlderThan(ctx, before); err != nil {
154				return NewErrorResponse(req.RequestID, err), nil
155			}
156		}
157
158		return NewSuccessResponse(req.RequestID, nil), nil
159	})
160}
161
162// HandleClearAll handles history_clear_all messages.
163func (h *HistoryHandlers) HandleClearAll() webkit.MessageHandler {
164	return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
165		log := logging.FromContext(ctx)
166
167		requestID := ParseRequestID(payload)
168
169		log.Debug().
170			Str("request_id", requestID).
171			Msg("handling history_clear_all")
172
173		if err := h.historyUC.ClearAll(ctx); err != nil {
174			return NewErrorResponse(requestID, err), nil
175		}
176
177		return NewSuccessResponse(requestID, nil), nil
178	})
179}
180
181// rangeToTime converts a range string to a time.Time cutoff.
182// Returns zero time for "all" which means delete everything.
183func rangeToTime(r string) time.Time {
184	now := time.Now()
185	switch r {
186	case "hour":
187		return now.Add(-time.Hour)
188	case "day":
189		return now.AddDate(0, 0, -1)
190	case "week":
191		return now.AddDate(0, 0, -7)
192	case "month":
193		return now.AddDate(0, -1, 0)
194	case "all":
195		return time.Time{} // Zero time signals "delete all"
196	default:
197		return now
198	}
199}
200
201// HandleAnalytics handles history_analytics messages.
202func (h *HistoryHandlers) HandleAnalytics() webkit.MessageHandler {
203	return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
204		log := logging.FromContext(ctx)
205
206		requestID := ParseRequestID(payload)
207
208		log.Debug().
209			Str("request_id", requestID).
210			Msg("handling history_analytics")
211
212		analytics, err := h.historyUC.GetAnalytics(ctx)
213		if err != nil {
214			return NewErrorResponse(requestID, err), nil
215		}
216
217		return NewSuccessResponse(requestID, analytics), nil
218	})
219}
220
221// domainStatsRequest is the payload for history_domain_stats messages.
222type domainStatsRequest struct {
223	RequestID string `json:"requestId"`
224	Limit     int    `json:"limit"`
225}
226
227// HandleDomainStats handles history_domain_stats messages.
228func (h *HistoryHandlers) HandleDomainStats() webkit.MessageHandler {
229	return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
230		log := logging.FromContext(ctx)
231
232		var req domainStatsRequest
233		if err := json.Unmarshal(payload, &req); err != nil {
234			return NewErrorResponse("", err), nil
235		}
236
237		log.Debug().
238			Str("request_id", req.RequestID).
239			Int("limit", req.Limit).
240			Msg("handling history_domain_stats")
241
242		stats, err := h.historyUC.GetDomainStats(ctx, req.Limit)
243		if err != nil {
244			return NewErrorResponse(req.RequestID, err), nil
245		}
246
247		return NewSuccessResponse(req.RequestID, stats), nil
248	})
249}
250
251// deleteDomainRequest is the payload for history_delete_domain messages.
252type deleteDomainRequest struct {
253	RequestID string `json:"requestId"`
254	Domain    string `json:"domain"`
255}
256
257// HandleDeleteDomain handles history_delete_domain messages.
258func (h *HistoryHandlers) HandleDeleteDomain() webkit.MessageHandler {
259	return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
260		log := logging.FromContext(ctx)
261
262		var req deleteDomainRequest
263		if err := json.Unmarshal(payload, &req); err != nil {
264			return NewErrorResponse("", err), nil
265		}
266
267		log.Debug().
268			Str("request_id", req.RequestID).
269			Str("domain", req.Domain).
270			Msg("handling history_delete_domain")
271
272		if err := h.historyUC.DeleteByDomain(ctx, req.Domain); err != nil {
273			return NewErrorResponse(req.RequestID, err), nil
274		}
275
276		return NewSuccessResponse(req.RequestID, nil), nil
277	})
278}

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