aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/handlers_pages.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/web/handlers_pages.go')
-rw-r--r--internal/web/handlers_pages.go107
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,