feat(app): add InitialURL support and normalize URLs with shared utility
- commit
- a531dc8e5cdfb5e6e2b72daded56de728f237b05
- author
- bnema · · 2025-12-17 20:45 UTC
- parent
- 4b325e3
- signature
- unsigned
4 files · +17 -17
M internal/application/usecase/manage_tabs.go +3 -2
| @@ -5,6 +5,7 @@ import ( | |||
| 5 | 5 | "fmt" | |
| 6 | 6 | ||
| 7 | 7 | "github.com/bnema/dumber/internal/domain/entity" | |
| 8 | + | "github.com/bnema/dumber/internal/domain/url" | |
| 8 | 9 | "github.com/bnema/dumber/internal/logging" | |
| 9 | 10 | ) | |
| 10 | 11 | ||
| @@ -54,10 +55,10 @@ func (uc *ManageTabsUseCase) Create(ctx context.Context, input CreateTabInput) ( | |||
| 54 | 55 | workspaceID := entity.WorkspaceID(uc.idGenerator()) | |
| 55 | 56 | paneID := entity.PaneID(uc.idGenerator()) | |
| 56 | 57 | ||
| 57 | - | // Create initial pane | |
| 58 | + | // Create initial pane with normalized URL | |
| 58 | 59 | pane := entity.NewPane(paneID) | |
| 59 | 60 | if input.InitialURL != "" { | |
| 60 | - | pane.URI = input.InitialURL | |
| 61 | + | pane.URI = url.Normalize(input.InitialURL) | |
| 61 | 62 | } | |
| 62 | 63 | ||
| 63 | 64 | // Create tab with workspace | |
| @@ -5,6 +5,7 @@ import ( | |||||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | 6 | ||||
| 7 | "github.com/bnema/dumber/internal/domain/entity" | 7 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 8 | + | "github.com/bnema/dumber/internal/domain/url" | |||
| 8 | "github.com/bnema/dumber/internal/logging" | 9 | "github.com/bnema/dumber/internal/logging" | ||
| 9 | ) | 10 | ) | ||
| 10 | 11 | ||||
| @@ -54,10 +55,10 @@ func (uc *ManageTabsUseCase) Create(ctx context.Context, input CreateTabInput) ( | |||||
| 54 | workspaceID := entity.WorkspaceID(uc.idGenerator()) | 55 | workspaceID := entity.WorkspaceID(uc.idGenerator()) | ||
| 55 | paneID := entity.PaneID(uc.idGenerator()) | 56 | paneID := entity.PaneID(uc.idGenerator()) | ||
| 56 | 57 | ||||
| 57 | - | // Create initial pane | 58 | + | // Create initial pane with normalized URL |
| 58 | pane := entity.NewPane(paneID) | 59 | pane := entity.NewPane(paneID) | ||
| 59 | if input.InitialURL != "" { | 60 | if input.InitialURL != "" { | ||
| 60 | - | pane.URI = input.InitialURL | 61 | + | pane.URI = url.Normalize(input.InitialURL) |
| 61 | } | 62 | } | ||
| 62 | 63 | ||||
| 63 | // Create tab with workspace | 64 | // Create tab with workspace | ||
M internal/ui/app.go +6 -2
| @@ -215,8 +215,12 @@ func (a *App) onActivate(ctx context.Context) { | |||
| 215 | 215 | a.navCoord.SetOmniboxProvider(a) | |
| 216 | 216 | log.Debug().Msg("omnibox config stored, provider set") | |
| 217 | 217 | ||
| 218 | - | // Create an initial tab using coordinator - opens dumb://home homepage | |
| 219 | - | if _, err := a.tabCoord.Create(ctx, "dumb://home"); err != nil { | |
| 218 | + | // Create an initial tab using coordinator | |
| 219 | + | initialURL := "dumb://home" | |
| 220 | + | if a.deps.InitialURL != "" { | |
| 221 | + | initialURL = a.deps.InitialURL | |
| 222 | + | } | |
| 223 | + | if _, err := a.tabCoord.Create(ctx, initialURL); err != nil { | |
| 220 | 224 | log.Error().Err(err).Msg("failed to create initial tab") | |
| 221 | 225 | } | |
| 222 | 226 | ||
| @@ -215,8 +215,12 @@ func (a *App) onActivate(ctx context.Context) { | |||||
| 215 | a.navCoord.SetOmniboxProvider(a) | 215 | a.navCoord.SetOmniboxProvider(a) | ||
| 216 | log.Debug().Msg("omnibox config stored, provider set") | 216 | log.Debug().Msg("omnibox config stored, provider set") | ||
| 217 | 217 | ||||
| 218 | - | // Create an initial tab using coordinator - opens dumb://home homepage | 218 | + | // Create an initial tab using coordinator |
| 219 | - | if _, err := a.tabCoord.Create(ctx, "dumb://home"); err != nil { | 219 | + | initialURL := "dumb://home" |
| 220 | + | if a.deps.InitialURL != "" { | |||
| 221 | + | initialURL = a.deps.InitialURL | |||
| 222 | + | } | |||
| 223 | + | if _, err := a.tabCoord.Create(ctx, initialURL); err != nil { | |||
| 220 | log.Error().Err(err).Msg("failed to create initial tab") | 224 | log.Error().Err(err).Msg("failed to create initial tab") | ||
| 221 | } | 225 | } | ||
| 222 | 226 | ||||
M internal/ui/component/omnibox.go +5 -11
| @@ -9,6 +9,7 @@ import ( | |||
| 9 | 9 | ||
| 10 | 10 | "github.com/bnema/dumber/internal/application/usecase" | |
| 11 | 11 | "github.com/bnema/dumber/internal/domain/entity" | |
| 12 | + | "github.com/bnema/dumber/internal/domain/url" | |
| 12 | 13 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 13 | 14 | "github.com/bnema/dumber/internal/logging" | |
| 14 | 15 | "github.com/bnema/dumber/internal/ui/cache" | |
| @@ -953,11 +954,7 @@ func (o *Omnibox) navigateToSelected() { | |||
| 953 | 954 | // looksLikeURL checks if the input appears to be a URL (not a search query). | |
| 954 | 955 | // Returns true for strings like "github.com", "google.com/search", etc. | |
| 955 | 956 | func (o *Omnibox) looksLikeURL(input string) bool { | |
| 956 | - | if input == "" { | |
| 957 | - | return false | |
| 958 | - | } | |
| 959 | - | // Contains a dot and no spaces = likely a URL | |
| 960 | - | return strings.Contains(input, ".") && !strings.Contains(input, " ") | |
| 957 | + | return url.LooksLikeURL(input) | |
| 961 | 958 | } | |
| 962 | 959 | ||
| 963 | 960 | // toggleFavorite adds or removes the selected item from favorites. | |
| @@ -1058,12 +1055,9 @@ func (o *Omnibox) buildURL(input string) string { | |||
| 1058 | 1055 | } | |
| 1059 | 1056 | } | |
| 1060 | 1057 | ||
| 1061 | - | // Check if it looks like a URL | |
| 1062 | - | if strings.Contains(input, ".") && !strings.Contains(input, " ") { | |
| 1063 | - | if !strings.HasPrefix(input, "http://") && !strings.HasPrefix(input, "https://") { | |
| 1064 | - | return "https://" + input | |
| 1065 | - | } | |
| 1066 | - | return input | |
| 1058 | + | // Check if it looks like a URL - use shared URL normalization | |
| 1059 | + | if url.LooksLikeURL(input) { | |
| 1060 | + | return url.Normalize(input) | |
| 1067 | 1061 | } | |
| 1068 | 1062 | ||
| 1069 | 1063 | // Use default search | |
| @@ -9,6 +9,7 @@ import ( | |||||
| 9 | 9 | ||||
| 10 | "github.com/bnema/dumber/internal/application/usecase" | 10 | "github.com/bnema/dumber/internal/application/usecase" | ||
| 11 | "github.com/bnema/dumber/internal/domain/entity" | 11 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 12 | + | "github.com/bnema/dumber/internal/domain/url" | |||
| 12 | "github.com/bnema/dumber/internal/infrastructure/config" | 13 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| 13 | "github.com/bnema/dumber/internal/logging" | 14 | "github.com/bnema/dumber/internal/logging" | ||
| 14 | "github.com/bnema/dumber/internal/ui/cache" | 15 | "github.com/bnema/dumber/internal/ui/cache" | ||
| @@ -953,11 +954,7 @@ func (o *Omnibox) navigateToSelected() { | |||||
| 953 | // looksLikeURL checks if the input appears to be a URL (not a search query). | 954 | // looksLikeURL checks if the input appears to be a URL (not a search query). | ||
| 954 | // Returns true for strings like "github.com", "google.com/search", etc. | 955 | // Returns true for strings like "github.com", "google.com/search", etc. | ||
| 955 | func (o *Omnibox) looksLikeURL(input string) bool { | 956 | func (o *Omnibox) looksLikeURL(input string) bool { | ||
| 956 | - | if input == "" { | 957 | + | return url.LooksLikeURL(input) |
| 957 | - | return false | |||
| 958 | - | } | |||
| 959 | - | // Contains a dot and no spaces = likely a URL | |||
| 960 | - | return strings.Contains(input, ".") && !strings.Contains(input, " ") | |||
| 961 | } | 958 | } | ||
| 962 | 959 | ||||
| 963 | // toggleFavorite adds or removes the selected item from favorites. | 960 | // toggleFavorite adds or removes the selected item from favorites. | ||
| @@ -1058,12 +1055,9 @@ func (o *Omnibox) buildURL(input string) string { | |||||
| 1058 | } | 1055 | } | ||
| 1059 | } | 1056 | } | ||
| 1060 | 1057 | ||||
| 1061 | - | // Check if it looks like a URL | 1058 | + | // Check if it looks like a URL - use shared URL normalization |
| 1062 | - | if strings.Contains(input, ".") && !strings.Contains(input, " ") { | 1059 | + | if url.LooksLikeURL(input) { |
| 1063 | - | if !strings.HasPrefix(input, "http://") && !strings.HasPrefix(input, "https://") { | 1060 | + | return url.Normalize(input) |
| 1064 | - | return "https://" + input | |||
| 1065 | - | } | |||
| 1066 | - | return input | |||
| 1067 | } | 1061 | } | ||
| 1068 | 1062 | ||||
| 1069 | // Use default search | 1063 | // Use default search | ||
M internal/ui/deps.go +3 -2
| @@ -16,8 +16,9 @@ import ( | |||
| 16 | 16 | // This struct is created once at startup and passed to UI components. | |
| 17 | 17 | type Dependencies struct { | |
| 18 | 18 | // Core context and configuration | |
| 19 | - | Ctx context.Context | |
| 20 | - | Config *config.Config | |
| 19 | + | Ctx context.Context | |
| 20 | + | Config *config.Config | |
| 21 | + | InitialURL string // URL to open on startup (optional) | |
| 21 | 22 | ||
| 22 | 23 | // Theme management | |
| 23 | 24 | Theme *theme.Manager | |
| @@ -16,8 +16,9 @@ import ( | |||||
| 16 | // This struct is created once at startup and passed to UI components. | 16 | // This struct is created once at startup and passed to UI components. | ||
| 17 | type Dependencies struct { | 17 | type Dependencies struct { | ||
| 18 | // Core context and configuration | 18 | // Core context and configuration | ||
| 19 | - | Ctx context.Context | 19 | + | Ctx context.Context |
| 20 | - | Config *config.Config | 20 | + | Config *config.Config |
| 21 | + | InitialURL string // URL to open on startup (optional) | |||
| 21 | 22 | ||||
| 22 | // Theme management | 23 | // Theme management | ||
| 23 | Theme *theme.Manager | 24 | Theme *theme.Manager | ||