diff options
Diffstat (limited to 'internal/web/handlers_pages.go')
| -rw-r--r-- | internal/web/handlers_pages.go | 107 |
1 files changed, 101 insertions, 6 deletions
diff --git a/internal/web/handlers_pages.go b/internal/web/handlers_pages.go index b085b82..0a2c9c4 100644 --- a/internal/web/handlers_pages.go +++ b/internal/web/handlers_pages.go @@ -6,6 +6,7 @@ import ( "strconv" "strings" + "github.com/google/uuid" "github.com/gramanas/blogspace/internal/slug" "github.com/gramanas/blogspace/internal/store" ) @@ -38,19 +39,86 @@ func (s *Server) loadPage(w http.ResponseWriter, r *http.Request) *store.Page { return p } +// pageKind reads a new page's kind from the form or query (?kind= on the +// "New special page" link, a hidden field on the POST). An existing page +// keeps its own. +func pageKind(r *http.Request) string { + return pick(r.FormValue("kind"), store.PageKindPosts, store.PageKindGallery) +} + +// themeFiles are the images the design uses (logo, favicon, header and +// background image): decoration, not content, so a gallery never shows them. +func themeFiles(t Theme) []uuid.UUID { + var ids []uuid.UUID + for _, v := range []string{t.Logo, t.Favicon, t.HeaderImage, t.BgImage} { + if id, err := uuid.Parse(v); err == nil { + ids = append(ids, id) + } + } + return ids +} + +// pageFormData is what the page form needs besides the page. For a gallery +// that is every library image but the theme's, and which ones the page +// hides — an unsaved page hides none. +func (s *Server) pageFormData(r *http.Request, p *store.Page) (map[string]any, error) { + d := map[string]any{"page": p} + if p.Kind != store.PageKindGallery { + return d, nil + } + images, _, err := blogStore(r).ListFiles(r.Context(), "image", "", 0, 0) + if err != nil { + return nil, err + } + skip := map[uuid.UUID]bool{} + for _, id := range themeFiles(ParseTheme(currentBlog(r).ThemeJSON)) { + skip[id] = true + } + shown := images[:0] + for _, f := range images { + if !skip[f.ID] { + shown = append(shown, f) + } + } + hidden := map[string]bool{} + if p.ID != 0 { + if hidden, err = blogStore(r).HiddenFiles(r.Context(), p.ID); err != nil { + return nil, err + } + } + d["images"], d["hidden"] = shown, hidden + return d, nil +} + +// hiddenFromForm is the "hide" checkboxes of a gallery form as ids; junk is dropped. +func hiddenFromForm(r *http.Request) []uuid.UUID { + ids := []uuid.UUID{} + for _, v := range r.Form["hide"] { + if id, err := uuid.Parse(v); err == nil { + ids = append(ids, id) + } + } + return ids +} + func (s *Server) handlePageForm(w http.ResponseWriter, r *http.Request) { - p := &store.Page{ShowInNav: true, ShowNotices: true} + p := &store.Page{Kind: pageKind(r), ShowInNav: true, ShowNotices: true} if r.PathValue("id") != "" { if p = s.loadPage(w, r); p == nil { return } } - s.render(w, r, "dashboard/page_form.html", map[string]any{"page": p}) + d, err := s.pageFormData(r, p) + if err != nil { + s.serverError(w, err) + return + } + s.render(w, r, "dashboard/page_form.html", d) } func (s *Server) handlePageSave(w http.ResponseWriter, r *http.Request) { blog := currentBlog(r) - p := &store.Page{} + p := &store.Page{Kind: pageKind(r)} if r.PathValue("id") != "" { if p = s.loadPage(w, r); p == nil { return @@ -81,8 +149,25 @@ func (s *Server) handlePageSave(w http.ResponseWriter, r *http.Request) { case !slug.Valid(p.Slug) || reservedPageSlugs[p.Slug]: msg = s.tr(r, "Slug may only contain lowercase letters, digits and dashes (and not be a reserved word).") } + // The rejected form is rendered with what was sent, ticks included. + fail := func(status int, msg string) { + d, err := s.pageFormData(r, p) + if err != nil { + s.serverError(w, err) + return + } + if p.Kind == store.PageKindGallery { + hidden := map[string]bool{} + for _, id := range hiddenFromForm(r) { + hidden[id.String()] = true + } + d["hidden"] = hidden + } + d["error"] = msg + s.renderStatus(w, r, status, "dashboard/page_form.html", d) + } if msg != "" { - s.renderStatus(w, r, http.StatusBadRequest, "dashboard/page_form.html", map[string]any{"page": p, "error": msg}) + fail(http.StatusBadRequest, msg) return } p.IntroMD = appendFile(p.IntroMD, img, p.Format) @@ -106,12 +191,18 @@ func (s *Server) handlePageSave(w http.ResponseWriter, r *http.Request) { } if err != nil { if errors.Is(err, store.ErrConflict) { - s.renderStatus(w, r, http.StatusConflict, "dashboard/page_form.html", map[string]any{"page": p, "error": s.tr(r, "A page with that slug already exists.")}) + fail(http.StatusConflict, s.tr(r, "A page with that slug already exists.")) return } s.serverError(w, err) return } + if p.Kind == store.PageKindGallery { + if err := blogStore(r).SetHiddenFiles(r.Context(), p.ID, hiddenFromForm(r)); err != nil { + s.serverError(w, err) + return + } + } redirectOK(w, r, "/b/"+blog.Subdomain+"/pages/"+strconv.FormatInt(p.ID, 10)+"/edit", s.tr(r, "Saved. Refresh your blog to see it.")) } @@ -120,8 +211,12 @@ func (s *Server) handlePageDeleteConfirm(w http.ResponseWriter, r *http.Request) if p == nil { return } + what := s.trf(r, "the page “%s” and all %d of its posts", p.Title, p.PostCount) + if p.Special() { + what = s.trf(r, "the gallery page “%s”", p.Title) + } s.render(w, r, "dashboard/confirm.html", map[string]any{ - "what": s.trf(r, "the page “%s” and all %d of its posts", p.Title, p.PostCount), + "what": what, "action": r.URL.Path, "back": "/b/" + currentBlog(r).Subdomain + "/pages", "isHome": p.IsHome, |
