detached aa98e12

feat(omnibox): add Space key to toggle favorites (add in history, remove in favorites)

commit
aa98e12f01b9e69729b56e7f2fa1e0a8e68ad2f7
author
brice · · 2025-12-14 21:43 UTC
committer
brice · · 2025-12-14 17:44 UTC
parent
f3b20f3
signature
unsigned
2 files · +90 -0

this diff holds invisible characters (direction controls, zero-width characters). They are shown as <U+XXXX> markers, never applied.

M internal/ui/component/omnibox.go +89 -0
@@ -7,6 +7,7 @@ import (
77 "time"
88
99 "github.com/bnema/dumber/internal/application/usecase"
10+ "github.com/bnema/dumber/internal/domain/entity"
1011 "github.com/bnema/dumber/internal/infrastructure/config"
1112 "github.com/bnema/dumber/internal/logging"
1213 "github.com/jwijenbergh/puregotk/v4/gdk"
@@ -299,6 +300,11 @@ func (o *Omnibox) handleKeyPress(keyval uint, state gdk.ModifierType) bool {
299300 o.toggleViewMode()
300301 return true
301302
303+ case uint(gdk.KEY_space):
304+ // Space toggles favorite: add in history mode, remove in favorites mode
305+ o.toggleFavorite()
306+ return true
307+
302308 case uint(gdk.KEY_1), uint(gdk.KEY_2), uint(gdk.KEY_3), uint(gdk.KEY_4), uint(gdk.KEY_5),
303309 uint(gdk.KEY_6), uint(gdk.KEY_7), uint(gdk.KEY_8), uint(gdk.KEY_9):
304310 if ctrl {
@@ -723,6 +729,89 @@ func (o *Omnibox) navigateToSelected() {
723729 }
724730 }
725731
732+// toggleFavorite adds or removes the selected item from favorites.
733+// In History mode: adds the selected item to favorites
734+// In Favorites mode: removes the selected item from favorites
735+func (o *Omnibox) toggleFavorite() {
736+ log := logging.FromContext(o.ctx)
737+
738+ o.mu.RLock()
739+ mode := o.viewMode
740+ idx := o.selectedIndex
741+ suggestions := o.suggestions
742+ favorites := o.favorites
743+ o.mu.RUnlock()
744+
745+ if o.favoritesUC == nil {
746+ log.Warn().Msg("toggle favorite: favoritesUC is nil")
747+ return
748+ }
749+
750+ if mode == ViewModeHistory {
751+ // Add to favorites
752+ if idx < 0 || idx >= len(suggestions) {
753+ log.Debug().Int("index", idx).Msg("add favorite: invalid selection")
754+ return
755+ }
756+
757+ s := suggestions[idx]
758+ if s.URL == "" {
759+ log.Debug().Msg("add favorite: empty URL")
760+ return
761+ }
762+
763+ go func() {
764+ ctx := o.ctx
765+ log := logging.FromContext(ctx)
766+
767+ log.Debug().Str("url", s.URL).Str("title", s.Title).Msg("adding to favorites")
768+
769+ input := usecase.AddFavoriteInput{
770+ URL: s.URL,
771+ Title: s.Title,
772+ }
773+
774+ fav, err := o.favoritesUC.Add(ctx, input)
775+ if err != nil {
776+ log.Error().Err(err).Str("url", s.URL).Msg("failed to add favorite")
777+ return
778+ }
779+
780+ log.Info().Str("url", s.URL).Int64("id", int64(fav.ID)).Msg("favorite added from omnibox")
781+ }()
782+ } else {
783+ // Remove from favorites
784+ if idx < 0 || idx >= len(favorites) {
785+ log.Debug().Int("index", idx).Msg("remove favorite: invalid selection")
786+ return
787+ }
788+
789+ f := favorites[idx]
790+ if f.ID == 0 {
791+ log.Debug().Msg("remove favorite: invalid ID")
792+ return
793+ }
794+
795+ go func() {
796+ ctx := o.ctx
797+ log := logging.FromContext(ctx)
798+
799+ log.Debug().Int64("id", f.ID).Str("url", f.URL).Msg("removing from favorites")
800+
801+ err := o.favoritesUC.Remove(ctx, entity.FavoriteID(f.ID))
802+ if err != nil {
803+ log.Error().Err(err).Int64("id", f.ID).Msg("failed to remove favorite")
804+ return
805+ }
806+
807+ log.Info().Int64("id", f.ID).Str("url", f.URL).Msg("favorite removed from omnibox")
808+
809+ // Refresh favorites list
810+ o.loadFavorites(o.entry.GetText())
811+ }()
812+ }
813+}
814+
726815 // buildURL constructs a URL from input, handling search shortcuts.
727816 func (o *Omnibox) buildURL(input string) string {
728817 if input == "" {
@@ -7,6 +7,7 @@ import (
7 "time"7 "time"
8 8
9 "github.com/bnema/dumber/internal/application/usecase"9 "github.com/bnema/dumber/internal/application/usecase"
10+ "github.com/bnema/dumber/internal/domain/entity"
10 "github.com/bnema/dumber/internal/infrastructure/config"11 "github.com/bnema/dumber/internal/infrastructure/config"
11 "github.com/bnema/dumber/internal/logging"12 "github.com/bnema/dumber/internal/logging"
12 "github.com/jwijenbergh/puregotk/v4/gdk"13 "github.com/jwijenbergh/puregotk/v4/gdk"
@@ -299,6 +300,11 @@ func (o *Omnibox) handleKeyPress(keyval uint, state gdk.ModifierType) bool {
299 o.toggleViewMode()300 o.toggleViewMode()
300 return true301 return true
301 302
303+ case uint(gdk.KEY_space):
304+ // Space toggles favorite: add in history mode, remove in favorites mode
305+ o.toggleFavorite()
306+ return true
307+
302 case uint(gdk.KEY_1), uint(gdk.KEY_2), uint(gdk.KEY_3), uint(gdk.KEY_4), uint(gdk.KEY_5),308 case uint(gdk.KEY_1), uint(gdk.KEY_2), uint(gdk.KEY_3), uint(gdk.KEY_4), uint(gdk.KEY_5),
303 uint(gdk.KEY_6), uint(gdk.KEY_7), uint(gdk.KEY_8), uint(gdk.KEY_9):309 uint(gdk.KEY_6), uint(gdk.KEY_7), uint(gdk.KEY_8), uint(gdk.KEY_9):
304 if ctrl {310 if ctrl {
@@ -723,6 +729,89 @@ func (o *Omnibox) navigateToSelected() {
723 }729 }
724 }730 }
725 731
732+// toggleFavorite adds or removes the selected item from favorites.
733+// In History mode: adds the selected item to favorites
734+// In Favorites mode: removes the selected item from favorites
735+func (o *Omnibox) toggleFavorite() {
736+ log := logging.FromContext(o.ctx)
737+
738+ o.mu.RLock()
739+ mode := o.viewMode
740+ idx := o.selectedIndex
741+ suggestions := o.suggestions
742+ favorites := o.favorites
743+ o.mu.RUnlock()
744+
745+ if o.favoritesUC == nil {
746+ log.Warn().Msg("toggle favorite: favoritesUC is nil")
747+ return
748+ }
749+
750+ if mode == ViewModeHistory {
751+ // Add to favorites
752+ if idx < 0 || idx >= len(suggestions) {
753+ log.Debug().Int("index", idx).Msg("add favorite: invalid selection")
754+ return
755+ }
756+
757+ s := suggestions[idx]
758+ if s.URL == "" {
759+ log.Debug().Msg("add favorite: empty URL")
760+ return
761+ }
762+
763+ go func() {
764+ ctx := o.ctx
765+ log := logging.FromContext(ctx)
766+
767+ log.Debug().Str("url", s.URL).Str("title", s.Title).Msg("adding to favorites")
768+
769+ input := usecase.AddFavoriteInput{
770+ URL: s.URL,
771+ Title: s.Title,
772+ }
773+
774+ fav, err := o.favoritesUC.Add(ctx, input)
775+ if err != nil {
776+ log.Error().Err(err).Str("url", s.URL).Msg("failed to add favorite")
777+ return
778+ }
779+
780+ log.Info().Str("url", s.URL).Int64("id", int64(fav.ID)).Msg("favorite added from omnibox")
781+ }()
782+ } else {
783+ // Remove from favorites
784+ if idx < 0 || idx >= len(favorites) {
785+ log.Debug().Int("index", idx).Msg("remove favorite: invalid selection")
786+ return
787+ }
788+
789+ f := favorites[idx]
790+ if f.ID == 0 {
791+ log.Debug().Msg("remove favorite: invalid ID")
792+ return
793+ }
794+
795+ go func() {
796+ ctx := o.ctx
797+ log := logging.FromContext(ctx)
798+
799+ log.Debug().Int64("id", f.ID).Str("url", f.URL).Msg("removing from favorites")
800+
801+ err := o.favoritesUC.Remove(ctx, entity.FavoriteID(f.ID))
802+ if err != nil {
803+ log.Error().Err(err).Int64("id", f.ID).Msg("failed to remove favorite")
804+ return
805+ }
806+
807+ log.Info().Int64("id", f.ID).Str("url", f.URL).Msg("favorite removed from omnibox")
808+
809+ // Refresh favorites list
810+ o.loadFavorites(o.entry.GetText())
811+ }()
812+ }
813+}
814+
726 // buildURL constructs a URL from input, handling search shortcuts.815 // buildURL constructs a URL from input, handling search shortcuts.
727 func (o *Omnibox) buildURL(input string) string {816 func (o *Omnibox) buildURL(input string) string {
728 if input == "" {817 if input == "" {
A mediakeys/v1/salt +1 -0
@@ -0,0 +1,1 @@
1+<U+007F>�v�}K⏎̸
@@ -0,0 +1,1 @@
1+<U+007F>�v�}K⏎̸

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