fix(prefs): persist favicon choice and apply to HTML results
- Save favicon cookie on POST /preferences; reflect selection in template - Add getFaviconService helper; pass favicon service into FromResponse - Compute ResultView.FaviconIconURL (none/google/duckduckgo/self proxy) - Update result_item and video_item templates; add httpapi/views tests Made-with: Cursor
This commit is contained in:
parent
518215f62e
commit
90ea4c9f56
7 changed files with 171 additions and 43 deletions
|
|
@ -56,16 +56,17 @@ type PageData struct {
|
|||
ShowHeader bool
|
||||
IsImageSearch bool
|
||||
// Theme is the user's selected theme (light/dark) from cookie
|
||||
Theme string
|
||||
Theme string
|
||||
FaviconService string
|
||||
// New fields for three-column layout
|
||||
Categories []string
|
||||
CategoryIcons map[string]string
|
||||
CategoryIcons map[string]string
|
||||
DisabledCategories []string
|
||||
ActiveCategory string
|
||||
TimeFilters []FilterOption
|
||||
TypeFilters []FilterOption
|
||||
ActiveTime string
|
||||
ActiveType string
|
||||
ActiveCategory string
|
||||
TimeFilters []FilterOption
|
||||
TypeFilters []FilterOption
|
||||
ActiveTime string
|
||||
ActiveType string
|
||||
}
|
||||
|
||||
// ResultView is a template-friendly wrapper around a MainResult.
|
||||
|
|
@ -76,6 +77,8 @@ type ResultView struct {
|
|||
TemplateName string
|
||||
// Domain is the hostname extracted from the result URL, used for favicon proxying.
|
||||
Domain string
|
||||
// FaviconIconURL is the resolved favicon image URL for the user's favicon preference (empty = hide).
|
||||
FaviconIconURL string
|
||||
// SafeTitle and SafeContent are HTML-unescaped versions for rendering.
|
||||
// The API returns HTML entities which Go templates escape by default.
|
||||
SafeTitle template.HTML
|
||||
|
|
@ -84,8 +87,8 @@ type ResultView struct {
|
|||
|
||||
// PageNumber represents a numbered pagination button.
|
||||
type PageNumber struct {
|
||||
Num int
|
||||
IsCurrent bool
|
||||
Num int
|
||||
IsCurrent bool
|
||||
}
|
||||
|
||||
// InfoboxView is a template-friendly infobox.
|
||||
|
|
@ -102,9 +105,9 @@ type FilterOption struct {
|
|||
}
|
||||
|
||||
var (
|
||||
tmplFull *template.Template
|
||||
tmplIndex *template.Template
|
||||
tmplFragment *template.Template
|
||||
tmplFull *template.Template
|
||||
tmplIndex *template.Template
|
||||
tmplFragment *template.Template
|
||||
tmplPreferences *template.Template
|
||||
)
|
||||
|
||||
|
|
@ -152,8 +155,26 @@ func OpenSearchXML(baseURL string) ([]byte, error) {
|
|||
return []byte(result), nil
|
||||
}
|
||||
|
||||
// faviconIconURL returns a safe img src for the given service and hostname, or "" for none/invalid.
|
||||
func faviconIconURL(service, domain string) string {
|
||||
domain = strings.TrimSpace(domain)
|
||||
if domain == "" {
|
||||
return ""
|
||||
}
|
||||
switch service {
|
||||
case "google":
|
||||
return "https://www.google.com/s2/favicons?domain=" + url.QueryEscape(domain) + "&sz=32"
|
||||
case "duckduckgo":
|
||||
return "https://icons.duckduckgo.com/ip3/" + domain + ".ico"
|
||||
case "self":
|
||||
return "/favicon/" + domain
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// FromResponse builds PageData from a search response and request params.
|
||||
func FromResponse(resp contracts.SearchResponse, query string, pageno int, activeCategory, activeTime, activeType string) PageData {
|
||||
func FromResponse(resp contracts.SearchResponse, query string, pageno int, activeCategory, activeTime, activeType, faviconService string) PageData {
|
||||
// Set defaults
|
||||
if activeCategory == "" {
|
||||
activeCategory = "all"
|
||||
|
|
@ -164,9 +185,10 @@ func FromResponse(resp contracts.SearchResponse, query string, pageno int, activ
|
|||
Pageno: pageno,
|
||||
NumberOfResults: resp.NumberOfResults,
|
||||
UnresponsiveEngines: resp.UnresponsiveEngines,
|
||||
FaviconService: faviconService,
|
||||
|
||||
// New: categories with icons
|
||||
Categories: []string{"all", "news", "images", "videos", "maps"},
|
||||
Categories: []string{"all", "news", "images", "videos", "maps"},
|
||||
DisabledCategories: []string{"shopping", "music", "weather"},
|
||||
CategoryIcons: map[string]string{
|
||||
"all": "🌐",
|
||||
|
|
@ -220,11 +242,12 @@ func FromResponse(resp contracts.SearchResponse, query string, pageno int, activ
|
|||
}
|
||||
r.Thumbnail = util.SanitizeResultURL(r.Thumbnail)
|
||||
pd.Results[i] = ResultView{
|
||||
MainResult: r,
|
||||
TemplateName: tmplName,
|
||||
Domain: domain,
|
||||
SafeTitle: template.HTML(html.UnescapeString(r.Title)),
|
||||
SafeContent: template.HTML(html.UnescapeString(r.Content)),
|
||||
MainResult: r,
|
||||
TemplateName: tmplName,
|
||||
Domain: domain,
|
||||
FaviconIconURL: faviconIconURL(faviconService, domain),
|
||||
SafeTitle: template.HTML(html.UnescapeString(r.Title)),
|
||||
SafeContent: template.HTML(html.UnescapeString(r.Content)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -328,8 +351,12 @@ func RenderSearchAuto(w http.ResponseWriter, r *http.Request, data PageData) err
|
|||
}
|
||||
|
||||
// RenderPreferences renders the full preferences page.
|
||||
func RenderPreferences(w http.ResponseWriter, sourceURL, theme string) error {
|
||||
func RenderPreferences(w http.ResponseWriter, sourceURL, theme, faviconService string) error {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
return tmplPreferences.ExecuteTemplate(w, "base", PageData{ShowHeader: true, SourceURL: sourceURL, Theme: theme})
|
||||
return tmplPreferences.ExecuteTemplate(w, "base", PageData{
|
||||
ShowHeader: true,
|
||||
SourceURL: sourceURL,
|
||||
Theme: theme,
|
||||
FaviconService: faviconService,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue