From 518215f62e8533b2dd814f3d62d1afb7a3fa84f0 Mon Sep 17 00:00:00 2001 From: ashisgreat22 Date: Mon, 23 Mar 2026 22:49:41 +0100 Subject: [PATCH] feat(ui): dark theme redesign, fix image search and defaults - Inline CSS in base.html (Inter, dark mode, sticky search, tabs, results) - Remove HTMX/JS from templates; pagination via GET links - Atmospheric side gradients + grid; wider column on large viewports - Parse ?category= for HTML tabs (fixes Images category routing) - Include bing_images, ddg_images, qwant_images in local_ported defaults - Default listen port 5355; update Docker, compose, flake, README - Favicon img uses /favicon/ proxy; preferences without inline JS Made-with: Cursor --- Dockerfile | 2 +- README.md | 4 +- config.example.toml | 5 +- docker-compose.yml | 2 +- flake.nix | 2 +- internal/config/config.go | 4 +- internal/config/config_test.go | 8 +- internal/search/request_params.go | 23 +- internal/search/request_params_test.go | 18 + internal/views/templates/base.html | 1011 ++++++++++++++++++- internal/views/templates/image_item.html | 4 +- internal/views/templates/preferences.html | 26 +- internal/views/templates/result_item.html | 4 +- internal/views/templates/results.html | 51 +- internal/views/templates/results_inner.html | 38 +- internal/views/templates/video_item.html | 11 +- 16 files changed, 1107 insertions(+), 106 deletions(-) diff --git a/Dockerfile b/Dockerfile index e21960f..9f3443f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN apk add --no-cache ca-certificates tzdata COPY --from=builder /kafka /usr/local/bin/kafka COPY config.example.toml /etc/kafka/config.example.toml -EXPOSE 8080 +EXPOSE 5355 ENTRYPOINT ["kafka"] CMD ["-config", "/etc/kafka/config.toml"] diff --git a/README.md b/README.md index e4c146d..7427922 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ go run ./cmd/samsa -config config.toml ### Example ```bash -curl "http://localhost:8080/search?q=golang&format=json&engines=github,duckduckgo" +curl "http://localhost:5355/search?q=golang&format=json&engines=github,duckduckgo" ``` ### Response (JSON) @@ -155,7 +155,7 @@ Copy `config.example.toml` to `config.toml` and edit. All settings can also be o | Variable | Description | |---|---| -| `PORT` | Listen port (default: 8080) | +| `PORT` | Listen port (default: 5355) | | `BASE_URL` | Public URL for OpenSearch XML | | `UPSTREAM_SEARXNG_URL` | Upstream instance URL | | `LOCAL_PORTED_ENGINES` | Comma-separated local engine list | diff --git a/config.example.toml b/config.example.toml index 10af1b2..4fcd931 100644 --- a/config.example.toml +++ b/config.example.toml @@ -4,7 +4,7 @@ [server] # Listen port (env: PORT) -port = 8080 +port = 5355 # HTTP timeout for engine and upstream calls (env: HTTP_TIMEOUT) http_timeout = "10s" @@ -27,7 +27,8 @@ url = "" [engines] # Comma-separated list of engines to execute locally in Go (env: LOCAL_PORTED_ENGINES) # Engines not listed here will be proxied to the upstream instance. -local_ported = ["wikipedia", "arxiv", "crossref", "braveapi", "qwant", "duckduckgo", "github", "reddit", "bing", "google", "youtube"] +# Include bing_images, ddg_images, qwant_images for image search when [upstream].url is empty. +local_ported = ["wikipedia", "arxiv", "crossref", "braveapi", "qwant", "duckduckgo", "github", "reddit", "bing", "google", "youtube", "bing_images", "ddg_images", "qwant_images"] [engines.brave] # Brave Search API key (env: BRAVE_API_KEY) diff --git a/docker-compose.yml b/docker-compose.yml index 98eae96..538713f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: kafka: build: . ports: - - "8080:8080" + - "5355:5355" volumes: - ./config.toml:/etc/kafka/config.toml:ro depends_on: diff --git a/flake.nix b/flake.nix index 3552728..9ff4c83 100644 --- a/flake.nix +++ b/flake.nix @@ -61,7 +61,7 @@ port = lib.mkOption { type = lib.types.port; - default = 8080; + default = 5355; description = "Port to listen on."; }; diff --git a/internal/config/config.go b/internal/config/config.go index fe9cc5b..2318b6c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -160,12 +160,12 @@ func validateConfig(cfg *Config) error { func defaultConfig() *Config { return &Config{ Server: ServerConfig{ - Port: 8080, + Port: 5355, HTTPTimeout: "10s", }, Upstream: UpstreamConfig{}, Engines: EnginesConfig{ - LocalPorted: []string{"wikipedia", "arxiv", "crossref", "braveapi", "qwant", "duckduckgo", "github", "reddit", "bing", "google", "youtube"}, + LocalPorted: []string{"wikipedia", "arxiv", "crossref", "braveapi", "qwant", "duckduckgo", "github", "reddit", "bing", "google", "youtube", "bing_images", "ddg_images", "qwant_images"}, Qwant: QwantConfig{ Category: "web-lite", ResultsPerPage: 10, diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 07bddef..1bf1b47 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -11,11 +11,11 @@ func TestLoadDefaults(t *testing.T) { if err != nil { t.Fatalf("Load with missing file should return defaults: %v", err) } - if cfg.Server.Port != 8080 { - t.Errorf("expected default port 8080, got %d", cfg.Server.Port) + if cfg.Server.Port != 5355 { + t.Errorf("expected default port 5355, got %d", cfg.Server.Port) } - if len(cfg.Engines.LocalPorted) != 11 { - t.Errorf("expected 11 default engines, got %d", len(cfg.Engines.LocalPorted)) + if len(cfg.Engines.LocalPorted) != 14 { + t.Errorf("expected 14 default engines, got %d", len(cfg.Engines.LocalPorted)) } } diff --git a/internal/search/request_params.go b/internal/search/request_params.go index e72f739..1e4ac84 100644 --- a/internal/search/request_params.go +++ b/internal/search/request_params.go @@ -161,6 +161,10 @@ func ParseSearchRequest(r *http.Request) (SearchRequest, error) { delete(catSet, category) } } + // HTML UI uses a single ?category=images (etc.) query param; honor it here. + if single := strings.TrimSpace(r.FormValue("category")); single != "" { + catSet[single] = true + } categories := make([]string, 0, len(catSet)) for c := range catSet { categories = append(categories, c) @@ -196,16 +200,16 @@ func ParseSearchRequest(r *http.Request) (SearchRequest, error) { return SearchRequest{ Format: OutputFormat(format), - Query: q, - Pageno: pageno, - Safesearch: safesearch, - TimeRange: timeRange, + Query: q, + Pageno: pageno, + Safesearch: safesearch, + TimeRange: timeRange, TimeoutLimit: timeoutLimit, - Language: language, - Engines: engines, - Categories: categories, - EngineData: engineData, - AccessToken: accessToken, + Language: language, + Engines: engines, + Categories: categories, + EngineData: engineData, + AccessToken: accessToken, }, nil } @@ -250,4 +254,3 @@ func parseAccessToken(r *http.Request) string { return "" } - diff --git a/internal/search/request_params_test.go b/internal/search/request_params_test.go index a14911f..0924b06 100644 --- a/internal/search/request_params_test.go +++ b/internal/search/request_params_test.go @@ -72,3 +72,21 @@ func TestParseSearchRequest_CategoriesAndEngineData(t *testing.T) { } } +func TestParseSearchRequest_SingularCategoryParam(t *testing.T) { + r := httptest.NewRequest(http.MethodGet, "/search?q=cats&category=images", nil) + req, err := ParseSearchRequest(r) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + found := false + for _, c := range req.Categories { + if c == "images" { + found = true + break + } + } + if !found { + t.Fatalf("expected category images from ?category=images, got %v", req.Categories) + } +} + diff --git a/internal/views/templates/base.html b/internal/views/templates/base.html index 979ba5d..f75f358 100644 --- a/internal/views/templates/base.html +++ b/internal/views/templates/base.html @@ -1,6 +1,6 @@ {{define "base"}} - + @@ -8,10 +8,1017 @@ {{if .Query}}{{.Query}} — {{end}}samsa - + + + +