diff options
| author | grm <grm@eyesin.space> | 2026-09-18 19:34:35 +0300 |
|---|---|---|
| committer | grm <grm@eyesin.space> | 2026-09-18 19:34:35 +0300 |
| commit | 96336d8bacd2bdf81acf9a18e2fe57be9d4e9b4f (patch) | |
| tree | e7851ddadf2ed776ab24a1bad4d446b7c48567db /internal/web/handlers_design.go | |
| parent | 63f9fdff6e5bdf6333a4b7813ebeb5b797838b6d (diff) | |
| download | blogspace-96336d8bacd2bdf81acf9a18e2fe57be9d4e9b4f.tar.gz blogspace-96336d8bacd2bdf81acf9a18e2fe57be9d4e9b4f.tar.bz2 blogspace-96336d8bacd2bdf81acf9a18e2fe57be9d4e9b4f.zip | |
Show a live preview of the header on the Design tab's Menu card
The menu options had grown to a dozen switches with nothing to look at
but the blog after a save. A new POST /b/{sub}/design/preview reads the
form as a save would and renders the header alone — the real blog.css
and theme CSS, the modules in their order, the menu as edited — into a
sandboxed frame that refreshes shortly after every change, with a Wide
screen / Phone toggle. Nothing is stored.
The frame keeps the dashboard's origin (allow-same-origin, no scripts)
so the blog's logo and fonts, linked through /b/<sub>/media since the
root host's /media is the root blog's, get the session cookie; that is
safe because no header module is owner HTML. The frame runs no scripts,
so the page's own script folds the menu in it the way the blog does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'internal/web/handlers_design.go')
| -rw-r--r-- | internal/web/handlers_design.go | 35 |
1 files changed, 35 insertions, 0 deletions
diff --git a/internal/web/handlers_design.go b/internal/web/handlers_design.go index 792dcdc..a10f285 100644 --- a/internal/web/handlers_design.go +++ b/internal/web/handlers_design.go @@ -1,6 +1,7 @@ package web import ( + "log" "net/http" "time" @@ -203,6 +204,40 @@ func (s *Server) handleDesign(w http.ResponseWriter, r *http.Request) { redirectOK(w, r, back, s.tr(r, "Design saved. Refresh your blog to see it.")) } +// handleDesignPreview renders the header as the design form would save it, +// for the Menu card's preview frame: the form is read like a save (theme, +// modules, menu), nothing is stored, and a bad row is shown rather than +// reported. Login, ownership, CSRF and the body cap are withBlog's. The page +// is on the root host, so the blog's files are linked through /b/<sub>/media. +func (s *Server) handleDesignPreview(w http.ResponseWriter, r *http.Request) { + blog := currentBlog(r) + th := ThemeFromForm(ParseTheme(blog.ThemeJSON), r.Form) + th.HeaderImage = "" // the banner would dwarf the menu, which is what the frame is for + mods, _ := parseModules(r.Form, s.lang(r), blog.Language) + pages, err := blogStore(r).ListPages(r.Context()) + if err != nil { + s.serverError(w, err) + return + } + menu, _ := parseMenu(r.Form, s.lang(r), pages) + media := "/b/" + blog.Subdomain + "/media" + data := map[string]any{"theme": th, "css": th.CSSAt(media), "layout": buildLayout(th, mods), "menu": menu, "pages": pages} + for i := range pages { // the home page is "current", so its link shows the mark + if pages[i].IsHome { + data["page"] = &pages[i] + } + } + tpl, err := s.tpl.get(blog.Language, "blog/preview.html") + if err != nil { + s.serverError(w, err) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + if err := tpl.ExecuteTemplate(w, "preview", view{Blog: blog, Lang: blog.Language, Media: media, Data: data}); err != nil { + log.Printf("render preview: %v", err) + } +} + // handleDesignReset restores the defaults, layout modules included; images // stay in the library and the menu is left alone. func (s *Server) handleDesignReset(w http.ResponseWriter, r *http.Request) { |
