From e974eb6f3feb06c8951f85c1d34161938905f807 Mon Sep 17 00:00:00 2001 From: grm Date: Sat, 12 Sep 2026 19:47:28 +0300 Subject: Add a favicon and rework the Design page Blogs had no icon at all, and browsers probing /favicon.ico ended up rendering the 404 page. Ship a default paper & ink icon (svg + png) and let each blog replace it from the Design page; /favicon.ico now serves the default or redirects to the blog's own image. ICO uploads accepted. The Design page gains colour schemes, reset to defaults, thumbnail image pickers, jump links and a sticky save bar, plus new options: heading font, link underline/hover, content box style and padding, header image height, post dates and format, footer RSS toggle, menu alignment/style/hover and sidebar width. The Menu card picks the position with illustrated tiles and shows only the options that apply to it, using CSS :checked rules rather than JavaScript. All new defaults reproduce the previous look, so existing blogs are unchanged until edited. Theme is jsonb, so no migration. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A --- internal/web/handlers_media.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'internal/web/handlers_media.go') diff --git a/internal/web/handlers_media.go b/internal/web/handlers_media.go index e651135..9d5a515 100644 --- a/internal/web/handlers_media.go +++ b/internal/web/handlers_media.go @@ -3,6 +3,7 @@ package web import ( "bytes" "errors" + "io/fs" "net/http" "github.com/google/uuid" @@ -36,3 +37,23 @@ func (s *Server) handleMedia(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") http.ServeContent(w, r, img.Filename, img.CreatedAt, bytes.NewReader(img.Data)) } + +// handleFavicon answers the browsers that ask for /favicon.ico regardless of +// the tags: the blog's own icon if it set one, else the +// Blogspace default. Without this the request would render the 404 page. +func (s *Server) handleFavicon(w http.ResponseWriter, r *http.Request) { + if icon := ParseTheme(currentBlog(r).ThemeJSON).Favicon; icon != "" { + http.Redirect(w, r, "/media/"+icon, http.StatusFound) + return + } + data, err := fs.ReadFile(s.tpl.AssetsFS(), "favicon.png") + if err != nil { + s.serverError(w, err) + return + } + w.Header().Set("Content-Type", "image/png") + if !s.cfg.Dev { + w.Header().Set("Cache-Control", "public, max-age=3600") + } + w.Write(data) +} -- cgit v1.2.3