refactor(M1+M5): decouple handlers from usecase/config, deps.go from infrastructure
M1: Define narrow port interfaces (HomepageHistory, HomepageFavorites, AccentKeyHandler, DownloadPreparer, AutoCopyConfig) and refactor all webkit handlers to depend on port interfaces instead of concrete usecase/config types. Move keybinding and config handler construction to bootstrap. M5: Replace infrastructure types in ui/Dependencies with port interfaces (ToolkitAvailabilityNotifier, FaviconService, FilterManager). Move FilterStatus to port package with type alias in filtering.
- commit
- d0d5c96339691afe4174d789f1d3026b41dd2d8a
- author
- brice · · 2026-03-17 10:12 UTC
- parent
- cfe4a9c
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
27 files · +336 -181
M cmd/dumber/main.go +2 -4
| @@ -18,11 +18,9 @@ import ( | |||
| 18 | 18 | "github.com/bnema/dumber/internal/domain/repository" | |
| 19 | 19 | "github.com/bnema/dumber/internal/infrastructure/cache" | |
| 20 | 20 | "github.com/bnema/dumber/internal/infrastructure/clipboard" | |
| 21 | - | "github.com/bnema/dumber/internal/infrastructure/colorscheme" | |
| 22 | 21 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 23 | 22 | "github.com/bnema/dumber/internal/infrastructure/deps" | |
| 24 | 23 | "github.com/bnema/dumber/internal/infrastructure/favicon" | |
| 25 | - | "github.com/bnema/dumber/internal/infrastructure/filtering" | |
| 26 | 24 | "github.com/bnema/dumber/internal/infrastructure/idle" | |
| 27 | 25 | "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite" | |
| 28 | 26 | "github.com/bnema/dumber/internal/infrastructure/updater" | |
| @@ -507,7 +505,7 @@ func buildUIDependencies( | |||
| 507 | 505 | cfg *config.Config, | |
| 508 | 506 | themeManager *theme.Manager, | |
| 509 | 507 | colorResolver port.ColorSchemeResolver, | |
| 510 | - | adwaitaDetector *colorscheme.AdwaitaDetector, | |
| 508 | + | adwaitaDetector port.ToolkitAvailabilityNotifier, | |
| 511 | 509 | engine port.Engine, | |
| 512 | 510 | repos *repositories, | |
| 513 | 511 | uc *useCases, | |
| @@ -516,7 +514,7 @@ func buildUIDependencies( | |||
| 516 | 514 | startupCrashReports []string, | |
| 517 | 515 | ) *ui.Dependencies { | |
| 518 | 516 | // Type-assert to *webkit.Engine to access FilterManager (not yet on port.Engine). | |
| 519 | - | var filterManager *filtering.Manager | |
| 517 | + | var filterManager port.FilterManager | |
| 520 | 518 | if wkEngine, ok := engine.(*webkit.Engine); ok { | |
| 521 | 519 | filterManager = wkEngine.InternalFilterManager() | |
| 522 | 520 | } | |
| @@ -18,11 +18,9 @@ import ( | |||||
| 18 | "github.com/bnema/dumber/internal/domain/repository" | 18 | "github.com/bnema/dumber/internal/domain/repository" | ||
| 19 | "github.com/bnema/dumber/internal/infrastructure/cache" | 19 | "github.com/bnema/dumber/internal/infrastructure/cache" | ||
| 20 | "github.com/bnema/dumber/internal/infrastructure/clipboard" | 20 | "github.com/bnema/dumber/internal/infrastructure/clipboard" | ||
| 21 | - | "github.com/bnema/dumber/internal/infrastructure/colorscheme" | |||
| 22 | "github.com/bnema/dumber/internal/infrastructure/config" | 21 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| 23 | "github.com/bnema/dumber/internal/infrastructure/deps" | 22 | "github.com/bnema/dumber/internal/infrastructure/deps" | ||
| 24 | "github.com/bnema/dumber/internal/infrastructure/favicon" | 23 | "github.com/bnema/dumber/internal/infrastructure/favicon" | ||
| 25 | - | "github.com/bnema/dumber/internal/infrastructure/filtering" | |||
| 26 | "github.com/bnema/dumber/internal/infrastructure/idle" | 24 | "github.com/bnema/dumber/internal/infrastructure/idle" | ||
| 27 | "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite" | 25 | "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite" | ||
| 28 | "github.com/bnema/dumber/internal/infrastructure/updater" | 26 | "github.com/bnema/dumber/internal/infrastructure/updater" | ||
| @@ -507,7 +505,7 @@ func buildUIDependencies( | |||||
| 507 | cfg *config.Config, | 505 | cfg *config.Config, | ||
| 508 | themeManager *theme.Manager, | 506 | themeManager *theme.Manager, | ||
| 509 | colorResolver port.ColorSchemeResolver, | 507 | colorResolver port.ColorSchemeResolver, | ||
| 510 | - | adwaitaDetector *colorscheme.AdwaitaDetector, | 508 | + | adwaitaDetector port.ToolkitAvailabilityNotifier, |
| 511 | engine port.Engine, | 509 | engine port.Engine, | ||
| 512 | repos *repositories, | 510 | repos *repositories, | ||
| 513 | uc *useCases, | 511 | uc *useCases, | ||
| @@ -516,7 +514,7 @@ func buildUIDependencies( | |||||
| 516 | startupCrashReports []string, | 514 | startupCrashReports []string, | ||
| 517 | ) *ui.Dependencies { | 515 | ) *ui.Dependencies { | ||
| 518 | // Type-assert to *webkit.Engine to access FilterManager (not yet on port.Engine). | 516 | // Type-assert to *webkit.Engine to access FilterManager (not yet on port.Engine). | ||
| 519 | - | var filterManager *filtering.Manager | 517 | + | var filterManager port.FilterManager |
| 520 | if wkEngine, ok := engine.(*webkit.Engine); ok { | 518 | if wkEngine, ok := engine.(*webkit.Engine); ok { | ||
| 521 | filterManager = wkEngine.InternalFilterManager() | 519 | filterManager = wkEngine.InternalFilterManager() | ||
| 522 | } | 520 | } | ||
M internal/application/port/color_scheme.go +5 -0
| @@ -33,6 +33,11 @@ type ColorSchemeDetector interface { | |||
| 33 | 33 | Detect() (prefersDark bool, ok bool) | |
| 34 | 34 | } | |
| 35 | 35 | ||
| 36 | + | // ToolkitAvailabilityNotifier signals that a toolkit (e.g., GTK/Adwaita) is available. | |
| 37 | + | type ToolkitAvailabilityNotifier interface { | |
| 38 | + | MarkAvailable() | |
| 39 | + | } | |
| 40 | + | ||
| 36 | 41 | // ColorSchemeResolver resolves the effective color scheme preference. | |
| 37 | 42 | // It manages multiple detectors and respects config overrides. | |
| 38 | 43 | type ColorSchemeResolver interface { | |
| @@ -33,6 +33,11 @@ type ColorSchemeDetector interface { | |||||
| 33 | Detect() (prefersDark bool, ok bool) | 33 | Detect() (prefersDark bool, ok bool) | ||
| 34 | } | 34 | } | ||
| 35 | 35 | ||||
| 36 | + | // ToolkitAvailabilityNotifier signals that a toolkit (e.g., GTK/Adwaita) is available. | |||
| 37 | + | type ToolkitAvailabilityNotifier interface { | |||
| 38 | + | MarkAvailable() | |||
| 39 | + | } | |||
| 40 | + | ||||
| 36 | // ColorSchemeResolver resolves the effective color scheme preference. | 41 | // ColorSchemeResolver resolves the effective color scheme preference. | ||
| 37 | // It manages multiple detectors and respects config overrides. | 42 | // It manages multiple detectors and respects config overrides. | ||
| 38 | type ColorSchemeResolver interface { | 43 | type ColorSchemeResolver interface { | ||
A internal/application/port/favicon_service.go +16 -0
| @@ -0,0 +1,16 @@ | |||
| 1 | + | package port | |
| 2 | + | ||
| 3 | + | import "context" | |
| 4 | + | ||
| 5 | + | // FaviconService provides favicon retrieval and caching. | |
| 6 | + | type FaviconService interface { | |
| 7 | + | GetCached(domain string) ([]byte, bool) | |
| 8 | + | Get(ctx context.Context, domain string) ([]byte, error) | |
| 9 | + | DiskPathPNG(domain string) string | |
| 10 | + | HasPNGOnDisk(domain string) bool | |
| 11 | + | HasPNGSizedOnDisk(domain string, size int) bool | |
| 12 | + | EnsureSizedPNG(ctx context.Context, domain string, size int) error | |
| 13 | + | EnsureCacheDir() error | |
| 14 | + | EnsureDiskCache(ctx context.Context, domain string) | |
| 15 | + | Close() | |
| 16 | + | } | |
| @@ -0,0 +1,16 @@ | |||||
| 1 | + | package port | |||
| 2 | + | ||||
| 3 | + | import "context" | |||
| 4 | + | ||||
| 5 | + | // FaviconService provides favicon retrieval and caching. | |||
| 6 | + | type FaviconService interface { | |||
| 7 | + | GetCached(domain string) ([]byte, bool) | |||
| 8 | + | Get(ctx context.Context, domain string) ([]byte, error) | |||
| 9 | + | DiskPathPNG(domain string) string | |||
| 10 | + | HasPNGOnDisk(domain string) bool | |||
| 11 | + | HasPNGSizedOnDisk(domain string, size int) bool | |||
| 12 | + | EnsureSizedPNG(ctx context.Context, domain string, size int) error | |||
| 13 | + | EnsureCacheDir() error | |||
| 14 | + | EnsureDiskCache(ctx context.Context, domain string) | |||
| 15 | + | Close() | |||
| 16 | + | } | |||
A internal/application/port/filter_manager.go +33 -0
| @@ -0,0 +1,33 @@ | |||
| 1 | + | package port | |
| 2 | + | ||
| 3 | + | import "context" | |
| 4 | + | ||
| 5 | + | // FilterState represents the current state of the content filter system. | |
| 6 | + | type FilterState string | |
| 7 | + | ||
| 8 | + | const ( | |
| 9 | + | // FilterStateUninitialized means the filter manager has not been initialized yet. | |
| 10 | + | FilterStateUninitialized FilterState = "uninitialized" | |
| 11 | + | // FilterStateLoading means filters are being downloaded or compiled. | |
| 12 | + | FilterStateLoading FilterState = "loading" | |
| 13 | + | // FilterStateActive means filters are loaded and active. | |
| 14 | + | FilterStateActive FilterState = "active" | |
| 15 | + | // FilterStateDisabled means filtering is disabled by configuration. | |
| 16 | + | FilterStateDisabled FilterState = "disabled" | |
| 17 | + | // FilterStateError means an error occurred during filter loading. | |
| 18 | + | FilterStateError FilterState = "error" | |
| 19 | + | ) | |
| 20 | + | ||
| 21 | + | // FilterStatus represents the current status of the content filter system. | |
| 22 | + | // Used for UI toast notifications and status reporting. | |
| 23 | + | type FilterStatus struct { | |
| 24 | + | State FilterState | |
| 25 | + | Message string | |
| 26 | + | Version string | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | // FilterManager manages content filters. | |
| 30 | + | type FilterManager interface { | |
| 31 | + | SetStatusCallback(fn func(FilterStatus)) | |
| 32 | + | LoadAsync(ctx context.Context) | |
| 33 | + | } | |
| @@ -0,0 +1,33 @@ | |||||
| 1 | + | package port | |||
| 2 | + | ||||
| 3 | + | import "context" | |||
| 4 | + | ||||
| 5 | + | // FilterState represents the current state of the content filter system. | |||
| 6 | + | type FilterState string | |||
| 7 | + | ||||
| 8 | + | const ( | |||
| 9 | + | // FilterStateUninitialized means the filter manager has not been initialized yet. | |||
| 10 | + | FilterStateUninitialized FilterState = "uninitialized" | |||
| 11 | + | // FilterStateLoading means filters are being downloaded or compiled. | |||
| 12 | + | FilterStateLoading FilterState = "loading" | |||
| 13 | + | // FilterStateActive means filters are loaded and active. | |||
| 14 | + | FilterStateActive FilterState = "active" | |||
| 15 | + | // FilterStateDisabled means filtering is disabled by configuration. | |||
| 16 | + | FilterStateDisabled FilterState = "disabled" | |||
| 17 | + | // FilterStateError means an error occurred during filter loading. | |||
| 18 | + | FilterStateError FilterState = "error" | |||
| 19 | + | ) | |||
| 20 | + | ||||
| 21 | + | // FilterStatus represents the current status of the content filter system. | |||
| 22 | + | // Used for UI toast notifications and status reporting. | |||
| 23 | + | type FilterStatus struct { | |||
| 24 | + | State FilterState | |||
| 25 | + | Message string | |||
| 26 | + | Version string | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | // FilterManager manages content filters. | |||
| 30 | + | type FilterManager interface { | |||
| 31 | + | SetStatusCallback(fn func(FilterStatus)) | |||
| 32 | + | LoadAsync(ctx context.Context) | |||
| 33 | + | } | |||
A internal/application/port/handler_deps.go +32 -0
| @@ -0,0 +1,32 @@ | |||
| 1 | + | package port | |
| 2 | + | ||
| 3 | + | import "context" | |
| 4 | + | ||
| 5 | + | // AccentKeyHandler receives accent/dead-key events from the WebView JS bridge. | |
| 6 | + | type AccentKeyHandler interface { | |
| 7 | + | OnKeyPressed(ctx context.Context, char rune, shiftHeld bool) bool | |
| 8 | + | OnKeyReleased(ctx context.Context, char rune) | |
| 9 | + | } | |
| 10 | + | ||
| 11 | + | // DownloadPreparer resolves download destination paths with deduplication. | |
| 12 | + | type DownloadPreparer interface { | |
| 13 | + | Execute(ctx context.Context, input DownloadPrepareInput) *DownloadPrepareOutput | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | // DownloadPrepareInput matches usecase.PrepareDownloadInput. | |
| 17 | + | type DownloadPrepareInput struct { | |
| 18 | + | SuggestedFilename string | |
| 19 | + | Response DownloadResponse | |
| 20 | + | DownloadDir string | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | // DownloadPrepareOutput matches usecase.PrepareDownloadOutput. | |
| 24 | + | type DownloadPrepareOutput struct { | |
| 25 | + | Filename string | |
| 26 | + | DestinationPath string | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | // AutoCopyConfig provides the clipboard auto-copy setting. | |
| 30 | + | type AutoCopyConfig interface { | |
| 31 | + | IsAutoCopyEnabled() bool | |
| 32 | + | } | |
| @@ -0,0 +1,32 @@ | |||||
| 1 | + | package port | |||
| 2 | + | ||||
| 3 | + | import "context" | |||
| 4 | + | ||||
| 5 | + | // AccentKeyHandler receives accent/dead-key events from the WebView JS bridge. | |||
| 6 | + | type AccentKeyHandler interface { | |||
| 7 | + | OnKeyPressed(ctx context.Context, char rune, shiftHeld bool) bool | |||
| 8 | + | OnKeyReleased(ctx context.Context, char rune) | |||
| 9 | + | } | |||
| 10 | + | ||||
| 11 | + | // DownloadPreparer resolves download destination paths with deduplication. | |||
| 12 | + | type DownloadPreparer interface { | |||
| 13 | + | Execute(ctx context.Context, input DownloadPrepareInput) *DownloadPrepareOutput | |||
| 14 | + | } | |||
| 15 | + | ||||
| 16 | + | // DownloadPrepareInput matches usecase.PrepareDownloadInput. | |||
| 17 | + | type DownloadPrepareInput struct { | |||
| 18 | + | SuggestedFilename string | |||
| 19 | + | Response DownloadResponse | |||
| 20 | + | DownloadDir string | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | // DownloadPrepareOutput matches usecase.PrepareDownloadOutput. | |||
| 24 | + | type DownloadPrepareOutput struct { | |||
| 25 | + | Filename string | |||
| 26 | + | DestinationPath string | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | // AutoCopyConfig provides the clipboard auto-copy setting. | |||
| 30 | + | type AutoCopyConfig interface { | |||
| 31 | + | IsAutoCopyEnabled() bool | |||
| 32 | + | } | |||
A internal/application/port/homepage.go +50 -0
| @@ -0,0 +1,50 @@ | |||
| 1 | + | package port | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "time" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/dumber/internal/domain/entity" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | // HistorySearchInput holds search parameters (moved from usecase.SearchInput). | |
| 11 | + | type HistorySearchInput struct { | |
| 12 | + | Query string | |
| 13 | + | Limit int | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | // HistorySearchOutput holds search results (moved from usecase.SearchOutput). | |
| 17 | + | type HistorySearchOutput struct { | |
| 18 | + | Matches []entity.HistoryMatch | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | // HomepageHistory provides history operations needed by the WebUI homepage handlers. | |
| 22 | + | type HomepageHistory interface { | |
| 23 | + | GetRecent(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error) | |
| 24 | + | Search(ctx context.Context, input HistorySearchInput) (*HistorySearchOutput, error) | |
| 25 | + | Delete(ctx context.Context, id int64) error | |
| 26 | + | ClearAll(ctx context.Context) error | |
| 27 | + | ClearOlderThan(ctx context.Context, before time.Time) error | |
| 28 | + | GetAnalytics(ctx context.Context) (*entity.HistoryAnalytics, error) | |
| 29 | + | GetDomainStats(ctx context.Context, limit int) ([]*entity.DomainStat, error) | |
| 30 | + | DeleteByDomain(ctx context.Context, domain string) error | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | // HomepageFavorites provides favorites/folders/tags operations | |
| 34 | + | // needed by the WebUI homepage handlers. | |
| 35 | + | type HomepageFavorites interface { | |
| 36 | + | GetAll(ctx context.Context) ([]*entity.Favorite, error) | |
| 37 | + | SetShortcut(ctx context.Context, id entity.FavoriteID, key *int) error | |
| 38 | + | GetByShortcut(ctx context.Context, key int) (*entity.Favorite, error) | |
| 39 | + | Move(ctx context.Context, id entity.FavoriteID, folderID *entity.FolderID) error | |
| 40 | + | GetAllFolders(ctx context.Context) ([]*entity.Folder, error) | |
| 41 | + | CreateFolder(ctx context.Context, name string, parentID *entity.FolderID) (*entity.Folder, error) | |
| 42 | + | DeleteFolder(ctx context.Context, id entity.FolderID) error | |
| 43 | + | UpdateFolder(ctx context.Context, id entity.FolderID, name, icon string) error | |
| 44 | + | GetAllTags(ctx context.Context) ([]*entity.Tag, error) | |
| 45 | + | AddTag(ctx context.Context, name, color string) (*entity.Tag, error) | |
| 46 | + | DeleteTag(ctx context.Context, id entity.TagID) error | |
| 47 | + | UpdateTag(ctx context.Context, id entity.TagID, name, color string) error | |
| 48 | + | TagFavorite(ctx context.Context, favID entity.FavoriteID, tagID entity.TagID) error | |
| 49 | + | UntagFavorite(ctx context.Context, favID entity.FavoriteID, tagID entity.TagID) error | |
| 50 | + | } | |
| @@ -0,0 +1,50 @@ | |||||
| 1 | + | package port | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "time" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/dumber/internal/domain/entity" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | // HistorySearchInput holds search parameters (moved from usecase.SearchInput). | |||
| 11 | + | type HistorySearchInput struct { | |||
| 12 | + | Query string | |||
| 13 | + | Limit int | |||
| 14 | + | } | |||
| 15 | + | ||||
| 16 | + | // HistorySearchOutput holds search results (moved from usecase.SearchOutput). | |||
| 17 | + | type HistorySearchOutput struct { | |||
| 18 | + | Matches []entity.HistoryMatch | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | // HomepageHistory provides history operations needed by the WebUI homepage handlers. | |||
| 22 | + | type HomepageHistory interface { | |||
| 23 | + | GetRecent(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error) | |||
| 24 | + | Search(ctx context.Context, input HistorySearchInput) (*HistorySearchOutput, error) | |||
| 25 | + | Delete(ctx context.Context, id int64) error | |||
| 26 | + | ClearAll(ctx context.Context) error | |||
| 27 | + | ClearOlderThan(ctx context.Context, before time.Time) error | |||
| 28 | + | GetAnalytics(ctx context.Context) (*entity.HistoryAnalytics, error) | |||
| 29 | + | GetDomainStats(ctx context.Context, limit int) ([]*entity.DomainStat, error) | |||
| 30 | + | DeleteByDomain(ctx context.Context, domain string) error | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | // HomepageFavorites provides favorites/folders/tags operations | |||
| 34 | + | // needed by the WebUI homepage handlers. | |||
| 35 | + | type HomepageFavorites interface { | |||
| 36 | + | GetAll(ctx context.Context) ([]*entity.Favorite, error) | |||
| 37 | + | SetShortcut(ctx context.Context, id entity.FavoriteID, key *int) error | |||
| 38 | + | GetByShortcut(ctx context.Context, key int) (*entity.Favorite, error) | |||
| 39 | + | Move(ctx context.Context, id entity.FavoriteID, folderID *entity.FolderID) error | |||
| 40 | + | GetAllFolders(ctx context.Context) ([]*entity.Folder, error) | |||
| 41 | + | CreateFolder(ctx context.Context, name string, parentID *entity.FolderID) (*entity.Folder, error) | |||
| 42 | + | DeleteFolder(ctx context.Context, id entity.FolderID) error | |||
| 43 | + | UpdateFolder(ctx context.Context, id entity.FolderID, name, icon string) error | |||
| 44 | + | GetAllTags(ctx context.Context) ([]*entity.Tag, error) | |||
| 45 | + | AddTag(ctx context.Context, name, color string) (*entity.Tag, error) | |||
| 46 | + | DeleteTag(ctx context.Context, id entity.TagID) error | |||
| 47 | + | UpdateTag(ctx context.Context, id entity.TagID, name, color string) error | |||
| 48 | + | TagFavorite(ctx context.Context, favID entity.FavoriteID, tagID entity.TagID) error | |||
| 49 | + | UntagFavorite(ctx context.Context, favID entity.FavoriteID, tagID entity.TagID) error | |||
| 50 | + | } | |||
M internal/application/port/keybindings.go +20 -0
| @@ -64,3 +64,23 @@ type KeybindingsSaver interface { | |||
| 64 | 64 | ResetKeybinding(ctx context.Context, req ResetKeybindingRequest) error | |
| 65 | 65 | ResetAllKeybindings(ctx context.Context) error | |
| 66 | 66 | } | |
| 67 | + | ||
| 68 | + | // KeybindingsGetter retrieves all keybindings (narrow interface for the handler). | |
| 69 | + | type KeybindingsGetter interface { | |
| 70 | + | Execute(ctx context.Context) (KeybindingsConfig, error) | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | // KeybindingSetter updates a single keybinding (narrow interface for the handler). | |
| 74 | + | type KeybindingSetter interface { | |
| 75 | + | Execute(ctx context.Context, req SetKeybindingRequest) (SetKeybindingResponse, error) | |
| 76 | + | } | |
| 77 | + | ||
| 78 | + | // KeybindingResetter resets a single keybinding to default (narrow interface for the handler). | |
| 79 | + | type KeybindingResetter interface { | |
| 80 | + | Execute(ctx context.Context, req ResetKeybindingRequest) error | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | // AllKeybindingsResetter resets all keybindings to defaults (narrow interface for the handler). | |
| 84 | + | type AllKeybindingsResetter interface { | |
| 85 | + | Execute(ctx context.Context) error | |
| 86 | + | } | |
| @@ -64,3 +64,23 @@ type KeybindingsSaver interface { | |||||
| 64 | ResetKeybinding(ctx context.Context, req ResetKeybindingRequest) error | 64 | ResetKeybinding(ctx context.Context, req ResetKeybindingRequest) error | ||
| 65 | ResetAllKeybindings(ctx context.Context) error | 65 | ResetAllKeybindings(ctx context.Context) error | ||
| 66 | } | 66 | } | ||
| 67 | + | ||||
| 68 | + | // KeybindingsGetter retrieves all keybindings (narrow interface for the handler). | |||
| 69 | + | type KeybindingsGetter interface { | |||
| 70 | + | Execute(ctx context.Context) (KeybindingsConfig, error) | |||
| 71 | + | } | |||
| 72 | + | ||||
| 73 | + | // KeybindingSetter updates a single keybinding (narrow interface for the handler). | |||
| 74 | + | type KeybindingSetter interface { | |||
| 75 | + | Execute(ctx context.Context, req SetKeybindingRequest) (SetKeybindingResponse, error) | |||
| 76 | + | } | |||
| 77 | + | ||||
| 78 | + | // KeybindingResetter resets a single keybinding to default (narrow interface for the handler). | |||
| 79 | + | type KeybindingResetter interface { | |||
| 80 | + | Execute(ctx context.Context, req ResetKeybindingRequest) error | |||
| 81 | + | } | |||
| 82 | + | ||||
| 83 | + | // AllKeybindingsResetter resets all keybindings to defaults (narrow interface for the handler). | |||
| 84 | + | type AllKeybindingsResetter interface { | |||
| 85 | + | Execute(ctx context.Context) error | |||
| 86 | + | } | |||
M internal/application/usecase/insert_accent.go +3 -0
| @@ -11,6 +11,9 @@ import ( | |||
| 11 | 11 | "github.com/bnema/dumber/internal/logging" | |
| 12 | 12 | ) | |
| 13 | 13 | ||
| 14 | + | // Compile-time check: InsertAccentUseCase must satisfy port.AccentKeyHandler. | |
| 15 | + | var _ port.AccentKeyHandler = (*InsertAccentUseCase)(nil) | |
| 16 | + | ||
| 14 | 17 | // LongPressDelay is the duration a key must be held to trigger the accent picker. | |
| 15 | 18 | const LongPressDelay = 400 * time.Millisecond | |
| 16 | 19 | ||
| @@ -11,6 +11,9 @@ import ( | |||||
| 11 | "github.com/bnema/dumber/internal/logging" | 11 | "github.com/bnema/dumber/internal/logging" | ||
| 12 | ) | 12 | ) | ||
| 13 | 13 | ||||
| 14 | + | // Compile-time check: InsertAccentUseCase must satisfy port.AccentKeyHandler. | |||
| 15 | + | var _ port.AccentKeyHandler = (*InsertAccentUseCase)(nil) | |||
| 16 | + | ||||
| 14 | // LongPressDelay is the duration a key must be held to trigger the accent picker. | 17 | // LongPressDelay is the duration a key must be held to trigger the accent picker. | ||
| 15 | const LongPressDelay = 400 * time.Millisecond | 18 | const LongPressDelay = 400 * time.Millisecond | ||
| 16 | 19 | ||||
M internal/application/usecase/keybindings.go +6 -0
| @@ -7,6 +7,12 @@ import ( | |||
| 7 | 7 | "github.com/bnema/dumber/internal/application/port" | |
| 8 | 8 | ) | |
| 9 | 9 | ||
| 10 | + | // Compile-time checks: keybinding use cases must satisfy their port interfaces. | |
| 11 | + | var _ port.KeybindingsGetter = (*GetKeybindingsUseCase)(nil) | |
| 12 | + | var _ port.KeybindingSetter = (*SetKeybindingUseCase)(nil) | |
| 13 | + | var _ port.KeybindingResetter = (*ResetKeybindingUseCase)(nil) | |
| 14 | + | var _ port.AllKeybindingsResetter = (*ResetAllKeybindingsUseCase)(nil) | |
| 15 | + | ||
| 10 | 16 | // validModes defines the valid keybinding modes. | |
| 11 | 17 | var validModes = map[string]bool{ | |
| 12 | 18 | "global": true, | |
| @@ -7,6 +7,12 @@ import ( | |||||
| 7 | "github.com/bnema/dumber/internal/application/port" | 7 | "github.com/bnema/dumber/internal/application/port" | ||
| 8 | ) | 8 | ) | ||
| 9 | 9 | ||||
| 10 | + | // Compile-time checks: keybinding use cases must satisfy their port interfaces. | |||
| 11 | + | var _ port.KeybindingsGetter = (*GetKeybindingsUseCase)(nil) | |||
| 12 | + | var _ port.KeybindingSetter = (*SetKeybindingUseCase)(nil) | |||
| 13 | + | var _ port.KeybindingResetter = (*ResetKeybindingUseCase)(nil) | |||
| 14 | + | var _ port.AllKeybindingsResetter = (*ResetAllKeybindingsUseCase)(nil) | |||
| 15 | + | ||||
| 10 | // validModes defines the valid keybinding modes. | 16 | // validModes defines the valid keybinding modes. | ||
| 11 | var validModes = map[string]bool{ | 17 | var validModes = map[string]bool{ | ||
| 12 | "global": true, | 18 | "global": true, | ||
M internal/application/usecase/manage_favorites.go +4 -0
| @@ -8,12 +8,16 @@ import ( | |||
| 8 | 8 | "sync" | |
| 9 | 9 | "time" | |
| 10 | 10 | ||
| 11 | + | "github.com/bnema/dumber/internal/application/port" | |
| 11 | 12 | "github.com/bnema/dumber/internal/domain/entity" | |
| 12 | 13 | "github.com/bnema/dumber/internal/domain/repository" | |
| 13 | 14 | domainurl "github.com/bnema/dumber/internal/domain/url" | |
| 14 | 15 | "github.com/bnema/dumber/internal/logging" | |
| 15 | 16 | ) | |
| 16 | 17 | ||
| 18 | + | // Compile-time check: ManageFavoritesUseCase must satisfy port.HomepageFavorites. | |
| 19 | + | var _ port.HomepageFavorites = (*ManageFavoritesUseCase)(nil) | |
| 20 | + | ||
| 17 | 21 | // ManageFavoritesUseCase handles favorite, folder, and tag operations. | |
| 18 | 22 | type ManageFavoritesUseCase struct { | |
| 19 | 23 | favoriteRepo repository.FavoriteRepository | |
| @@ -8,12 +8,16 @@ import ( | |||||
| 8 | "sync" | 8 | "sync" | ||
| 9 | "time" | 9 | "time" | ||
| 10 | 10 | ||||
| 11 | + | "github.com/bnema/dumber/internal/application/port" | |||
| 11 | "github.com/bnema/dumber/internal/domain/entity" | 12 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 12 | "github.com/bnema/dumber/internal/domain/repository" | 13 | "github.com/bnema/dumber/internal/domain/repository" | ||
| 13 | domainurl "github.com/bnema/dumber/internal/domain/url" | 14 | domainurl "github.com/bnema/dumber/internal/domain/url" | ||
| 14 | "github.com/bnema/dumber/internal/logging" | 15 | "github.com/bnema/dumber/internal/logging" | ||
| 15 | ) | 16 | ) | ||
| 16 | 17 | ||||
| 18 | + | // Compile-time check: ManageFavoritesUseCase must satisfy port.HomepageFavorites. | |||
| 19 | + | var _ port.HomepageFavorites = (*ManageFavoritesUseCase)(nil) | |||
| 20 | + | ||||
| 17 | // ManageFavoritesUseCase handles favorite, folder, and tag operations. | 21 | // ManageFavoritesUseCase handles favorite, folder, and tag operations. | ||
| 18 | type ManageFavoritesUseCase struct { | 22 | type ManageFavoritesUseCase struct { | ||
| 19 | favoriteRepo repository.FavoriteRepository | 23 | favoriteRepo repository.FavoriteRepository | ||
M internal/application/usecase/prepare_download.go +7 -17
| @@ -9,24 +9,14 @@ import ( | |||
| 9 | 9 | "github.com/bnema/dumber/internal/logging" | |
| 10 | 10 | ) | |
| 11 | 11 | ||
| 12 | - | // PrepareDownloadInput contains the inputs for preparing a download destination. | |
| 13 | - | type PrepareDownloadInput struct { | |
| 14 | - | // SuggestedFilename is the filename suggested by WebKit (from Content-Disposition or other sources). | |
| 15 | - | SuggestedFilename string | |
| 16 | - | // Response provides additional metadata (MIME type, URI) for filename resolution. | |
| 17 | - | // May be nil if not available. | |
| 18 | - | Response port.DownloadResponse | |
| 19 | - | // DownloadDir is the directory where downloads should be saved. | |
| 20 | - | DownloadDir string | |
| 21 | - | } | |
| 12 | + | // PrepareDownloadInput is an alias for port.DownloadPrepareInput. | |
| 13 | + | type PrepareDownloadInput = port.DownloadPrepareInput | |
| 22 | 14 | ||
| 23 | - | // PrepareDownloadOutput contains the resolved download destination. | |
| 24 | - | type PrepareDownloadOutput struct { | |
| 25 | - | // Filename is the sanitized, safe filename to use. | |
| 26 | - | Filename string | |
| 27 | - | // DestinationPath is the full path where the download should be saved. | |
| 28 | - | DestinationPath string | |
| 29 | - | } | |
| 15 | + | // PrepareDownloadOutput is an alias for port.DownloadPrepareOutput. | |
| 16 | + | type PrepareDownloadOutput = port.DownloadPrepareOutput | |
| 17 | + | ||
| 18 | + | // Compile-time check: PrepareDownloadUseCase must satisfy port.DownloadPreparer. | |
| 19 | + | var _ port.DownloadPreparer = (*PrepareDownloadUseCase)(nil) | |
| 30 | 20 | ||
| 31 | 21 | // PrepareDownloadUseCase orchestrates filename resolution for downloads. | |
| 32 | 22 | // It combines multiple sources (suggested name, response headers, URI) to | |
| @@ -9,24 +9,14 @@ import ( | |||||
| 9 | "github.com/bnema/dumber/internal/logging" | 9 | "github.com/bnema/dumber/internal/logging" | ||
| 10 | ) | 10 | ) | ||
| 11 | 11 | ||||
| 12 | - | // PrepareDownloadInput contains the inputs for preparing a download destination. | 12 | + | // PrepareDownloadInput is an alias for port.DownloadPrepareInput. |
| 13 | - | type PrepareDownloadInput struct { | 13 | + | type PrepareDownloadInput = port.DownloadPrepareInput |
| 14 | - | // SuggestedFilename is the filename suggested by WebKit (from Content-Disposition or other sources). | |||
| 15 | - | SuggestedFilename string | |||
| 16 | - | // Response provides additional metadata (MIME type, URI) for filename resolution. | |||
| 17 | - | // May be nil if not available. | |||
| 18 | - | Response port.DownloadResponse | |||
| 19 | - | // DownloadDir is the directory where downloads should be saved. | |||
| 20 | - | DownloadDir string | |||
| 21 | - | } | |||
| 22 | 14 | ||||
| 23 | - | // PrepareDownloadOutput contains the resolved download destination. | 15 | + | // PrepareDownloadOutput is an alias for port.DownloadPrepareOutput. |
| 24 | - | type PrepareDownloadOutput struct { | 16 | + | type PrepareDownloadOutput = port.DownloadPrepareOutput |
| 25 | - | // Filename is the sanitized, safe filename to use. | 17 | + | |
| 26 | - | Filename string | 18 | + | // Compile-time check: PrepareDownloadUseCase must satisfy port.DownloadPreparer. |
| 27 | - | // DestinationPath is the full path where the download should be saved. | 19 | + | var _ port.DownloadPreparer = (*PrepareDownloadUseCase)(nil) |
| 28 | - | DestinationPath string | |||
| 29 | - | } | |||
| 30 | 20 | ||||
| 31 | // PrepareDownloadUseCase orchestrates filename resolution for downloads. | 21 | // PrepareDownloadUseCase orchestrates filename resolution for downloads. | ||
| 32 | // It combines multiple sources (suggested name, response headers, URI) to | 22 | // It combines multiple sources (suggested name, response headers, URI) to | ||
M internal/application/usecase/search_history.go +8 -9
| @@ -5,11 +5,15 @@ import ( | |||
| 5 | 5 | "fmt" | |
| 6 | 6 | "time" | |
| 7 | 7 | ||
| 8 | + | "github.com/bnema/dumber/internal/application/port" | |
| 8 | 9 | "github.com/bnema/dumber/internal/domain/entity" | |
| 9 | 10 | "github.com/bnema/dumber/internal/domain/repository" | |
| 10 | 11 | "github.com/bnema/dumber/internal/logging" | |
| 11 | 12 | ) | |
| 12 | 13 | ||
| 14 | + | // Compile-time check: SearchHistoryUseCase must satisfy port.HomepageHistory. | |
| 15 | + | var _ port.HomepageHistory = (*SearchHistoryUseCase)(nil) | |
| 16 | + | ||
| 13 | 17 | // SearchHistoryUseCase handles history search and retrieval operations. | |
| 14 | 18 | type SearchHistoryUseCase struct { | |
| 15 | 19 | historyRepo repository.HistoryRepository | |
| @@ -22,16 +26,11 @@ func NewSearchHistoryUseCase(historyRepo repository.HistoryRepository) *SearchHi | |||
| 22 | 26 | } | |
| 23 | 27 | } | |
| 24 | 28 | ||
| 25 | - | // SearchInput contains search parameters. | |
| 26 | - | type SearchInput struct { | |
| 27 | - | Query string | |
| 28 | - | Limit int | |
| 29 | - | } | |
| 29 | + | // SearchInput is an alias for port.HistorySearchInput. | |
| 30 | + | type SearchInput = port.HistorySearchInput | |
| 30 | 31 | ||
| 31 | - | // SearchOutput contains search results. | |
| 32 | - | type SearchOutput struct { | |
| 33 | - | Matches []entity.HistoryMatch | |
| 34 | - | } | |
| 32 | + | // SearchOutput is an alias for port.HistorySearchOutput. | |
| 33 | + | type SearchOutput = port.HistorySearchOutput | |
| 35 | 34 | ||
| 36 | 35 | // Search performs a full-text search on history entries using SQLite FTS5. | |
| 37 | 36 | // Returns only entries that actually match the query terms. | |
| @@ -5,11 +5,15 @@ import ( | |||||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | "time" | 6 | "time" | ||
| 7 | 7 | ||||
| 8 | + | "github.com/bnema/dumber/internal/application/port" | |||
| 8 | "github.com/bnema/dumber/internal/domain/entity" | 9 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 9 | "github.com/bnema/dumber/internal/domain/repository" | 10 | "github.com/bnema/dumber/internal/domain/repository" | ||
| 10 | "github.com/bnema/dumber/internal/logging" | 11 | "github.com/bnema/dumber/internal/logging" | ||
| 11 | ) | 12 | ) | ||
| 12 | 13 | ||||
| 14 | + | // Compile-time check: SearchHistoryUseCase must satisfy port.HomepageHistory. | |||
| 15 | + | var _ port.HomepageHistory = (*SearchHistoryUseCase)(nil) | |||
| 16 | + | ||||
| 13 | // SearchHistoryUseCase handles history search and retrieval operations. | 17 | // SearchHistoryUseCase handles history search and retrieval operations. | ||
| 14 | type SearchHistoryUseCase struct { | 18 | type SearchHistoryUseCase struct { | ||
| 15 | historyRepo repository.HistoryRepository | 19 | historyRepo repository.HistoryRepository | ||
| @@ -22,16 +26,11 @@ func NewSearchHistoryUseCase(historyRepo repository.HistoryRepository) *SearchHi | |||||
| 22 | } | 26 | } | ||
| 23 | } | 27 | } | ||
| 24 | 28 | ||||
| 25 | - | // SearchInput contains search parameters. | 29 | + | // SearchInput is an alias for port.HistorySearchInput. |
| 26 | - | type SearchInput struct { | 30 | + | type SearchInput = port.HistorySearchInput |
| 27 | - | Query string | |||
| 28 | - | Limit int | |||
| 29 | - | } | |||
| 30 | 31 | ||||
| 31 | - | // SearchOutput contains search results. | 32 | + | // SearchOutput is an alias for port.HistorySearchOutput. |
| 32 | - | type SearchOutput struct { | 33 | + | type SearchOutput = port.HistorySearchOutput |
| 33 | - | Matches []entity.HistoryMatch | |||
| 34 | - | } | |||
| 35 | 34 | ||||
| 36 | // Search performs a full-text search on history entries using SQLite FTS5. | 35 | // Search performs a full-text search on history entries using SQLite FTS5. | ||
| 37 | // Returns only entries that actually match the query terms. | 36 | // Returns only entries that actually match the query terms. | ||
M internal/bootstrap/engine.go +55 -20
| @@ -40,41 +40,46 @@ func BuildEngine(input EngineInput) (port.Engine, error) { | |||
| 40 | 40 | CookiePolicy: port.CookiePolicy(cfg.Engine.CookiePolicy), | |
| 41 | 41 | } | |
| 42 | 42 | wkCfg := webkit.EngineConfigFromConfig(cfg.Engine.WebKit) | |
| 43 | + | ||
| 44 | + | // Pre-build keybindings handler for handler registration. | |
| 45 | + | keybindingsHandler, err := buildKeybindingsHandler() | |
| 46 | + | if err != nil { | |
| 47 | + | return nil, fmt.Errorf("failed to build keybindings handler: %w", err) | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | // Pre-build config save function for handler registration. | |
| 51 | + | saveConfigFunc := buildSaveConfigFunc() | |
| 52 | + | ||
| 43 | 53 | return webkit.NewEngine( | |
| 44 | 54 | input.Ctx, cfg, opts, wkCfg, | |
| 45 | 55 | input.ThemeManager, input.ColorResolver, input.Logger, | |
| 46 | 56 | func(ctx context.Context, router *webkit.MessageRouter, deps port.HandlerDependencies) error { | |
| 47 | - | var historyUC *usecase.SearchHistoryUseCase | |
| 57 | + | var historyUC port.HomepageHistory | |
| 48 | 58 | if deps.HistoryUC != nil { | |
| 49 | - | historyUC, _ = deps.HistoryUC.(*usecase.SearchHistoryUseCase) | |
| 59 | + | historyUC, _ = deps.HistoryUC.(port.HomepageHistory) | |
| 50 | 60 | } | |
| 51 | - | var favoritesUC *usecase.ManageFavoritesUseCase | |
| 61 | + | var favoritesUC port.HomepageFavorites | |
| 52 | 62 | if deps.FavoritesUC != nil { | |
| 53 | - | favoritesUC, _ = deps.FavoritesUC.(*usecase.ManageFavoritesUseCase) | |
| 63 | + | favoritesUC, _ = deps.FavoritesUC.(port.HomepageFavorites) | |
| 54 | 64 | } | |
| 55 | - | var configGetter func() *config.Config | |
| 65 | + | var autoCopyConfig port.AutoCopyConfig | |
| 56 | 66 | if deps.ConfigGetter != nil { | |
| 57 | - | configGetter = func() *config.Config { | |
| 58 | - | raw := deps.ConfigGetter() | |
| 59 | - | if raw == nil { | |
| 60 | - | return nil | |
| 61 | - | } | |
| 62 | - | cfg, _ := raw.(*config.Config) | |
| 63 | - | return cfg | |
| 64 | - | } | |
| 67 | + | autoCopyConfig, _ = deps.ConfigGetter().(port.AutoCopyConfig) | |
| 65 | 68 | } | |
| 66 | 69 | return handlers.RegisterAll(ctx, router, handlers.Config{ | |
| 67 | - | HistoryUC: historyUC, | |
| 68 | - | FavoritesUC: favoritesUC, | |
| 69 | - | Clipboard: deps.Clipboard, | |
| 70 | - | ConfigGetter: configGetter, | |
| 71 | - | OnClipboardCopied: deps.OnClipboardCopied, | |
| 70 | + | HistoryUC: historyUC, | |
| 71 | + | FavoritesUC: favoritesUC, | |
| 72 | + | Clipboard: deps.Clipboard, | |
| 73 | + | AutoCopyConfig: autoCopyConfig, | |
| 74 | + | SaveConfig: saveConfigFunc, | |
| 75 | + | KeybindingsHandler: keybindingsHandler, | |
| 76 | + | OnClipboardCopied: deps.OnClipboardCopied, | |
| 72 | 77 | }) | |
| 73 | 78 | }, | |
| 74 | 79 | func(ctx context.Context, router *webkit.MessageRouter, handler any) error { | |
| 75 | - | accentHandler, ok := handler.(handlers.AccentKeyHandler) | |
| 80 | + | accentHandler, ok := handler.(port.AccentKeyHandler) | |
| 76 | 81 | if !ok { | |
| 77 | - | return fmt.Errorf("handler does not implement AccentKeyHandler") | |
| 82 | + | return fmt.Errorf("handler does not implement port.AccentKeyHandler") | |
| 78 | 83 | } | |
| 79 | 84 | return handlers.RegisterAccentHandlers(ctx, router, accentHandler) | |
| 80 | 85 | }, | |
| @@ -85,3 +90,33 @@ func BuildEngine(input EngineInput) (port.Engine, error) { | |||
| 85 | 90 | return nil, fmt.Errorf("unknown engine type: %q", engineType) | |
| 86 | 91 | } | |
| 87 | 92 | } | |
| 93 | + | ||
| 94 | + | // buildKeybindingsHandler constructs the keybindings handler using the config manager. | |
| 95 | + | func buildKeybindingsHandler() (*handlers.KeybindingsHandler, error) { | |
| 96 | + | mgr := config.GetManager() | |
| 97 | + | if mgr == nil { | |
| 98 | + | return nil, fmt.Errorf("config manager not initialized") | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | gateway := config.NewKeybindingsGateway(mgr) | |
| 102 | + | ||
| 103 | + | return handlers.NewKeybindingsHandler( | |
| 104 | + | usecase.NewGetKeybindingsUseCase(gateway), | |
| 105 | + | usecase.NewSetKeybindingUseCase(gateway, gateway), | |
| 106 | + | usecase.NewResetKeybindingUseCase(gateway), | |
| 107 | + | usecase.NewResetAllKeybindingsUseCase(gateway), | |
| 108 | + | ), nil | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | // buildSaveConfigFunc constructs the config save function using the config manager. | |
| 112 | + | func buildSaveConfigFunc() func(context.Context, port.WebUIConfig) error { | |
| 113 | + | mgr := config.GetManager() | |
| 114 | + | if mgr == nil { | |
| 115 | + | // Return a function that errors — config manager may not be ready yet. | |
| 116 | + | return func(_ context.Context, _ port.WebUIConfig) error { | |
| 117 | + | return fmt.Errorf("config manager not initialized") | |
| 118 | + | } | |
| 119 | + | } | |
| 120 | + | uc := usecase.NewSaveWebUIConfigUseCase(config.NewWebUIConfigGateway(mgr)) | |
| 121 | + | return uc.Execute | |
| 122 | + | } | |
| @@ -40,41 +40,46 @@ func BuildEngine(input EngineInput) (port.Engine, error) { | |||||
| 40 | CookiePolicy: port.CookiePolicy(cfg.Engine.CookiePolicy), | 40 | CookiePolicy: port.CookiePolicy(cfg.Engine.CookiePolicy), | ||
| 41 | } | 41 | } | ||
| 42 | wkCfg := webkit.EngineConfigFromConfig(cfg.Engine.WebKit) | 42 | wkCfg := webkit.EngineConfigFromConfig(cfg.Engine.WebKit) | ||
| 43 | + | ||||
| 44 | + | // Pre-build keybindings handler for handler registration. | |||
| 45 | + | keybindingsHandler, err := buildKeybindingsHandler() | |||
| 46 | + | if err != nil { | |||
| 47 | + | return nil, fmt.Errorf("failed to build keybindings handler: %w", err) | |||
| 48 | + | } | |||
| 49 | + | ||||
| 50 | + | // Pre-build config save function for handler registration. | |||
| 51 | + | saveConfigFunc := buildSaveConfigFunc() | |||
| 52 | + | ||||
| 43 | return webkit.NewEngine( | 53 | return webkit.NewEngine( | ||
| 44 | input.Ctx, cfg, opts, wkCfg, | 54 | input.Ctx, cfg, opts, wkCfg, | ||
| 45 | input.ThemeManager, input.ColorResolver, input.Logger, | 55 | input.ThemeManager, input.ColorResolver, input.Logger, | ||
| 46 | func(ctx context.Context, router *webkit.MessageRouter, deps port.HandlerDependencies) error { | 56 | func(ctx context.Context, router *webkit.MessageRouter, deps port.HandlerDependencies) error { | ||
| 47 | - | var historyUC *usecase.SearchHistoryUseCase | 57 | + | var historyUC port.HomepageHistory |
| 48 | if deps.HistoryUC != nil { | 58 | if deps.HistoryUC != nil { | ||
| 49 | - | historyUC, _ = deps.HistoryUC.(*usecase.SearchHistoryUseCase) | 59 | + | historyUC, _ = deps.HistoryUC.(port.HomepageHistory) |
| 50 | } | 60 | } | ||
| 51 | - | var favoritesUC *usecase.ManageFavoritesUseCase | 61 | + | var favoritesUC port.HomepageFavorites |
| 52 | if deps.FavoritesUC != nil { | 62 | if deps.FavoritesUC != nil { | ||
| 53 | - | favoritesUC, _ = deps.FavoritesUC.(*usecase.ManageFavoritesUseCase) | 63 | + | favoritesUC, _ = deps.FavoritesUC.(port.HomepageFavorites) |
| 54 | } | 64 | } | ||
| 55 | - | var configGetter func() *config.Config | 65 | + | var autoCopyConfig port.AutoCopyConfig |
| 56 | if deps.ConfigGetter != nil { | 66 | if deps.ConfigGetter != nil { | ||
| 57 | - | configGetter = func() *config.Config { | 67 | + | autoCopyConfig, _ = deps.ConfigGetter().(port.AutoCopyConfig) |
| 58 | - | raw := deps.ConfigGetter() | |||
| 59 | - | if raw == nil { | |||
| 60 | - | return nil | |||
| 61 | - | } | |||
| 62 | - | cfg, _ := raw.(*config.Config) | |||
| 63 | - | return cfg | |||
| 64 | - | } | |||
| 65 | } | 68 | } | ||
| 66 | return handlers.RegisterAll(ctx, router, handlers.Config{ | 69 | return handlers.RegisterAll(ctx, router, handlers.Config{ | ||
| 67 | - | HistoryUC: historyUC, | 70 | + | HistoryUC: historyUC, |
| 68 | - | FavoritesUC: favoritesUC, | 71 | + | FavoritesUC: favoritesUC, |
| 69 | - | Clipboard: deps.Clipboard, | 72 | + | Clipboard: deps.Clipboard, |
| 70 | - | ConfigGetter: configGetter, | 73 | + | AutoCopyConfig: autoCopyConfig, |
| 71 | - | OnClipboardCopied: deps.OnClipboardCopied, | 74 | + | SaveConfig: saveConfigFunc, |
| 75 | + | KeybindingsHandler: keybindingsHandler, | |||
| 76 | + | OnClipboardCopied: deps.OnClipboardCopied, | |||
| 72 | }) | 77 | }) | ||
| 73 | }, | 78 | }, | ||
| 74 | func(ctx context.Context, router *webkit.MessageRouter, handler any) error { | 79 | func(ctx context.Context, router *webkit.MessageRouter, handler any) error { | ||
| 75 | - | accentHandler, ok := handler.(handlers.AccentKeyHandler) | 80 | + | accentHandler, ok := handler.(port.AccentKeyHandler) |
| 76 | if !ok { | 81 | if !ok { | ||
| 77 | - | return fmt.Errorf("handler does not implement AccentKeyHandler") | 82 | + | return fmt.Errorf("handler does not implement port.AccentKeyHandler") |
| 78 | } | 83 | } | ||
| 79 | return handlers.RegisterAccentHandlers(ctx, router, accentHandler) | 84 | return handlers.RegisterAccentHandlers(ctx, router, accentHandler) | ||
| 80 | }, | 85 | }, | ||
| @@ -85,3 +90,33 @@ func BuildEngine(input EngineInput) (port.Engine, error) { | |||||
| 85 | return nil, fmt.Errorf("unknown engine type: %q", engineType) | 90 | return nil, fmt.Errorf("unknown engine type: %q", engineType) | ||
| 86 | } | 91 | } | ||
| 87 | } | 92 | } | ||
| 93 | + | ||||
| 94 | + | // buildKeybindingsHandler constructs the keybindings handler using the config manager. | |||
| 95 | + | func buildKeybindingsHandler() (*handlers.KeybindingsHandler, error) { | |||
| 96 | + | mgr := config.GetManager() | |||
| 97 | + | if mgr == nil { | |||
| 98 | + | return nil, fmt.Errorf("config manager not initialized") | |||
| 99 | + | } | |||
| 100 | + | ||||
| 101 | + | gateway := config.NewKeybindingsGateway(mgr) | |||
| 102 | + | ||||
| 103 | + | return handlers.NewKeybindingsHandler( | |||
| 104 | + | usecase.NewGetKeybindingsUseCase(gateway), | |||
| 105 | + | usecase.NewSetKeybindingUseCase(gateway, gateway), | |||
| 106 | + | usecase.NewResetKeybindingUseCase(gateway), | |||
| 107 | + | usecase.NewResetAllKeybindingsUseCase(gateway), | |||
| 108 | + | ), nil | |||
| 109 | + | } | |||
| 110 | + | ||||
| 111 | + | // buildSaveConfigFunc constructs the config save function using the config manager. | |||
| 112 | + | func buildSaveConfigFunc() func(context.Context, port.WebUIConfig) error { | |||
| 113 | + | mgr := config.GetManager() | |||
| 114 | + | if mgr == nil { | |||
| 115 | + | // Return a function that errors — config manager may not be ready yet. | |||
| 116 | + | return func(_ context.Context, _ port.WebUIConfig) error { | |||
| 117 | + | return fmt.Errorf("config manager not initialized") | |||
| 118 | + | } | |||
| 119 | + | } | |||
| 120 | + | uc := usecase.NewSaveWebUIConfigUseCase(config.NewWebUIConfigGateway(mgr)) | |||
| 121 | + | return uc.Execute | |||
| 122 | + | } | |||
M internal/infrastructure/filtering/manager.go +4 -0
| @@ -12,10 +12,14 @@ import ( | |||
| 12 | 12 | "sync/atomic" | |
| 13 | 13 | "time" | |
| 14 | 14 | ||
| 15 | + | "github.com/bnema/dumber/internal/application/port" | |
| 15 | 16 | "github.com/bnema/dumber/internal/logging" | |
| 16 | 17 | "github.com/bnema/puregotk-webkit/webkit" | |
| 17 | 18 | ) | |
| 18 | 19 | ||
| 20 | + | // Compile-time check that Manager satisfies port.FilterManager. | |
| 21 | + | var _ port.FilterManager = (*Manager)(nil) | |
| 22 | + | ||
| 19 | 23 | const ( | |
| 20 | 24 | storeDirPerm = 0o755 | |
| 21 | 25 | jsonDirPerm = 0o755 | |
| @@ -12,10 +12,14 @@ import ( | |||||
| 12 | "sync/atomic" | 12 | "sync/atomic" | ||
| 13 | "time" | 13 | "time" | ||
| 14 | 14 | ||||
| 15 | + | "github.com/bnema/dumber/internal/application/port" | |||
| 15 | "github.com/bnema/dumber/internal/logging" | 16 | "github.com/bnema/dumber/internal/logging" | ||
| 16 | "github.com/bnema/puregotk-webkit/webkit" | 17 | "github.com/bnema/puregotk-webkit/webkit" | ||
| 17 | ) | 18 | ) | ||
| 18 | 19 | ||||
| 20 | + | // Compile-time check that Manager satisfies port.FilterManager. | |||
| 21 | + | var _ port.FilterManager = (*Manager)(nil) | |||
| 22 | + | ||||
| 19 | const ( | 23 | const ( | ||
| 20 | storeDirPerm = 0o755 | 24 | storeDirPerm = 0o755 | ||
| 21 | jsonDirPerm = 0o755 | 25 | jsonDirPerm = 0o755 | ||
M internal/infrastructure/filtering/types.go +15 -16
| @@ -1,31 +1,30 @@ | |||
| 1 | 1 | package filtering | |
| 2 | 2 | ||
| 3 | - | import "time" | |
| 3 | + | import ( | |
| 4 | + | "time" | |
| 4 | 5 | ||
| 5 | - | // FilterState represents the current state of the content filter system. | |
| 6 | - | type FilterState string | |
| 6 | + | "github.com/bnema/dumber/internal/application/port" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | // FilterState is an alias for port.FilterState to avoid breaking existing references. | |
| 10 | + | type FilterState = port.FilterState | |
| 11 | + | ||
| 12 | + | // FilterStatus is an alias for port.FilterStatus so filtering.Manager satisfies port.FilterManager. | |
| 13 | + | type FilterStatus = port.FilterStatus | |
| 7 | 14 | ||
| 8 | 15 | const ( | |
| 9 | 16 | // StateUninitialized means the filter manager has not been initialized yet. | |
| 10 | - | StateUninitialized FilterState = "uninitialized" | |
| 17 | + | StateUninitialized FilterState = port.FilterStateUninitialized | |
| 11 | 18 | // StateLoading means filters are being downloaded or compiled. | |
| 12 | - | StateLoading FilterState = "loading" | |
| 19 | + | StateLoading FilterState = port.FilterStateLoading | |
| 13 | 20 | // StateActive means filters are loaded and active. | |
| 14 | - | StateActive FilterState = "active" | |
| 21 | + | StateActive FilterState = port.FilterStateActive | |
| 15 | 22 | // StateDisabled means filtering is disabled by configuration. | |
| 16 | - | StateDisabled FilterState = "disabled" | |
| 23 | + | StateDisabled FilterState = port.FilterStateDisabled | |
| 17 | 24 | // StateError means an error occurred during filter loading. | |
| 18 | - | StateError FilterState = "error" | |
| 25 | + | StateError FilterState = port.FilterStateError | |
| 19 | 26 | ) | |
| 20 | 27 | ||
| 21 | - | // FilterStatus represents the current status of the content filter system. | |
| 22 | - | // Used for UI toast notifications and status reporting. | |
| 23 | - | type FilterStatus struct { | |
| 24 | - | State FilterState | |
| 25 | - | Message string | |
| 26 | - | Version string | |
| 27 | - | } | |
| 28 | - | ||
| 29 | 28 | // Manifest represents the manifest.json from ublock-webkit-filters releases. | |
| 30 | 29 | // It contains metadata about the filter lists and their rule counts. | |
| 31 | 30 | type Manifest struct { | |
| @@ -1,31 +1,30 @@ | |||||
| 1 | package filtering | 1 | package filtering | ||
| 2 | 2 | ||||
| 3 | - | import "time" | 3 | + | import ( |
| 4 | + | "time" | |||
| 4 | 5 | ||||
| 5 | - | // FilterState represents the current state of the content filter system. | 6 | + | "github.com/bnema/dumber/internal/application/port" |
| 6 | - | type FilterState string | 7 | + | ) |
| 8 | + | ||||
| 9 | + | // FilterState is an alias for port.FilterState to avoid breaking existing references. | |||
| 10 | + | type FilterState = port.FilterState | |||
| 11 | + | ||||
| 12 | + | // FilterStatus is an alias for port.FilterStatus so filtering.Manager satisfies port.FilterManager. | |||
| 13 | + | type FilterStatus = port.FilterStatus | |||
| 7 | 14 | ||||
| 8 | const ( | 15 | const ( | ||
| 9 | // StateUninitialized means the filter manager has not been initialized yet. | 16 | // StateUninitialized means the filter manager has not been initialized yet. | ||
| 10 | - | StateUninitialized FilterState = "uninitialized" | 17 | + | StateUninitialized FilterState = port.FilterStateUninitialized |
| 11 | // StateLoading means filters are being downloaded or compiled. | 18 | // StateLoading means filters are being downloaded or compiled. | ||
| 12 | - | StateLoading FilterState = "loading" | 19 | + | StateLoading FilterState = port.FilterStateLoading |
| 13 | // StateActive means filters are loaded and active. | 20 | // StateActive means filters are loaded and active. | ||
| 14 | - | StateActive FilterState = "active" | 21 | + | StateActive FilterState = port.FilterStateActive |
| 15 | // StateDisabled means filtering is disabled by configuration. | 22 | // StateDisabled means filtering is disabled by configuration. | ||
| 16 | - | StateDisabled FilterState = "disabled" | 23 | + | StateDisabled FilterState = port.FilterStateDisabled |
| 17 | // StateError means an error occurred during filter loading. | 24 | // StateError means an error occurred during filter loading. | ||
| 18 | - | StateError FilterState = "error" | 25 | + | StateError FilterState = port.FilterStateError |
| 19 | ) | 26 | ) | ||
| 20 | 27 | ||||
| 21 | - | // FilterStatus represents the current status of the content filter system. | |||
| 22 | - | // Used for UI toast notifications and status reporting. | |||
| 23 | - | type FilterStatus struct { | |||
| 24 | - | State FilterState | |||
| 25 | - | Message string | |||
| 26 | - | Version string | |||
| 27 | - | } | |||
| 28 | - | ||||
| 29 | // Manifest represents the manifest.json from ublock-webkit-filters releases. | 28 | // Manifest represents the manifest.json from ublock-webkit-filters releases. | ||
| 30 | // It contains metadata about the filter lists and their rule counts. | 29 | // It contains metadata about the filter lists and their rule counts. | ||
| 31 | type Manifest struct { | 30 | type Manifest struct { | ||
M internal/infrastructure/webkit/handlers/clipboard.go +10 -12
| @@ -5,24 +5,23 @@ import ( | |||
| 5 | 5 | "encoding/json" | |
| 6 | 6 | ||
| 7 | 7 | "github.com/bnema/dumber/internal/application/port" | |
| 8 | - | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 9 | 8 | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 10 | 9 | "github.com/bnema/dumber/internal/logging" | |
| 11 | 10 | ) | |
| 12 | 11 | ||
| 13 | 12 | // ClipboardHandler handles clipboard-related messages from webviews. | |
| 14 | 13 | type ClipboardHandler struct { | |
| 15 | - | clipboard port.Clipboard | |
| 16 | - | configGetter func() *config.Config | |
| 17 | - | onCopied func(textLen int) // Called after successful auto-copy (for toast notification) | |
| 14 | + | clipboard port.Clipboard | |
| 15 | + | autoCopyConfig port.AutoCopyConfig | |
| 16 | + | onCopied func(textLen int) // Called after successful auto-copy (for toast notification) | |
| 18 | 17 | } | |
| 19 | 18 | ||
| 20 | 19 | // NewClipboardHandler creates a new ClipboardHandler. | |
| 21 | - | func NewClipboardHandler(clipboard port.Clipboard, configGetter func() *config.Config, onCopied func(textLen int)) *ClipboardHandler { | |
| 20 | + | func NewClipboardHandler(clipboard port.Clipboard, autoCopyConfig port.AutoCopyConfig, onCopied func(textLen int)) *ClipboardHandler { | |
| 22 | 21 | return &ClipboardHandler{ | |
| 23 | - | clipboard: clipboard, | |
| 24 | - | configGetter: configGetter, | |
| 25 | - | onCopied: onCopied, | |
| 22 | + | clipboard: clipboard, | |
| 23 | + | autoCopyConfig: autoCopyConfig, | |
| 24 | + | onCopied: onCopied, | |
| 26 | 25 | } | |
| 27 | 26 | } | |
| 28 | 27 | ||
| @@ -38,8 +37,7 @@ func (h *ClipboardHandler) HandleAutoCopySelection() webkit.MessageHandler { | |||
| 38 | 37 | log := logging.FromContext(ctx) | |
| 39 | 38 | ||
| 40 | 39 | // Check if feature is enabled | |
| 41 | - | cfg := h.configGetter() | |
| 42 | - | if cfg == nil || !cfg.Clipboard.AutoCopyOnSelection { | |
| 40 | + | if h.autoCopyConfig == nil || !h.autoCopyConfig.IsAutoCopyEnabled() { | |
| 43 | 41 | return nil, nil | |
| 44 | 42 | } | |
| 45 | 43 | ||
| @@ -75,10 +73,10 @@ func RegisterClipboardHandlers( | |||
| 75 | 73 | ctx context.Context, | |
| 76 | 74 | router *webkit.MessageRouter, | |
| 77 | 75 | clipboard port.Clipboard, | |
| 78 | - | configGetter func() *config.Config, | |
| 76 | + | autoCopyConfig port.AutoCopyConfig, | |
| 79 | 77 | onCopied func(textLen int), | |
| 80 | 78 | ) error { | |
| 81 | - | handler := NewClipboardHandler(clipboard, configGetter, onCopied) | |
| 79 | + | handler := NewClipboardHandler(clipboard, autoCopyConfig, onCopied) | |
| 82 | 80 | ||
| 83 | 81 | // Register auto_copy_selection handler | |
| 84 | 82 | if err := router.RegisterHandler("auto_copy_selection", handler.HandleAutoCopySelection()); err != nil { | |
| @@ -5,24 +5,23 @@ import ( | |||||
| 5 | "encoding/json" | 5 | "encoding/json" | ||
| 6 | 6 | ||||
| 7 | "github.com/bnema/dumber/internal/application/port" | 7 | "github.com/bnema/dumber/internal/application/port" | ||
| 8 | - | "github.com/bnema/dumber/internal/infrastructure/config" | |||
| 9 | "github.com/bnema/dumber/internal/infrastructure/webkit" | 8 | "github.com/bnema/dumber/internal/infrastructure/webkit" | ||
| 10 | "github.com/bnema/dumber/internal/logging" | 9 | "github.com/bnema/dumber/internal/logging" | ||
| 11 | ) | 10 | ) | ||
| 12 | 11 | ||||
| 13 | // ClipboardHandler handles clipboard-related messages from webviews. | 12 | // ClipboardHandler handles clipboard-related messages from webviews. | ||
| 14 | type ClipboardHandler struct { | 13 | type ClipboardHandler struct { | ||
| 15 | - | clipboard port.Clipboard | 14 | + | clipboard port.Clipboard |
| 16 | - | configGetter func() *config.Config | 15 | + | autoCopyConfig port.AutoCopyConfig |
| 17 | - | onCopied func(textLen int) // Called after successful auto-copy (for toast notification) | 16 | + | onCopied func(textLen int) // Called after successful auto-copy (for toast notification) |
| 18 | } | 17 | } | ||
| 19 | 18 | ||||
| 20 | // NewClipboardHandler creates a new ClipboardHandler. | 19 | // NewClipboardHandler creates a new ClipboardHandler. | ||
| 21 | - | func NewClipboardHandler(clipboard port.Clipboard, configGetter func() *config.Config, onCopied func(textLen int)) *ClipboardHandler { | 20 | + | func NewClipboardHandler(clipboard port.Clipboard, autoCopyConfig port.AutoCopyConfig, onCopied func(textLen int)) *ClipboardHandler { |
| 22 | return &ClipboardHandler{ | 21 | return &ClipboardHandler{ | ||
| 23 | - | clipboard: clipboard, | 22 | + | clipboard: clipboard, |
| 24 | - | configGetter: configGetter, | 23 | + | autoCopyConfig: autoCopyConfig, |
| 25 | - | onCopied: onCopied, | 24 | + | onCopied: onCopied, |
| 26 | } | 25 | } | ||
| 27 | } | 26 | } | ||
| 28 | 27 | ||||
| @@ -38,8 +37,7 @@ func (h *ClipboardHandler) HandleAutoCopySelection() webkit.MessageHandler { | |||||
| 38 | log := logging.FromContext(ctx) | 37 | log := logging.FromContext(ctx) | ||
| 39 | 38 | ||||
| 40 | // Check if feature is enabled | 39 | // Check if feature is enabled | ||
| 41 | - | cfg := h.configGetter() | 40 | + | if h.autoCopyConfig == nil || !h.autoCopyConfig.IsAutoCopyEnabled() { |
| 42 | - | if cfg == nil || !cfg.Clipboard.AutoCopyOnSelection { | |||
| 43 | return nil, nil | 41 | return nil, nil | ||
| 44 | } | 42 | } | ||
| 45 | 43 | ||||
| @@ -75,10 +73,10 @@ func RegisterClipboardHandlers( | |||||
| 75 | ctx context.Context, | 73 | ctx context.Context, | ||
| 76 | router *webkit.MessageRouter, | 74 | router *webkit.MessageRouter, | ||
| 77 | clipboard port.Clipboard, | 75 | clipboard port.Clipboard, | ||
| 78 | - | configGetter func() *config.Config, | 76 | + | autoCopyConfig port.AutoCopyConfig, |
| 79 | onCopied func(textLen int), | 77 | onCopied func(textLen int), | ||
| 80 | ) error { | 78 | ) error { | ||
| 81 | - | handler := NewClipboardHandler(clipboard, configGetter, onCopied) | 79 | + | handler := NewClipboardHandler(clipboard, autoCopyConfig, onCopied) |
| 82 | 80 | ||||
| 83 | // Register auto_copy_selection handler | 81 | // Register auto_copy_selection handler | ||
| 84 | if err := router.RegisterHandler("auto_copy_selection", handler.HandleAutoCopySelection()); err != nil { | 82 | if err := router.RegisterHandler("auto_copy_selection", handler.HandleAutoCopySelection()); err != nil { | ||
M internal/infrastructure/webkit/handlers/config.go +12 -19
| @@ -6,18 +6,19 @@ import ( | |||
| 6 | 6 | "fmt" | |
| 7 | 7 | ||
| 8 | 8 | "github.com/bnema/dumber/internal/application/port" | |
| 9 | - | "github.com/bnema/dumber/internal/application/usecase" | |
| 10 | - | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 11 | 9 | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 12 | 10 | "github.com/bnema/dumber/internal/logging" | |
| 13 | 11 | ) | |
| 14 | 12 | ||
| 15 | 13 | // ConfigHandler handles configuration-related messages. | |
| 16 | - | type ConfigHandler struct{} | |
| 14 | + | type ConfigHandler struct { | |
| 15 | + | saveConfig func(context.Context, port.WebUIConfig) error | |
| 16 | + | } | |
| 17 | 17 | ||
| 18 | 18 | // NewConfigHandler creates a new ConfigHandler. | |
| 19 | - | func NewConfigHandler() *ConfigHandler { | |
| 20 | - | return &ConfigHandler{} | |
| 19 | + | // saveConfig is called to persist config changes (typically usecase.SaveWebUIConfigUseCase.Execute). | |
| 20 | + | func NewConfigHandler(saveConfig func(context.Context, port.WebUIConfig) error) *ConfigHandler { | |
| 21 | + | return &ConfigHandler{saveConfig: saveConfig} | |
| 21 | 22 | } | |
| 22 | 23 | ||
| 23 | 24 | // Handle processes the save_config message. | |
| @@ -25,6 +26,9 @@ func (h *ConfigHandler) Handle(ctx context.Context, _ webkit.WebViewID, payload | |||
| 25 | 26 | if h == nil { | |
| 26 | 27 | return nil, fmt.Errorf("config handler is nil") | |
| 27 | 28 | } | |
| 29 | + | if h.saveConfig == nil { | |
| 30 | + | return nil, fmt.Errorf("config handler: saveConfig not initialized") | |
| 31 | + | } | |
| 28 | 32 | log := logging.FromContext(ctx).With().Str("handler", "config").Logger() | |
| 29 | 33 | ||
| 30 | 34 | var payloadCfg port.WebUIConfig | |
| @@ -34,13 +38,8 @@ func (h *ConfigHandler) Handle(ctx context.Context, _ webkit.WebViewID, payload | |||
| 34 | 38 | } | |
| 35 | 39 | ||
| 36 | 40 | log.Info().Msg("saving appearance configuration from webui") | |
| 37 | - | mgr := config.GetManager() | |
| 38 | - | if mgr == nil { | |
| 39 | - | return nil, fmt.Errorf("config manager not initialized") | |
| 40 | - | } | |
| 41 | 41 | ||
| 42 | - | uc := usecase.NewSaveWebUIConfigUseCase(config.NewWebUIConfigGateway(mgr)) | |
| 43 | - | if err := uc.Execute(ctx, payloadCfg); err != nil { | |
| 42 | + | if err := h.saveConfig(ctx, payloadCfg); err != nil { | |
| 44 | 43 | log.Error().Err(err).Msg("failed to save config") | |
| 45 | 44 | return nil, fmt.Errorf("failed to save config: %w", err) | |
| 46 | 45 | } | |
| @@ -49,17 +48,11 @@ func (h *ConfigHandler) Handle(ctx context.Context, _ webkit.WebViewID, payload | |||
| 49 | 48 | } | |
| 50 | 49 | ||
| 51 | 50 | // RegisterConfigHandlers registers configuration handlers with the router. | |
| 52 | - | func RegisterConfigHandlers(ctx context.Context, router *webkit.MessageRouter) error { | |
| 53 | - | handler := NewConfigHandler() | |
| 54 | - | ||
| 55 | - | // worldName empty means main world (since dumb:// pages run in main world) | |
| 56 | - | // callback/errorCallback can be used if we want to notify JS of success/fail | |
| 57 | - | // In Svelte we use fetch() for GET, but for POST we use message bridge | |
| 58 | - | // Actually, if we use message bridge, we might want response callbacks. | |
| 51 | + | func RegisterConfigHandlers(ctx context.Context, router *webkit.MessageRouter, saveConfig func(context.Context, port.WebUIConfig) error) error { | |
| 52 | + | handler := NewConfigHandler(saveConfig) | |
| 59 | 53 | ||
| 60 | 54 | log := logging.FromContext(ctx).With().Str("component", "handlers").Logger() | |
| 61 | 55 | ||
| 62 | - | // Message type: save_config | |
| 63 | 56 | if err := router.RegisterHandlerWithCallbacks( | |
| 64 | 57 | "save_config", | |
| 65 | 58 | "__dumber_config_saved", | |
| @@ -6,18 +6,19 @@ import ( | |||||
| 6 | "fmt" | 6 | "fmt" | ||
| 7 | 7 | ||||
| 8 | "github.com/bnema/dumber/internal/application/port" | 8 | "github.com/bnema/dumber/internal/application/port" | ||
| 9 | - | "github.com/bnema/dumber/internal/application/usecase" | |||
| 10 | - | "github.com/bnema/dumber/internal/infrastructure/config" | |||
| 11 | "github.com/bnema/dumber/internal/infrastructure/webkit" | 9 | "github.com/bnema/dumber/internal/infrastructure/webkit" | ||
| 12 | "github.com/bnema/dumber/internal/logging" | 10 | "github.com/bnema/dumber/internal/logging" | ||
| 13 | ) | 11 | ) | ||
| 14 | 12 | ||||
| 15 | // ConfigHandler handles configuration-related messages. | 13 | // ConfigHandler handles configuration-related messages. | ||
| 16 | - | type ConfigHandler struct{} | 14 | + | type ConfigHandler struct { |
| 15 | + | saveConfig func(context.Context, port.WebUIConfig) error | |||
| 16 | + | } | |||
| 17 | 17 | ||||
| 18 | // NewConfigHandler creates a new ConfigHandler. | 18 | // NewConfigHandler creates a new ConfigHandler. | ||
| 19 | - | func NewConfigHandler() *ConfigHandler { | 19 | + | // saveConfig is called to persist config changes (typically usecase.SaveWebUIConfigUseCase.Execute). |
| 20 | - | return &ConfigHandler{} | 20 | + | func NewConfigHandler(saveConfig func(context.Context, port.WebUIConfig) error) *ConfigHandler { |
| 21 | + | return &ConfigHandler{saveConfig: saveConfig} | |||
| 21 | } | 22 | } | ||
| 22 | 23 | ||||
| 23 | // Handle processes the save_config message. | 24 | // Handle processes the save_config message. | ||
| @@ -25,6 +26,9 @@ func (h *ConfigHandler) Handle(ctx context.Context, _ webkit.WebViewID, payload | |||||
| 25 | if h == nil { | 26 | if h == nil { | ||
| 26 | return nil, fmt.Errorf("config handler is nil") | 27 | return nil, fmt.Errorf("config handler is nil") | ||
| 27 | } | 28 | } | ||
| 29 | + | if h.saveConfig == nil { | |||
| 30 | + | return nil, fmt.Errorf("config handler: saveConfig not initialized") | |||
| 31 | + | } | |||
| 28 | log := logging.FromContext(ctx).With().Str("handler", "config").Logger() | 32 | log := logging.FromContext(ctx).With().Str("handler", "config").Logger() | ||
| 29 | 33 | ||||
| 30 | var payloadCfg port.WebUIConfig | 34 | var payloadCfg port.WebUIConfig | ||
| @@ -34,13 +38,8 @@ func (h *ConfigHandler) Handle(ctx context.Context, _ webkit.WebViewID, payload | |||||
| 34 | } | 38 | } | ||
| 35 | 39 | ||||
| 36 | log.Info().Msg("saving appearance configuration from webui") | 40 | log.Info().Msg("saving appearance configuration from webui") | ||
| 37 | - | mgr := config.GetManager() | |||
| 38 | - | if mgr == nil { | |||
| 39 | - | return nil, fmt.Errorf("config manager not initialized") | |||
| 40 | - | } | |||
| 41 | 41 | ||||
| 42 | - | uc := usecase.NewSaveWebUIConfigUseCase(config.NewWebUIConfigGateway(mgr)) | 42 | + | if err := h.saveConfig(ctx, payloadCfg); err != nil { |
| 43 | - | if err := uc.Execute(ctx, payloadCfg); err != nil { | |||
| 44 | log.Error().Err(err).Msg("failed to save config") | 43 | log.Error().Err(err).Msg("failed to save config") | ||
| 45 | return nil, fmt.Errorf("failed to save config: %w", err) | 44 | return nil, fmt.Errorf("failed to save config: %w", err) | ||
| 46 | } | 45 | } | ||
| @@ -49,17 +48,11 @@ func (h *ConfigHandler) Handle(ctx context.Context, _ webkit.WebViewID, payload | |||||
| 49 | } | 48 | } | ||
| 50 | 49 | ||||
| 51 | // RegisterConfigHandlers registers configuration handlers with the router. | 50 | // RegisterConfigHandlers registers configuration handlers with the router. | ||
| 52 | - | func RegisterConfigHandlers(ctx context.Context, router *webkit.MessageRouter) error { | 51 | + | func RegisterConfigHandlers(ctx context.Context, router *webkit.MessageRouter, saveConfig func(context.Context, port.WebUIConfig) error) error { |
| 53 | - | handler := NewConfigHandler() | 52 | + | handler := NewConfigHandler(saveConfig) |
| 54 | - | ||||
| 55 | - | // worldName empty means main world (since dumb:// pages run in main world) | |||
| 56 | - | // callback/errorCallback can be used if we want to notify JS of success/fail | |||
| 57 | - | // In Svelte we use fetch() for GET, but for POST we use message bridge | |||
| 58 | - | // Actually, if we use message bridge, we might want response callbacks. | |||
| 59 | 53 | ||||
| 60 | log := logging.FromContext(ctx).With().Str("component", "handlers").Logger() | 54 | log := logging.FromContext(ctx).With().Str("component", "handlers").Logger() | ||
| 61 | 55 | ||||
| 62 | - | // Message type: save_config | |||
| 63 | if err := router.RegisterHandlerWithCallbacks( | 56 | if err := router.RegisterHandlerWithCallbacks( | ||
| 64 | "save_config", | 57 | "save_config", | ||
| 65 | "__dumber_config_saved", | 58 | "__dumber_config_saved", | ||
M internal/infrastructure/webkit/handlers/homepage/favorites.go +3 -3
| @@ -4,7 +4,7 @@ import ( | |||
| 4 | 4 | "context" | |
| 5 | 5 | "encoding/json" | |
| 6 | 6 | ||
| 7 | - | "github.com/bnema/dumber/internal/application/usecase" | |
| 7 | + | "github.com/bnema/dumber/internal/application/port" | |
| 8 | 8 | "github.com/bnema/dumber/internal/domain/entity" | |
| 9 | 9 | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 10 | 10 | "github.com/bnema/dumber/internal/logging" | |
| @@ -12,11 +12,11 @@ import ( | |||
| 12 | 12 | ||
| 13 | 13 | // FavoritesHandlers handles favorite-related messages from the homepage. | |
| 14 | 14 | type FavoritesHandlers struct { | |
| 15 | - | favoritesUC *usecase.ManageFavoritesUseCase | |
| 15 | + | favoritesUC port.HomepageFavorites | |
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | 18 | // NewFavoritesHandlers creates a new FavoritesHandlers instance. | |
| 19 | - | func NewFavoritesHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *FavoritesHandlers { | |
| 19 | + | func NewFavoritesHandlers(favoritesUC port.HomepageFavorites) *FavoritesHandlers { | |
| 20 | 20 | return &FavoritesHandlers{favoritesUC: favoritesUC} | |
| 21 | 21 | } | |
| 22 | 22 | ||
| @@ -4,7 +4,7 @@ import ( | |||||
| 4 | "context" | 4 | "context" | ||
| 5 | "encoding/json" | 5 | "encoding/json" | ||
| 6 | 6 | ||||
| 7 | - | "github.com/bnema/dumber/internal/application/usecase" | 7 | + | "github.com/bnema/dumber/internal/application/port" |
| 8 | "github.com/bnema/dumber/internal/domain/entity" | 8 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 9 | "github.com/bnema/dumber/internal/infrastructure/webkit" | 9 | "github.com/bnema/dumber/internal/infrastructure/webkit" | ||
| 10 | "github.com/bnema/dumber/internal/logging" | 10 | "github.com/bnema/dumber/internal/logging" | ||
| @@ -12,11 +12,11 @@ import ( | |||||
| 12 | 12 | ||||
| 13 | // FavoritesHandlers handles favorite-related messages from the homepage. | 13 | // FavoritesHandlers handles favorite-related messages from the homepage. | ||
| 14 | type FavoritesHandlers struct { | 14 | type FavoritesHandlers struct { | ||
| 15 | - | favoritesUC *usecase.ManageFavoritesUseCase | 15 | + | favoritesUC port.HomepageFavorites |
| 16 | } | 16 | } | ||
| 17 | 17 | ||||
| 18 | // NewFavoritesHandlers creates a new FavoritesHandlers instance. | 18 | // NewFavoritesHandlers creates a new FavoritesHandlers instance. | ||
| 19 | - | func NewFavoritesHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *FavoritesHandlers { | 19 | + | func NewFavoritesHandlers(favoritesUC port.HomepageFavorites) *FavoritesHandlers { |
| 20 | return &FavoritesHandlers{favoritesUC: favoritesUC} | 20 | return &FavoritesHandlers{favoritesUC: favoritesUC} | ||
| 21 | } | 21 | } | ||
| 22 | 22 | ||||
M internal/infrastructure/webkit/handlers/homepage/folders.go +3 -3
| @@ -4,7 +4,7 @@ import ( | |||
| 4 | 4 | "context" | |
| 5 | 5 | "encoding/json" | |
| 6 | 6 | ||
| 7 | - | "github.com/bnema/dumber/internal/application/usecase" | |
| 7 | + | "github.com/bnema/dumber/internal/application/port" | |
| 8 | 8 | "github.com/bnema/dumber/internal/domain/entity" | |
| 9 | 9 | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 10 | 10 | "github.com/bnema/dumber/internal/logging" | |
| @@ -12,11 +12,11 @@ import ( | |||
| 12 | 12 | ||
| 13 | 13 | // FolderHandlers handles folder-related messages from the homepage. | |
| 14 | 14 | type FolderHandlers struct { | |
| 15 | - | favoritesUC *usecase.ManageFavoritesUseCase | |
| 15 | + | favoritesUC port.HomepageFavorites | |
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | 18 | // NewFolderHandlers creates a new FolderHandlers instance. | |
| 19 | - | func NewFolderHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *FolderHandlers { | |
| 19 | + | func NewFolderHandlers(favoritesUC port.HomepageFavorites) *FolderHandlers { | |
| 20 | 20 | return &FolderHandlers{favoritesUC: favoritesUC} | |
| 21 | 21 | } | |
| 22 | 22 | ||
| @@ -4,7 +4,7 @@ import ( | |||||
| 4 | "context" | 4 | "context" | ||
| 5 | "encoding/json" | 5 | "encoding/json" | ||
| 6 | 6 | ||||
| 7 | - | "github.com/bnema/dumber/internal/application/usecase" | 7 | + | "github.com/bnema/dumber/internal/application/port" |
| 8 | "github.com/bnema/dumber/internal/domain/entity" | 8 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 9 | "github.com/bnema/dumber/internal/infrastructure/webkit" | 9 | "github.com/bnema/dumber/internal/infrastructure/webkit" | ||
| 10 | "github.com/bnema/dumber/internal/logging" | 10 | "github.com/bnema/dumber/internal/logging" | ||
| @@ -12,11 +12,11 @@ import ( | |||||
| 12 | 12 | ||||
| 13 | // FolderHandlers handles folder-related messages from the homepage. | 13 | // FolderHandlers handles folder-related messages from the homepage. | ||
| 14 | type FolderHandlers struct { | 14 | type FolderHandlers struct { | ||
| 15 | - | favoritesUC *usecase.ManageFavoritesUseCase | 15 | + | favoritesUC port.HomepageFavorites |
| 16 | } | 16 | } | ||
| 17 | 17 | ||||
| 18 | // NewFolderHandlers creates a new FolderHandlers instance. | 18 | // NewFolderHandlers creates a new FolderHandlers instance. | ||
| 19 | - | func NewFolderHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *FolderHandlers { | 19 | + | func NewFolderHandlers(favoritesUC port.HomepageFavorites) *FolderHandlers { |
| 20 | return &FolderHandlers{favoritesUC: favoritesUC} | 20 | return &FolderHandlers{favoritesUC: favoritesUC} | ||
| 21 | } | 21 | } | ||
| 22 | 22 | ||||
M internal/infrastructure/webkit/handlers/homepage/history.go +4 -4
| @@ -5,7 +5,7 @@ import ( | |||
| 5 | 5 | "encoding/json" | |
| 6 | 6 | "time" | |
| 7 | 7 | ||
| 8 | - | "github.com/bnema/dumber/internal/application/usecase" | |
| 8 | + | "github.com/bnema/dumber/internal/application/port" | |
| 9 | 9 | "github.com/bnema/dumber/internal/domain/entity" | |
| 10 | 10 | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 11 | 11 | "github.com/bnema/dumber/internal/logging" | |
| @@ -13,11 +13,11 @@ import ( | |||
| 13 | 13 | ||
| 14 | 14 | // HistoryHandlers handles history-related messages from the homepage. | |
| 15 | 15 | type HistoryHandlers struct { | |
| 16 | - | historyUC *usecase.SearchHistoryUseCase | |
| 16 | + | historyUC port.HomepageHistory | |
| 17 | 17 | } | |
| 18 | 18 | ||
| 19 | 19 | // NewHistoryHandlers creates a new HistoryHandlers instance. | |
| 20 | - | func NewHistoryHandlers(historyUC *usecase.SearchHistoryUseCase) *HistoryHandlers { | |
| 20 | + | func NewHistoryHandlers(historyUC port.HomepageHistory) *HistoryHandlers { | |
| 21 | 21 | return &HistoryHandlers{historyUC: historyUC} | |
| 22 | 22 | } | |
| 23 | 23 | ||
| @@ -75,7 +75,7 @@ func (h *HistoryHandlers) HandleSearchFTS() webkit.MessageHandler { | |||
| 75 | 75 | Str("query", req.Query). | |
| 76 | 76 | Msg("handling history_search_fts") | |
| 77 | 77 | ||
| 78 | - | output, err := h.historyUC.Search(ctx, usecase.SearchInput{ | |
| 78 | + | output, err := h.historyUC.Search(ctx, port.HistorySearchInput{ | |
| 79 | 79 | Query: req.Query, | |
| 80 | 80 | Limit: req.Limit, | |
| 81 | 81 | }) | |
| @@ -5,7 +5,7 @@ import ( | |||||
| 5 | "encoding/json" | 5 | "encoding/json" | ||
| 6 | "time" | 6 | "time" | ||
| 7 | 7 | ||||
| 8 | - | "github.com/bnema/dumber/internal/application/usecase" | 8 | + | "github.com/bnema/dumber/internal/application/port" |
| 9 | "github.com/bnema/dumber/internal/domain/entity" | 9 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 10 | "github.com/bnema/dumber/internal/infrastructure/webkit" | 10 | "github.com/bnema/dumber/internal/infrastructure/webkit" | ||
| 11 | "github.com/bnema/dumber/internal/logging" | 11 | "github.com/bnema/dumber/internal/logging" | ||
| @@ -13,11 +13,11 @@ import ( | |||||
| 13 | 13 | ||||
| 14 | // HistoryHandlers handles history-related messages from the homepage. | 14 | // HistoryHandlers handles history-related messages from the homepage. | ||
| 15 | type HistoryHandlers struct { | 15 | type HistoryHandlers struct { | ||
| 16 | - | historyUC *usecase.SearchHistoryUseCase | 16 | + | historyUC port.HomepageHistory |
| 17 | } | 17 | } | ||
| 18 | 18 | ||||
| 19 | // NewHistoryHandlers creates a new HistoryHandlers instance. | 19 | // NewHistoryHandlers creates a new HistoryHandlers instance. | ||
| 20 | - | func NewHistoryHandlers(historyUC *usecase.SearchHistoryUseCase) *HistoryHandlers { | 20 | + | func NewHistoryHandlers(historyUC port.HomepageHistory) *HistoryHandlers { |
| 21 | return &HistoryHandlers{historyUC: historyUC} | 21 | return &HistoryHandlers{historyUC: historyUC} | ||
| 22 | } | 22 | } | ||
| 23 | 23 | ||||
| @@ -75,7 +75,7 @@ func (h *HistoryHandlers) HandleSearchFTS() webkit.MessageHandler { | |||||
| 75 | Str("query", req.Query). | 75 | Str("query", req.Query). | ||
| 76 | Msg("handling history_search_fts") | 76 | Msg("handling history_search_fts") | ||
| 77 | 77 | ||||
| 78 | - | output, err := h.historyUC.Search(ctx, usecase.SearchInput{ | 78 | + | output, err := h.historyUC.Search(ctx, port.HistorySearchInput{ |
| 79 | Query: req.Query, | 79 | Query: req.Query, | ||
| 80 | Limit: req.Limit, | 80 | Limit: req.Limit, | ||
| 81 | }) | 81 | }) | ||
M internal/infrastructure/webkit/handlers/homepage/registry.go +3 -3
| @@ -4,15 +4,15 @@ import ( | |||
| 4 | 4 | "context" | |
| 5 | 5 | "fmt" | |
| 6 | 6 | ||
| 7 | - | "github.com/bnema/dumber/internal/application/usecase" | |
| 7 | + | "github.com/bnema/dumber/internal/application/port" | |
| 8 | 8 | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 9 | 9 | "github.com/bnema/dumber/internal/logging" | |
| 10 | 10 | ) | |
| 11 | 11 | ||
| 12 | 12 | // Config holds dependencies for homepage handlers. | |
| 13 | 13 | type Config struct { | |
| 14 | - | HistoryUC *usecase.SearchHistoryUseCase | |
| 15 | - | FavoritesUC *usecase.ManageFavoritesUseCase | |
| 14 | + | HistoryUC port.HomepageHistory | |
| 15 | + | FavoritesUC port.HomepageFavorites | |
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | 18 | // RegisterHandlers registers all homepage message handlers with the router. | |
| @@ -4,15 +4,15 @@ import ( | |||||
| 4 | "context" | 4 | "context" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | 6 | ||||
| 7 | - | "github.com/bnema/dumber/internal/application/usecase" | 7 | + | "github.com/bnema/dumber/internal/application/port" |
| 8 | "github.com/bnema/dumber/internal/infrastructure/webkit" | 8 | "github.com/bnema/dumber/internal/infrastructure/webkit" | ||
| 9 | "github.com/bnema/dumber/internal/logging" | 9 | "github.com/bnema/dumber/internal/logging" | ||
| 10 | ) | 10 | ) | ||
| 11 | 11 | ||||
| 12 | // Config holds dependencies for homepage handlers. | 12 | // Config holds dependencies for homepage handlers. | ||
| 13 | type Config struct { | 13 | type Config struct { | ||
| 14 | - | HistoryUC *usecase.SearchHistoryUseCase | 14 | + | HistoryUC port.HomepageHistory |
| 15 | - | FavoritesUC *usecase.ManageFavoritesUseCase | 15 | + | FavoritesUC port.HomepageFavorites |
| 16 | } | 16 | } | ||
| 17 | 17 | ||||
| 18 | // RegisterHandlers registers all homepage message handlers with the router. | 18 | // RegisterHandlers registers all homepage message handlers with the router. | ||
M internal/infrastructure/webkit/handlers/homepage/tags.go +3 -3
| @@ -4,7 +4,7 @@ import ( | |||
| 4 | 4 | "context" | |
| 5 | 5 | "encoding/json" | |
| 6 | 6 | ||
| 7 | - | "github.com/bnema/dumber/internal/application/usecase" | |
| 7 | + | "github.com/bnema/dumber/internal/application/port" | |
| 8 | 8 | "github.com/bnema/dumber/internal/domain/entity" | |
| 9 | 9 | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 10 | 10 | "github.com/bnema/dumber/internal/logging" | |
| @@ -12,11 +12,11 @@ import ( | |||
| 12 | 12 | ||
| 13 | 13 | // TagHandlers handles tag-related messages from the homepage. | |
| 14 | 14 | type TagHandlers struct { | |
| 15 | - | favoritesUC *usecase.ManageFavoritesUseCase | |
| 15 | + | favoritesUC port.HomepageFavorites | |
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | 18 | // NewTagHandlers creates a new TagHandlers instance. | |
| 19 | - | func NewTagHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *TagHandlers { | |
| 19 | + | func NewTagHandlers(favoritesUC port.HomepageFavorites) *TagHandlers { | |
| 20 | 20 | return &TagHandlers{favoritesUC: favoritesUC} | |
| 21 | 21 | } | |
| 22 | 22 | ||
| @@ -4,7 +4,7 @@ import ( | |||||
| 4 | "context" | 4 | "context" | ||
| 5 | "encoding/json" | 5 | "encoding/json" | ||
| 6 | 6 | ||||
| 7 | - | "github.com/bnema/dumber/internal/application/usecase" | 7 | + | "github.com/bnema/dumber/internal/application/port" |
| 8 | "github.com/bnema/dumber/internal/domain/entity" | 8 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 9 | "github.com/bnema/dumber/internal/infrastructure/webkit" | 9 | "github.com/bnema/dumber/internal/infrastructure/webkit" | ||
| 10 | "github.com/bnema/dumber/internal/logging" | 10 | "github.com/bnema/dumber/internal/logging" | ||
| @@ -12,11 +12,11 @@ import ( | |||||
| 12 | 12 | ||||
| 13 | // TagHandlers handles tag-related messages from the homepage. | 13 | // TagHandlers handles tag-related messages from the homepage. | ||
| 14 | type TagHandlers struct { | 14 | type TagHandlers struct { | ||
| 15 | - | favoritesUC *usecase.ManageFavoritesUseCase | 15 | + | favoritesUC port.HomepageFavorites |
| 16 | } | 16 | } | ||
| 17 | 17 | ||||
| 18 | // NewTagHandlers creates a new TagHandlers instance. | 18 | // NewTagHandlers creates a new TagHandlers instance. | ||
| 19 | - | func NewTagHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *TagHandlers { | 19 | + | func NewTagHandlers(favoritesUC port.HomepageFavorites) *TagHandlers { |
| 20 | return &TagHandlers{favoritesUC: favoritesUC} | 20 | return &TagHandlers{favoritesUC: favoritesUC} | ||
| 21 | } | 21 | } | ||
| 22 | 22 | ||||
M internal/infrastructure/webkit/handlers/keybindings.go +8 -9
| @@ -6,25 +6,24 @@ import ( | |||
| 6 | 6 | "fmt" | |
| 7 | 7 | ||
| 8 | 8 | "github.com/bnema/dumber/internal/application/port" | |
| 9 | - | "github.com/bnema/dumber/internal/application/usecase" | |
| 10 | 9 | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 11 | 10 | "github.com/bnema/dumber/internal/logging" | |
| 12 | 11 | ) | |
| 13 | 12 | ||
| 14 | 13 | // KeybindingsHandler handles keybinding-related messages. | |
| 15 | 14 | type KeybindingsHandler struct { | |
| 16 | - | getUC *usecase.GetKeybindingsUseCase | |
| 17 | - | setUC *usecase.SetKeybindingUseCase | |
| 18 | - | resetUC *usecase.ResetKeybindingUseCase | |
| 19 | - | resetAllUC *usecase.ResetAllKeybindingsUseCase | |
| 15 | + | getUC port.KeybindingsGetter | |
| 16 | + | setUC port.KeybindingSetter | |
| 17 | + | resetUC port.KeybindingResetter | |
| 18 | + | resetAllUC port.AllKeybindingsResetter | |
| 20 | 19 | } | |
| 21 | 20 | ||
| 22 | 21 | // NewKeybindingsHandler creates a new KeybindingsHandler. | |
| 23 | 22 | func NewKeybindingsHandler( | |
| 24 | - | getUC *usecase.GetKeybindingsUseCase, | |
| 25 | - | setUC *usecase.SetKeybindingUseCase, | |
| 26 | - | resetUC *usecase.ResetKeybindingUseCase, | |
| 27 | - | resetAllUC *usecase.ResetAllKeybindingsUseCase, | |
| 23 | + | getUC port.KeybindingsGetter, | |
| 24 | + | setUC port.KeybindingSetter, | |
| 25 | + | resetUC port.KeybindingResetter, | |
| 26 | + | resetAllUC port.AllKeybindingsResetter, | |
| 28 | 27 | ) *KeybindingsHandler { | |
| 29 | 28 | return &KeybindingsHandler{ | |
| 30 | 29 | getUC: getUC, | |
| @@ -6,25 +6,24 @@ import ( | |||||
| 6 | "fmt" | 6 | "fmt" | ||
| 7 | 7 | ||||
| 8 | "github.com/bnema/dumber/internal/application/port" | 8 | "github.com/bnema/dumber/internal/application/port" | ||
| 9 | - | "github.com/bnema/dumber/internal/application/usecase" | |||
| 10 | "github.com/bnema/dumber/internal/infrastructure/webkit" | 9 | "github.com/bnema/dumber/internal/infrastructure/webkit" | ||
| 11 | "github.com/bnema/dumber/internal/logging" | 10 | "github.com/bnema/dumber/internal/logging" | ||
| 12 | ) | 11 | ) | ||
| 13 | 12 | ||||
| 14 | // KeybindingsHandler handles keybinding-related messages. | 13 | // KeybindingsHandler handles keybinding-related messages. | ||
| 15 | type KeybindingsHandler struct { | 14 | type KeybindingsHandler struct { | ||
| 16 | - | getUC *usecase.GetKeybindingsUseCase | 15 | + | getUC port.KeybindingsGetter |
| 17 | - | setUC *usecase.SetKeybindingUseCase | 16 | + | setUC port.KeybindingSetter |
| 18 | - | resetUC *usecase.ResetKeybindingUseCase | 17 | + | resetUC port.KeybindingResetter |
| 19 | - | resetAllUC *usecase.ResetAllKeybindingsUseCase | 18 | + | resetAllUC port.AllKeybindingsResetter |
| 20 | } | 19 | } | ||
| 21 | 20 | ||||
| 22 | // NewKeybindingsHandler creates a new KeybindingsHandler. | 21 | // NewKeybindingsHandler creates a new KeybindingsHandler. | ||
| 23 | func NewKeybindingsHandler( | 22 | func NewKeybindingsHandler( | ||
| 24 | - | getUC *usecase.GetKeybindingsUseCase, | 23 | + | getUC port.KeybindingsGetter, |
| 25 | - | setUC *usecase.SetKeybindingUseCase, | 24 | + | setUC port.KeybindingSetter, |
| 26 | - | resetUC *usecase.ResetKeybindingUseCase, | 25 | + | resetUC port.KeybindingResetter, |
| 27 | - | resetAllUC *usecase.ResetAllKeybindingsUseCase, | 26 | + | resetAllUC port.AllKeybindingsResetter, |
| 28 | ) *KeybindingsHandler { | 27 | ) *KeybindingsHandler { | ||
| 29 | return &KeybindingsHandler{ | 28 | return &KeybindingsHandler{ | ||
| 30 | getUC: getUC, | 29 | getUC: getUC, | ||
M internal/infrastructure/webkit/handlers/register.go +16 -41
| @@ -7,26 +7,24 @@ import ( | |||
| 7 | 7 | "unicode/utf8" | |
| 8 | 8 | ||
| 9 | 9 | "github.com/bnema/dumber/internal/application/port" | |
| 10 | - | "github.com/bnema/dumber/internal/application/usecase" | |
| 11 | - | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 12 | 10 | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 13 | 11 | "github.com/bnema/dumber/internal/infrastructure/webkit/handlers/homepage" | |
| 14 | 12 | ) | |
| 15 | 13 | ||
| 16 | 14 | // AccentKeyHandler is implemented by the InsertAccentUseCase to receive | |
| 17 | 15 | // key press/release events forwarded from WebView JS via the message bridge. | |
| 18 | - | type AccentKeyHandler interface { | |
| 19 | - | OnKeyPressed(ctx context.Context, char rune, shiftHeld bool) bool | |
| 20 | - | OnKeyReleased(ctx context.Context, char rune) | |
| 21 | - | } | |
| 16 | + | // Deprecated: use port.AccentKeyHandler instead. This alias is kept for compatibility. | |
| 17 | + | type AccentKeyHandler = port.AccentKeyHandler | |
| 22 | 18 | ||
| 23 | 19 | // Config holds all dependencies for message handlers. | |
| 24 | 20 | type Config struct { | |
| 25 | - | HistoryUC *usecase.SearchHistoryUseCase | |
| 26 | - | FavoritesUC *usecase.ManageFavoritesUseCase | |
| 27 | - | Clipboard port.Clipboard | |
| 28 | - | ConfigGetter func() *config.Config | |
| 29 | - | OnClipboardCopied func(textLen int) // Called when auto-copy completes (for toast notification) | |
| 21 | + | HistoryUC port.HomepageHistory | |
| 22 | + | FavoritesUC port.HomepageFavorites | |
| 23 | + | Clipboard port.Clipboard | |
| 24 | + | AutoCopyConfig port.AutoCopyConfig | |
| 25 | + | SaveConfig func(context.Context, port.WebUIConfig) error // Pre-built by bootstrap (usecase.SaveWebUIConfigUseCase.Execute) | |
| 26 | + | KeybindingsHandler *KeybindingsHandler // Pre-built by bootstrap | |
| 27 | + | OnClipboardCopied func(textLen int) // Called when auto-copy completes (for toast notification) | |
| 30 | 28 | } | |
| 31 | 29 | ||
| 32 | 30 | // RegisterAll registers all message handlers with the router. | |
| @@ -41,32 +39,26 @@ func RegisterAll(ctx context.Context, router *webkit.MessageRouter, cfg Config) | |||
| 41 | 39 | } | |
| 42 | 40 | } | |
| 43 | 41 | ||
| 44 | - | // Configuration handlers (always available) | |
| 45 | - | if err := RegisterConfigHandlers(ctx, router); err != nil { | |
| 42 | + | // Configuration handlers | |
| 43 | + | if err := RegisterConfigHandlers(ctx, router, cfg.SaveConfig); err != nil { | |
| 46 | 44 | return err | |
| 47 | 45 | } | |
| 48 | 46 | ||
| 49 | 47 | // Keybindings handlers (always available) | |
| 50 | - | keybindingsHandler, err := createKeybindingsHandler() | |
| 51 | - | if err != nil { | |
| 52 | - | return fmt.Errorf("failed to create keybindings handler: %w", err) | |
| 48 | + | if cfg.KeybindingsHandler == nil { | |
| 49 | + | return fmt.Errorf("KeybindingsHandler is required") | |
| 53 | 50 | } | |
| 54 | - | if err := RegisterKeybindingsHandlers(ctx, router, keybindingsHandler); err != nil { | |
| 51 | + | if err := RegisterKeybindingsHandlers(ctx, router, cfg.KeybindingsHandler); err != nil { | |
| 55 | 52 | return err | |
| 56 | 53 | } | |
| 57 | 54 | ||
| 58 | 55 | // Clipboard handlers (for auto-copy on selection feature) | |
| 59 | - | if cfg.Clipboard != nil && cfg.ConfigGetter != nil { | |
| 60 | - | if err := RegisterClipboardHandlers(ctx, router, cfg.Clipboard, cfg.ConfigGetter, cfg.OnClipboardCopied); err != nil { | |
| 56 | + | if cfg.Clipboard != nil && cfg.AutoCopyConfig != nil { | |
| 57 | + | if err := RegisterClipboardHandlers(ctx, router, cfg.Clipboard, cfg.AutoCopyConfig, cfg.OnClipboardCopied); err != nil { | |
| 61 | 58 | return err | |
| 62 | 59 | } | |
| 63 | 60 | } | |
| 64 | 61 | ||
| 65 | - | // Future handler groups go here: | |
| 66 | - | // - settings handlers | |
| 67 | - | // - downloads handlers | |
| 68 | - | // - etc. | |
| 69 | - | ||
| 70 | 62 | return nil | |
| 71 | 63 | } | |
| 72 | 64 | ||
| @@ -117,20 +109,3 @@ func RegisterAccentHandlers(_ context.Context, router *webkit.MessageRouter, han | |||
| 117 | 109 | ||
| 118 | 110 | return nil | |
| 119 | 111 | } | |
| 120 | - | ||
| 121 | - | // createKeybindingsHandler wires the gateway and use cases for keybindings. | |
| 122 | - | func createKeybindingsHandler() (*KeybindingsHandler, error) { | |
| 123 | - | mgr := config.GetManager() | |
| 124 | - | if mgr == nil { | |
| 125 | - | return nil, fmt.Errorf("config manager not initialized") | |
| 126 | - | } | |
| 127 | - | ||
| 128 | - | gateway := config.NewKeybindingsGateway(mgr) | |
| 129 | - | ||
| 130 | - | return NewKeybindingsHandler( | |
| 131 | - | usecase.NewGetKeybindingsUseCase(gateway), | |
| 132 | - | usecase.NewSetKeybindingUseCase(gateway, gateway), | |
| 133 | - | usecase.NewResetKeybindingUseCase(gateway), | |
| 134 | - | usecase.NewResetAllKeybindingsUseCase(gateway), | |
| 135 | - | ), nil | |
| 136 | - | } | |
| @@ -7,26 +7,24 @@ import ( | |||||
| 7 | "unicode/utf8" | 7 | "unicode/utf8" | ||
| 8 | 8 | ||||
| 9 | "github.com/bnema/dumber/internal/application/port" | 9 | "github.com/bnema/dumber/internal/application/port" | ||
| 10 | - | "github.com/bnema/dumber/internal/application/usecase" | |||
| 11 | - | "github.com/bnema/dumber/internal/infrastructure/config" | |||
| 12 | "github.com/bnema/dumber/internal/infrastructure/webkit" | 10 | "github.com/bnema/dumber/internal/infrastructure/webkit" | ||
| 13 | "github.com/bnema/dumber/internal/infrastructure/webkit/handlers/homepage" | 11 | "github.com/bnema/dumber/internal/infrastructure/webkit/handlers/homepage" | ||
| 14 | ) | 12 | ) | ||
| 15 | 13 | ||||
| 16 | // AccentKeyHandler is implemented by the InsertAccentUseCase to receive | 14 | // AccentKeyHandler is implemented by the InsertAccentUseCase to receive | ||
| 17 | // key press/release events forwarded from WebView JS via the message bridge. | 15 | // key press/release events forwarded from WebView JS via the message bridge. | ||
| 18 | - | type AccentKeyHandler interface { | 16 | + | // Deprecated: use port.AccentKeyHandler instead. This alias is kept for compatibility. |
| 19 | - | OnKeyPressed(ctx context.Context, char rune, shiftHeld bool) bool | 17 | + | type AccentKeyHandler = port.AccentKeyHandler |
| 20 | - | OnKeyReleased(ctx context.Context, char rune) | |||
| 21 | - | } | |||
| 22 | 18 | ||||
| 23 | // Config holds all dependencies for message handlers. | 19 | // Config holds all dependencies for message handlers. | ||
| 24 | type Config struct { | 20 | type Config struct { | ||
| 25 | - | HistoryUC *usecase.SearchHistoryUseCase | 21 | + | HistoryUC port.HomepageHistory |
| 26 | - | FavoritesUC *usecase.ManageFavoritesUseCase | 22 | + | FavoritesUC port.HomepageFavorites |
| 27 | - | Clipboard port.Clipboard | 23 | + | Clipboard port.Clipboard |
| 28 | - | ConfigGetter func() *config.Config | 24 | + | AutoCopyConfig port.AutoCopyConfig |
| 29 | - | OnClipboardCopied func(textLen int) // Called when auto-copy completes (for toast notification) | 25 | + | SaveConfig func(context.Context, port.WebUIConfig) error // Pre-built by bootstrap (usecase.SaveWebUIConfigUseCase.Execute) |
| 26 | + | KeybindingsHandler *KeybindingsHandler // Pre-built by bootstrap | |||
| 27 | + | OnClipboardCopied func(textLen int) // Called when auto-copy completes (for toast notification) | |||
| 30 | } | 28 | } | ||
| 31 | 29 | ||||
| 32 | // RegisterAll registers all message handlers with the router. | 30 | // RegisterAll registers all message handlers with the router. | ||
| @@ -41,32 +39,26 @@ func RegisterAll(ctx context.Context, router *webkit.MessageRouter, cfg Config) | |||||
| 41 | } | 39 | } | ||
| 42 | } | 40 | } | ||
| 43 | 41 | ||||
| 44 | - | // Configuration handlers (always available) | 42 | + | // Configuration handlers |
| 45 | - | if err := RegisterConfigHandlers(ctx, router); err != nil { | 43 | + | if err := RegisterConfigHandlers(ctx, router, cfg.SaveConfig); err != nil { |
| 46 | return err | 44 | return err | ||
| 47 | } | 45 | } | ||
| 48 | 46 | ||||
| 49 | // Keybindings handlers (always available) | 47 | // Keybindings handlers (always available) | ||
| 50 | - | keybindingsHandler, err := createKeybindingsHandler() | 48 | + | if cfg.KeybindingsHandler == nil { |
| 51 | - | if err != nil { | 49 | + | return fmt.Errorf("KeybindingsHandler is required") |
| 52 | - | return fmt.Errorf("failed to create keybindings handler: %w", err) | |||
| 53 | } | 50 | } | ||
| 54 | - | if err := RegisterKeybindingsHandlers(ctx, router, keybindingsHandler); err != nil { | 51 | + | if err := RegisterKeybindingsHandlers(ctx, router, cfg.KeybindingsHandler); err != nil { |
| 55 | return err | 52 | return err | ||
| 56 | } | 53 | } | ||
| 57 | 54 | ||||
| 58 | // Clipboard handlers (for auto-copy on selection feature) | 55 | // Clipboard handlers (for auto-copy on selection feature) | ||
| 59 | - | if cfg.Clipboard != nil && cfg.ConfigGetter != nil { | 56 | + | if cfg.Clipboard != nil && cfg.AutoCopyConfig != nil { |
| 60 | - | if err := RegisterClipboardHandlers(ctx, router, cfg.Clipboard, cfg.ConfigGetter, cfg.OnClipboardCopied); err != nil { | 57 | + | if err := RegisterClipboardHandlers(ctx, router, cfg.Clipboard, cfg.AutoCopyConfig, cfg.OnClipboardCopied); err != nil { |
| 61 | return err | 58 | return err | ||
| 62 | } | 59 | } | ||
| 63 | } | 60 | } | ||
| 64 | 61 | ||||
| 65 | - | // Future handler groups go here: | |||
| 66 | - | // - settings handlers | |||
| 67 | - | // - downloads handlers | |||
| 68 | - | // - etc. | |||
| 69 | - | ||||
| 70 | return nil | 62 | return nil | ||
| 71 | } | 63 | } | ||
| 72 | 64 | ||||
| @@ -117,20 +109,3 @@ func RegisterAccentHandlers(_ context.Context, router *webkit.MessageRouter, han | |||||
| 117 | 109 | ||||
| 118 | return nil | 110 | return nil | ||
| 119 | } | 111 | } | ||
| 120 | - | ||||
| 121 | - | // createKeybindingsHandler wires the gateway and use cases for keybindings. | |||
| 122 | - | func createKeybindingsHandler() (*KeybindingsHandler, error) { | |||
| 123 | - | mgr := config.GetManager() | |||
| 124 | - | if mgr == nil { | |||
| 125 | - | return nil, fmt.Errorf("config manager not initialized") | |||
| 126 | - | } | |||
| 127 | - | ||||
| 128 | - | gateway := config.NewKeybindingsGateway(mgr) | |||
| 129 | - | ||||
| 130 | - | return NewKeybindingsHandler( | |||
| 131 | - | usecase.NewGetKeybindingsUseCase(gateway), | |||
| 132 | - | usecase.NewSetKeybindingUseCase(gateway, gateway), | |||
| 133 | - | usecase.NewResetKeybindingUseCase(gateway), | |||
| 134 | - | usecase.NewResetAllKeybindingsUseCase(gateway), | |||
| 135 | - | ), nil | |||
| 136 | - | } | |||
M internal/ui/adapter/favicon.go +3 -3
| @@ -17,7 +17,7 @@ import ( | |||
| 17 | 17 | // FaviconAdapter bridges the domain FaviconService to GTK by providing | |
| 18 | 18 | // gdk.Texture conversion and WebKit FaviconDatabase integration. | |
| 19 | 19 | type FaviconAdapter struct { | |
| 20 | - | service *favicon.Service | |
| 20 | + | service port.FaviconService | |
| 21 | 21 | faviconDB port.FaviconDatabase | |
| 22 | 22 | textureCache map[string]*gdk.Texture | |
| 23 | 23 | mu sync.RWMutex | |
| @@ -29,7 +29,7 @@ type warningLogFunc func(log *zerolog.Logger, err error) | |||
| 29 | 29 | ||
| 30 | 30 | // NewFaviconAdapter creates a new FaviconAdapter. | |
| 31 | 31 | // The faviconDB can be nil if a favicon database is not available. | |
| 32 | - | func NewFaviconAdapter(service *favicon.Service, faviconDB port.FaviconDatabase) *FaviconAdapter { | |
| 32 | + | func NewFaviconAdapter(service port.FaviconService, faviconDB port.FaviconDatabase) *FaviconAdapter { | |
| 33 | 33 | return &FaviconAdapter{ | |
| 34 | 34 | service: service, | |
| 35 | 35 | faviconDB: faviconDB, | |
| @@ -272,7 +272,7 @@ func (a *FaviconAdapter) PreloadFromCache(pageURL string) *gdk.Texture { | |||
| 272 | 272 | ||
| 273 | 273 | // Service returns the underlying favicon service. | |
| 274 | 274 | // Used by CLI components that need disk paths. | |
| 275 | - | func (a *FaviconAdapter) Service() *favicon.Service { | |
| 275 | + | func (a *FaviconAdapter) Service() port.FaviconService { | |
| 276 | 276 | return a.service | |
| 277 | 277 | } | |
| 278 | 278 | ||
| @@ -17,7 +17,7 @@ import ( | |||||
| 17 | // FaviconAdapter bridges the domain FaviconService to GTK by providing | 17 | // FaviconAdapter bridges the domain FaviconService to GTK by providing | ||
| 18 | // gdk.Texture conversion and WebKit FaviconDatabase integration. | 18 | // gdk.Texture conversion and WebKit FaviconDatabase integration. | ||
| 19 | type FaviconAdapter struct { | 19 | type FaviconAdapter struct { | ||
| 20 | - | service *favicon.Service | 20 | + | service port.FaviconService |
| 21 | faviconDB port.FaviconDatabase | 21 | faviconDB port.FaviconDatabase | ||
| 22 | textureCache map[string]*gdk.Texture | 22 | textureCache map[string]*gdk.Texture | ||
| 23 | mu sync.RWMutex | 23 | mu sync.RWMutex | ||
| @@ -29,7 +29,7 @@ type warningLogFunc func(log *zerolog.Logger, err error) | |||||
| 29 | 29 | ||||
| 30 | // NewFaviconAdapter creates a new FaviconAdapter. | 30 | // NewFaviconAdapter creates a new FaviconAdapter. | ||
| 31 | // The faviconDB can be nil if a favicon database is not available. | 31 | // The faviconDB can be nil if a favicon database is not available. | ||
| 32 | - | func NewFaviconAdapter(service *favicon.Service, faviconDB port.FaviconDatabase) *FaviconAdapter { | 32 | + | func NewFaviconAdapter(service port.FaviconService, faviconDB port.FaviconDatabase) *FaviconAdapter { |
| 33 | return &FaviconAdapter{ | 33 | return &FaviconAdapter{ | ||
| 34 | service: service, | 34 | service: service, | ||
| 35 | faviconDB: faviconDB, | 35 | faviconDB: faviconDB, | ||
| @@ -272,7 +272,7 @@ func (a *FaviconAdapter) PreloadFromCache(pageURL string) *gdk.Texture { | |||||
| 272 | 272 | ||||
| 273 | // Service returns the underlying favicon service. | 273 | // Service returns the underlying favicon service. | ||
| 274 | // Used by CLI components that need disk paths. | 274 | // Used by CLI components that need disk paths. | ||
| 275 | - | func (a *FaviconAdapter) Service() *favicon.Service { | 275 | + | func (a *FaviconAdapter) Service() port.FaviconService { |
| 276 | return a.service | 276 | return a.service | ||
| 277 | } | 277 | } | ||
| 278 | 278 | ||||
M internal/ui/app.go +5 -6
| @@ -17,7 +17,6 @@ import ( | |||
| 17 | 17 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 18 | 18 | "github.com/bnema/dumber/internal/infrastructure/desktop" | |
| 19 | 19 | "github.com/bnema/dumber/internal/infrastructure/filesystem" | |
| 20 | - | "github.com/bnema/dumber/internal/infrastructure/filtering" | |
| 21 | 20 | "github.com/bnema/dumber/internal/infrastructure/snapshot" | |
| 22 | 21 | "github.com/bnema/dumber/internal/infrastructure/textinput" | |
| 23 | 22 | ||
| @@ -3116,7 +3115,7 @@ func (a *App) initFilteringAsync(ctx context.Context) { | |||
| 3116 | 3115 | return | |
| 3117 | 3116 | } | |
| 3118 | 3117 | ||
| 3119 | - | a.deps.FilterManager.SetStatusCallback(func(status filtering.FilterStatus) { | |
| 3118 | + | a.deps.FilterManager.SetStatusCallback(func(status port.FilterStatus) { | |
| 3120 | 3119 | statusCopy := status // Capture for closure | |
| 3121 | 3120 | cb := glib.SourceFunc(func(_ uintptr) bool { | |
| 3122 | 3121 | a.showFilterStatus(ctx, statusCopy) | |
| @@ -3128,15 +3127,15 @@ func (a *App) initFilteringAsync(ctx context.Context) { | |||
| 3128 | 3127 | } | |
| 3129 | 3128 | ||
| 3130 | 3129 | // showFilterStatus displays toast notification for filter status. | |
| 3131 | - | func (a *App) showFilterStatus(ctx context.Context, status filtering.FilterStatus) { | |
| 3130 | + | func (a *App) showFilterStatus(ctx context.Context, status port.FilterStatus) { | |
| 3132 | 3131 | log := logging.FromContext(ctx) | |
| 3133 | 3132 | ||
| 3134 | 3133 | switch status.State { | |
| 3135 | - | case filtering.StateLoading: | |
| 3134 | + | case port.FilterStateLoading: | |
| 3136 | 3135 | if a.appToaster != nil { | |
| 3137 | 3136 | a.appToaster.Show(ctx, status.Message, component.ToastInfo) | |
| 3138 | 3137 | } | |
| 3139 | - | case filtering.StateActive: | |
| 3138 | + | case port.FilterStateActive: | |
| 3140 | 3139 | // Apply filters to existing webviews that were created before filters loaded | |
| 3141 | 3140 | if a.contentCoord != nil && a.deps.FilterManager != nil { | |
| 3142 | 3141 | a.contentCoord.ApplyFiltersToAll(ctx) | |
| @@ -3145,7 +3144,7 @@ func (a *App) showFilterStatus(ctx context.Context, status filtering.FilterStatu | |||
| 3145 | 3144 | if a.appToaster != nil { | |
| 3146 | 3145 | a.appToaster.Show(ctx, fmt.Sprintf("Ad blocker ready (%s)", status.Version), component.ToastInfo) | |
| 3147 | 3146 | } | |
| 3148 | - | case filtering.StateError: | |
| 3147 | + | case port.FilterStateError: | |
| 3149 | 3148 | if a.appToaster != nil { | |
| 3150 | 3149 | a.appToaster.Show(ctx, "Filter load failed: "+status.Message, component.ToastError) | |
| 3151 | 3150 | } | |
| @@ -17,7 +17,6 @@ import ( | |||||
| 17 | "github.com/bnema/dumber/internal/infrastructure/config" | 17 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| 18 | "github.com/bnema/dumber/internal/infrastructure/desktop" | 18 | "github.com/bnema/dumber/internal/infrastructure/desktop" | ||
| 19 | "github.com/bnema/dumber/internal/infrastructure/filesystem" | 19 | "github.com/bnema/dumber/internal/infrastructure/filesystem" | ||
| 20 | - | "github.com/bnema/dumber/internal/infrastructure/filtering" | |||
| 21 | "github.com/bnema/dumber/internal/infrastructure/snapshot" | 20 | "github.com/bnema/dumber/internal/infrastructure/snapshot" | ||
| 22 | "github.com/bnema/dumber/internal/infrastructure/textinput" | 21 | "github.com/bnema/dumber/internal/infrastructure/textinput" | ||
| 23 | 22 | ||||
| @@ -3116,7 +3115,7 @@ func (a *App) initFilteringAsync(ctx context.Context) { | |||||
| 3116 | return | 3115 | return | ||
| 3117 | } | 3116 | } | ||
| 3118 | 3117 | ||||
| 3119 | - | a.deps.FilterManager.SetStatusCallback(func(status filtering.FilterStatus) { | 3118 | + | a.deps.FilterManager.SetStatusCallback(func(status port.FilterStatus) { |
| 3120 | statusCopy := status // Capture for closure | 3119 | statusCopy := status // Capture for closure | ||
| 3121 | cb := glib.SourceFunc(func(_ uintptr) bool { | 3120 | cb := glib.SourceFunc(func(_ uintptr) bool { | ||
| 3122 | a.showFilterStatus(ctx, statusCopy) | 3121 | a.showFilterStatus(ctx, statusCopy) | ||
| @@ -3128,15 +3127,15 @@ func (a *App) initFilteringAsync(ctx context.Context) { | |||||
| 3128 | } | 3127 | } | ||
| 3129 | 3128 | ||||
| 3130 | // showFilterStatus displays toast notification for filter status. | 3129 | // showFilterStatus displays toast notification for filter status. | ||
| 3131 | - | func (a *App) showFilterStatus(ctx context.Context, status filtering.FilterStatus) { | 3130 | + | func (a *App) showFilterStatus(ctx context.Context, status port.FilterStatus) { |
| 3132 | log := logging.FromContext(ctx) | 3131 | log := logging.FromContext(ctx) | ||
| 3133 | 3132 | ||||
| 3134 | switch status.State { | 3133 | switch status.State { | ||
| 3135 | - | case filtering.StateLoading: | 3134 | + | case port.FilterStateLoading: |
| 3136 | if a.appToaster != nil { | 3135 | if a.appToaster != nil { | ||
| 3137 | a.appToaster.Show(ctx, status.Message, component.ToastInfo) | 3136 | a.appToaster.Show(ctx, status.Message, component.ToastInfo) | ||
| 3138 | } | 3137 | } | ||
| 3139 | - | case filtering.StateActive: | 3138 | + | case port.FilterStateActive: |
| 3140 | // Apply filters to existing webviews that were created before filters loaded | 3139 | // Apply filters to existing webviews that were created before filters loaded | ||
| 3141 | if a.contentCoord != nil && a.deps.FilterManager != nil { | 3140 | if a.contentCoord != nil && a.deps.FilterManager != nil { | ||
| 3142 | a.contentCoord.ApplyFiltersToAll(ctx) | 3141 | a.contentCoord.ApplyFiltersToAll(ctx) | ||
| @@ -3145,7 +3144,7 @@ func (a *App) showFilterStatus(ctx context.Context, status filtering.FilterStatu | |||||
| 3145 | if a.appToaster != nil { | 3144 | if a.appToaster != nil { | ||
| 3146 | a.appToaster.Show(ctx, fmt.Sprintf("Ad blocker ready (%s)", status.Version), component.ToastInfo) | 3145 | a.appToaster.Show(ctx, fmt.Sprintf("Ad blocker ready (%s)", status.Version), component.ToastInfo) | ||
| 3147 | } | 3146 | } | ||
| 3148 | - | case filtering.StateError: | 3147 | + | case port.FilterStateError: |
| 3149 | if a.appToaster != nil { | 3148 | if a.appToaster != nil { | ||
| 3150 | a.appToaster.Show(ctx, "Filter load failed: "+status.Message, component.ToastError) | 3149 | a.appToaster.Show(ctx, "Filter load failed: "+status.Message, component.ToastError) | ||
| 3151 | } | 3150 | } | ||
M internal/ui/deps.go +6 -9
| @@ -8,10 +8,7 @@ import ( | |||
| 8 | 8 | "github.com/bnema/dumber/internal/application/usecase" | |
| 9 | 9 | "github.com/bnema/dumber/internal/domain/entity" | |
| 10 | 10 | "github.com/bnema/dumber/internal/domain/repository" | |
| 11 | - | "github.com/bnema/dumber/internal/infrastructure/colorscheme" | |
| 12 | 11 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 13 | - | "github.com/bnema/dumber/internal/infrastructure/favicon" | |
| 14 | - | "github.com/bnema/dumber/internal/infrastructure/filtering" | |
| 15 | 12 | "github.com/bnema/dumber/internal/ui/theme" | |
| 16 | 13 | ) | |
| 17 | 14 | ||
| @@ -20,9 +17,9 @@ import ( | |||
| 20 | 17 | type Dependencies struct { | |
| 21 | 18 | // Core context and configuration | |
| 22 | 19 | Ctx context.Context | |
| 23 | - | Config *config.Config | |
| 24 | - | InitialURL string // URL to open on startup (optional) | |
| 25 | - | RestoreSessionID string // Session ID to restore on startup (optional) | |
| 20 | + | Config *config.Config // TODO: replace with port interface in a later milestone | |
| 21 | + | InitialURL string // URL to open on startup (optional) | |
| 22 | + | RestoreSessionID string // Session ID to restore on startup (optional) | |
| 26 | 23 | StartupCrashReports []string | |
| 27 | 24 | OnFirstWebViewShown func(context.Context) | |
| 28 | 25 | OnSessionPersisted func() // Called by main after session is persisted to DB | |
| @@ -31,7 +28,7 @@ type Dependencies struct { | |||
| 31 | 28 | // Theme and color scheme management | |
| 32 | 29 | Theme *theme.Manager | |
| 33 | 30 | ColorResolver port.ColorSchemeResolver | |
| 34 | - | AdwaitaDetector *colorscheme.AdwaitaDetector | |
| 31 | + | AdwaitaDetector port.ToolkitAvailabilityNotifier | |
| 35 | 32 | ||
| 36 | 33 | // XDG paths | |
| 37 | 34 | XDG port.XDGPaths | |
| @@ -58,8 +55,8 @@ type Dependencies struct { | |||
| 58 | 55 | ||
| 59 | 56 | // Infrastructure Adapters | |
| 60 | 57 | Clipboard port.Clipboard | |
| 61 | - | FaviconService *favicon.Service | |
| 62 | - | FilterManager *filtering.Manager | |
| 58 | + | FaviconService port.FaviconService | |
| 59 | + | FilterManager port.FilterManager | |
| 63 | 60 | IdleInhibitor port.IdleInhibitor | |
| 64 | 61 | ||
| 65 | 62 | // Accent picker for dead keys support | |
| @@ -8,10 +8,7 @@ import ( | |||||
| 8 | "github.com/bnema/dumber/internal/application/usecase" | 8 | "github.com/bnema/dumber/internal/application/usecase" | ||
| 9 | "github.com/bnema/dumber/internal/domain/entity" | 9 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 10 | "github.com/bnema/dumber/internal/domain/repository" | 10 | "github.com/bnema/dumber/internal/domain/repository" | ||
| 11 | - | "github.com/bnema/dumber/internal/infrastructure/colorscheme" | |||
| 12 | "github.com/bnema/dumber/internal/infrastructure/config" | 11 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| 13 | - | "github.com/bnema/dumber/internal/infrastructure/favicon" | |||
| 14 | - | "github.com/bnema/dumber/internal/infrastructure/filtering" | |||
| 15 | "github.com/bnema/dumber/internal/ui/theme" | 12 | "github.com/bnema/dumber/internal/ui/theme" | ||
| 16 | ) | 13 | ) | ||
| 17 | 14 | ||||
| @@ -20,9 +17,9 @@ import ( | |||||
| 20 | type Dependencies struct { | 17 | type Dependencies struct { | ||
| 21 | // Core context and configuration | 18 | // Core context and configuration | ||
| 22 | Ctx context.Context | 19 | Ctx context.Context | ||
| 23 | - | Config *config.Config | 20 | + | Config *config.Config // TODO: replace with port interface in a later milestone |
| 24 | - | InitialURL string // URL to open on startup (optional) | 21 | + | InitialURL string // URL to open on startup (optional) |
| 25 | - | RestoreSessionID string // Session ID to restore on startup (optional) | 22 | + | RestoreSessionID string // Session ID to restore on startup (optional) |
| 26 | StartupCrashReports []string | 23 | StartupCrashReports []string | ||
| 27 | OnFirstWebViewShown func(context.Context) | 24 | OnFirstWebViewShown func(context.Context) | ||
| 28 | OnSessionPersisted func() // Called by main after session is persisted to DB | 25 | OnSessionPersisted func() // Called by main after session is persisted to DB | ||
| @@ -31,7 +28,7 @@ type Dependencies struct { | |||||
| 31 | // Theme and color scheme management | 28 | // Theme and color scheme management | ||
| 32 | Theme *theme.Manager | 29 | Theme *theme.Manager | ||
| 33 | ColorResolver port.ColorSchemeResolver | 30 | ColorResolver port.ColorSchemeResolver | ||
| 34 | - | AdwaitaDetector *colorscheme.AdwaitaDetector | 31 | + | AdwaitaDetector port.ToolkitAvailabilityNotifier |
| 35 | 32 | ||||
| 36 | // XDG paths | 33 | // XDG paths | ||
| 37 | XDG port.XDGPaths | 34 | XDG port.XDGPaths | ||
| @@ -58,8 +55,8 @@ type Dependencies struct { | |||||
| 58 | 55 | ||||
| 59 | // Infrastructure Adapters | 56 | // Infrastructure Adapters | ||
| 60 | Clipboard port.Clipboard | 57 | Clipboard port.Clipboard | ||
| 61 | - | FaviconService *favicon.Service | 58 | + | FaviconService port.FaviconService |
| 62 | - | FilterManager *filtering.Manager | 59 | + | FilterManager port.FilterManager |
| 63 | IdleInhibitor port.IdleInhibitor | 60 | IdleInhibitor port.IdleInhibitor | ||
| 64 | 61 | ||||
| 65 | // Accent picker for dead keys support | 62 | // Accent picker for dead keys support | ||