From e96bbec551c0388e972894b349413fa6ef607352 Mon Sep 17 00:00:00 2001 From: grm Date: Thu, 17 Sep 2026 21:29:44 +0300 Subject: Add custom fonts: upload WOFF2/WOFF/TTF/OTF files and pick them on the Design tab Fonts become a file kind of their own (sniffed from the bytes like images, served inline from /media), and the theme gets custom_font and custom_heading_font: a chosen file is declared as @font-face and put first in the stack, the built-in Font / Heading font choice behind it as the fallback, so there is no "custom" enum value and no invalid state. The pickers are plain selects with an upload input, no script. Fonts uploaded before this are reclassified by the migration. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A --- internal/web/handlers_design.go | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'internal/web/handlers_design.go') diff --git a/internal/web/handlers_design.go b/internal/web/handlers_design.go index acc7569..792dcdc 100644 --- a/internal/web/handlers_design.go +++ b/internal/web/handlers_design.go @@ -13,7 +13,8 @@ import ( // the menu — saved in one go. // recentImages is how many library images the pickers list up front; the -// rest are fetched by the script's library panel, page by page. +// rest are fetched by the script's library panel, page by page. The font +// pickers list as many, with no panel behind them: a blog has a few fonts. const recentImages = 48 // designView is the state the design page shows: what the database holds, or @@ -71,13 +72,17 @@ func (s *Server) designData(r *http.Request, v designView) (map[string]any, erro if err != nil { return nil, err } - // The pickers list the recent images; a chosen one older than that still + fonts, _, err := bs.ListFiles(r.Context(), "font", "", recentImages, 0) + if err != nil { + return nil, err + } + // The pickers list the recent files; a chosen one older than that still // needs its name for the select. names := map[string]string{} - for _, f := range images { + for _, f := range append(images, fonts...) { names[f.ID.String()] = f.Filename } - for _, id := range []string{v.Theme.BgImage, v.Theme.HeaderImage, v.Theme.Logo, v.Theme.Favicon} { + for _, id := range []string{v.Theme.BgImage, v.Theme.HeaderImage, v.Theme.Logo, v.Theme.Favicon, v.Theme.CustomFont, v.Theme.CustomHeadingFont} { if id == "" || names[id] != "" { continue } @@ -115,7 +120,7 @@ func (s *Server) designData(r *http.Request, v designView) (map[string]any, erro "templates": templates, "pageItem": store.MenuItem{PageID: &pageID}, "linkItem": store.MenuItem{}, "kinds": moduleKinds, "names": moduleNames, "areaNames": areaNames, "single": singleModuleAreas, "counts": []int{3, 5, 8, 10, 15, 20}, "tagCounts": []int{5, 10, 20, 30, 50}, "perPage": postsPerPageChoices, - "images": images, "imageNames": names, "imageCount": imageCount, "presets": Presets(), "today": time.Now(), + "images": images, "fonts": fonts, "imageNames": names, "imageCount": imageCount, "presets": Presets(), "today": time.Now(), }, nil } @@ -164,16 +169,17 @@ func (s *Server) handleDesign(w http.ResponseWriter, r *http.Request) { // Optional direct uploads from the pickers. One that is stored stays // chosen in the form even when something else is rejected. for _, u := range []struct { - field string - dst *string - }{{"bg_image_file", &v.Theme.BgImage}, {"header_image_file", &v.Theme.HeaderImage}, {"logo_file", &v.Theme.Logo}, {"favicon_file", &v.Theme.Favicon}} { - img, err := s.readUpload(r, u.field, true) + field, kind string + dst *string + }{{"bg_image_file", "image", &v.Theme.BgImage}, {"header_image_file", "image", &v.Theme.HeaderImage}, {"logo_file", "image", &v.Theme.Logo}, {"favicon_file", "image", &v.Theme.Favicon}, + {"custom_font_file", "font", &v.Theme.CustomFont}, {"custom_heading_font_file", "font", &v.Theme.CustomHeadingFont}} { + f, err := s.readUpload(r, u.field, u.kind) if err != nil { note(err) continue } - if img != nil { - *u.dst = img.ID.String() + if f != nil { + *u.dst = f.ID.String() } } if problem != nil { -- cgit v1.2.3