aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/handlers_layout.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/web/handlers_layout.go')
-rw-r--r--internal/web/handlers_layout.go315
1 files changed, 0 insertions, 315 deletions
diff --git a/internal/web/handlers_layout.go b/internal/web/handlers_layout.go
deleted file mode 100644
index db4f47c..0000000
--- a/internal/web/handlers_layout.go
+++ /dev/null
@@ -1,315 +0,0 @@
-package web
-
-import (
- "errors"
- "net/http"
- "strconv"
- "strings"
-
- "github.com/gramanas/blogspace/internal/i18n"
- "github.com/gramanas/blogspace/internal/store"
-)
-
-// The Layout tab: which areas of the blog are shown, how wide the columns
-// are, what modules each area holds, and the menu.
-
-const (
- maxModuleBytes = 20_000
- maxRecentCount = 20
- maxMenuLabel = 60
-)
-
-func (s *Server) layoutURL(r *http.Request) string {
- return "/b/" + currentBlog(r).Subdomain + "/layout"
-}
-
-// layoutData is everything layout.html needs; it is rebuilt after a failed form too.
-func (s *Server) layoutData(r *http.Request) (map[string]any, error) {
- blog := currentBlog(r)
- mods, err := blogStore(r).ListModules(r.Context())
- if err != nil {
- return nil, err
- }
- menu, err := blogStore(r).ListMenu(r.Context())
- if err != nil {
- return nil, err
- }
- pages, err := blogStore(r).ListPages(r.Context())
- if err != nil {
- return nil, err
- }
- byArea := map[string][]store.Module{}
- for _, m := range mods {
- byArea[m.Area] = append(byArea[m.Area], m)
- }
- var freePages []store.Page // pages not yet in the menu
- for _, p := range pages {
- if !p.ShowInNav {
- freePages = append(freePages, p)
- }
- }
- return map[string]any{"theme": ParseTheme(blog.ThemeJSON), "areas": byArea, "menu": menu, "freePages": freePages,
- "kinds": moduleKinds, "names": moduleNames, "areaNames": areaNames, "areaOrder": []string{"above", "below"}}, nil
-}
-
-func (s *Server) handleLayout(w http.ResponseWriter, r *http.Request) {
- data, err := s.layoutData(r)
- if err != nil {
- s.serverError(w, err)
- return
- }
- s.render(w, r, "dashboard/layout.html", data)
-}
-
-func (s *Server) handleLayoutWidths(w http.ResponseWriter, r *http.Request) {
- blog := currentBlog(r)
- theme := LayoutFromForm(ParseTheme(blog.ThemeJSON), r.Form)
- if err := blogStore(r).UpdateTheme(r.Context(), theme.JSON()); err != nil {
- s.serverError(w, err)
- return
- }
- redirectOK(w, r, s.layoutURL(r)+"#columns", s.tr(r, "Columns saved. Refresh your blog to see them."))
-}
-
-// handleAreaToggle shows or hides a whole area, keeping its modules.
-func (s *Server) handleAreaToggle(w http.ResponseWriter, r *http.Request) {
- blog := currentBlog(r)
- area := r.PathValue("area")
- if _, ok := areaNames[area]; !ok || singleModuleAreas[area] {
- http.NotFound(w, r)
- return
- }
- theme := ParseTheme(blog.ThemeJSON)
- on := !theme.AreaOn(area)
- theme.SetAreaOn(area, on)
- if err := blogStore(r).UpdateTheme(r.Context(), theme.JSON()); err != nil {
- s.serverError(w, err)
- return
- }
- msg := s.trf(r, "%s hidden.", s.tr(r, areaNames[area]))
- if on {
- msg = s.trf(r, "%s shown again. Refresh your blog to see it.", s.tr(r, areaNames[area]))
- }
- redirectOK(w, r, s.layoutURL(r)+"#"+area, msg)
-}
-
-func (s *Server) handleModuleAdd(w http.ResponseWriter, r *http.Request) {
- area, kind := r.PathValue("area"), r.FormValue("kind")
- if !allowedKind(area, kind) {
- s.plainError(w, http.StatusBadRequest, s.tr(r, "That module cannot go in that area."))
- return
- }
- if singleModuleAreas[area] {
- mods, err := blogStore(r).ListModules(r.Context())
- if err != nil {
- s.serverError(w, err)
- return
- }
- for _, m := range mods {
- if m.Area == area {
- redirectOK(w, r, s.layoutURL(r)+"#main", s.tr(r, "There is already a module there; edit or remove it first."))
- return
- }
- }
- }
- m := &store.Module{Area: area, Kind: kind, Count: 5}
- blogLang := currentBlog(r).Language // headings are content: the blog's language, not the viewer's
- switch kind { // sensible headings so the module reads well before it is edited
- case "archive":
- m.Title = i18n.T(blogLang, "Archive")
- case "recent":
- m.Title = i18n.T(blogLang, "Recent posts")
- case "sitemap":
- m.Title = i18n.T(blogLang, "Pages")
- case "tags":
- m.Title = i18n.T(blogLang, "Tags")
- m.Count = 10
- case "tagcloud":
- m.Title = i18n.T(blogLang, "Tag cloud")
- }
- m, err := blogStore(r).CreateModule(r.Context(), m)
- if err != nil {
- s.serverError(w, err)
- return
- }
- if kind == "html" || kind == "text" {
- redirectOK(w, r, s.layoutURL(r)+"/modules/"+strconv.FormatInt(m.ID, 10)+"/edit", s.tr(r, "Added. Now write what it should show."))
- return
- }
- redirectOK(w, r, s.layoutURL(r)+"#"+anchorFor(area), s.trf(r, "%s added. Refresh your blog to see it.", s.tr(r, moduleNames[kind])))
-}
-
-// anchorFor is the layout.html card an area is edited in.
-func anchorFor(area string) string {
- if singleModuleAreas[area] {
- return "main"
- }
- return area
-}
-
-func (s *Server) loadModule(w http.ResponseWriter, r *http.Request) *store.Module {
- id, _ := strconv.ParseInt(r.PathValue("id"), 10, 64)
- m, err := blogStore(r).ModuleByID(r.Context(), id)
- if err != nil {
- if errors.Is(err, store.ErrNotFound) {
- http.NotFound(w, r)
- } else {
- s.serverError(w, err)
- }
- return nil
- }
- return m
-}
-
-func (s *Server) handleModuleForm(w http.ResponseWriter, r *http.Request) {
- m := s.loadModule(w, r)
- if m == nil {
- return
- }
- if !moduleHasSettings(*m) {
- http.Redirect(w, r, s.layoutURL(r), http.StatusSeeOther)
- return
- }
- s.render(w, r, "dashboard/module_form.html", s.moduleFormData(r, m, ""))
-}
-
-func (s *Server) moduleFormData(r *http.Request, m *store.Module, errMsg string) map[string]any {
- d := map[string]any{"module": m, "name": s.tr(r, moduleNames[m.Kind]), "area": s.tr(r, areaNames[m.Area]), "counts": []int{3, 5, 8, 10, 15, 20}, "tagCounts": []int{5, 10, 20, 30, 50}}
- if errMsg != "" {
- d["error"] = errMsg
- }
- return d
-}
-
-func (s *Server) handleModuleSave(w http.ResponseWriter, r *http.Request) {
- m := s.loadModule(w, r)
- if m == nil {
- return
- }
- m.Title = strings.TrimSpace(r.FormValue("title"))
- m.Body = strings.ReplaceAll(r.FormValue("body"), "\r\n", "\n")
- if n, err := strconv.Atoi(r.FormValue("count")); err == nil {
- if m.Kind == "tags" { // 0 = all of them
- m.Count = clampInt(n, 0, maxTagListCount)
- } else {
- m.Count = clampInt(n, 1, maxRecentCount)
- }
- }
- fail := func(msg string) {
- s.renderStatus(w, r, http.StatusBadRequest, "dashboard/module_form.html", s.moduleFormData(r, m, msg))
- }
- switch {
- case len(m.Title) > 120:
- fail(s.tr(r, "Heading is too long (max 120 characters)."))
- return
- case len(m.Body) > maxModuleBytes:
- fail(s.tr(r, "Content is too long (20 KB max)."))
- return
- }
- if err := blogStore(r).UpdateModule(r.Context(), m); err != nil {
- s.serverError(w, err)
- return
- }
- redirectOK(w, r, s.layoutURL(r)+"#"+anchorFor(m.Area), s.tr(r, "Saved. Refresh your blog to see it."))
-}
-
-func (s *Server) handleModuleMove(w http.ResponseWriter, r *http.Request) {
- m := s.loadModule(w, r)
- if m == nil {
- return
- }
- dir := 1
- if r.FormValue("dir") == "up" {
- dir = -1
- }
- if err := blogStore(r).MoveModule(r.Context(), m.ID, dir); err != nil {
- s.serverError(w, err)
- return
- }
- http.Redirect(w, r, s.layoutURL(r)+"#"+anchorFor(m.Area), http.StatusSeeOther)
-}
-
-// handleModuleDeleteConfirm guards modules with content the blogger typed in;
-// the list removes the others directly.
-func (s *Server) handleModuleDeleteConfirm(w http.ResponseWriter, r *http.Request) {
- m := s.loadModule(w, r)
- if m == nil {
- return
- }
- if strings.TrimSpace(m.Body) == "" {
- s.deleteModule(w, r, m)
- return
- }
- s.render(w, r, "dashboard/confirm.html", map[string]any{"what": s.trf(r, "the ā€œ%sā€ module and its text", s.tr(r, moduleNames[m.Kind])), "action": r.URL.Path, "back": s.layoutURL(r)})
-}
-
-func (s *Server) handleModuleDelete(w http.ResponseWriter, r *http.Request) {
- if m := s.loadModule(w, r); m != nil {
- s.deleteModule(w, r, m)
- }
-}
-
-func (s *Server) deleteModule(w http.ResponseWriter, r *http.Request, m *store.Module) {
- if err := blogStore(r).DeleteModule(r.Context(), m.ID); err != nil {
- s.serverError(w, err)
- return
- }
- redirectOK(w, r, s.layoutURL(r)+"#"+anchorFor(m.Area), s.trf(r, "%s removed.", s.tr(r, moduleNames[m.Kind])))
-}
-
-// ---- menu --------------------------------------------------------------------
-
-func (s *Server) handleMenuAddPage(w http.ResponseWriter, r *http.Request) {
- pageID, _ := strconv.ParseInt(r.FormValue("page"), 10, 64)
- if pageID == 0 {
- redirectOK(w, r, s.layoutURL(r)+"#menu", s.tr(r, "Pick a page first."))
- return
- }
- if err := blogStore(r).AddMenuPage(r.Context(), pageID); err != nil {
- s.serverError(w, err)
- return
- }
- redirectOK(w, r, s.layoutURL(r)+"#menu", s.tr(r, "Page added to the menu. Refresh your blog to see it."))
-}
-
-func (s *Server) handleMenuAddLink(w http.ResponseWriter, r *http.Request) {
- label, link := strings.TrimSpace(r.FormValue("label")), strings.TrimSpace(r.FormValue("url"))
- if link != "" && !strings.HasPrefix(link, "/") && !strings.Contains(link, ":") {
- link = "https://" + link // "example.org" is what people type
- }
- switch {
- case label == "" || len(label) > maxMenuLabel:
- redirectOK(w, r, s.layoutURL(r)+"#menu", s.tr(r, "Give the link a short text (max 60 characters)."))
- return
- case !validLinkURL(link):
- redirectOK(w, r, s.layoutURL(r)+"#menu", s.tr(r, "That address does not look right. Use a full web address like https://example.org or a path like /about."))
- return
- }
- if err := blogStore(r).AddMenuLink(r.Context(), label, link); err != nil {
- s.serverError(w, err)
- return
- }
- redirectOK(w, r, s.layoutURL(r)+"#menu", s.tr(r, "Link added to the menu. Refresh your blog to see it."))
-}
-
-func (s *Server) handleMenuMove(w http.ResponseWriter, r *http.Request) {
- id, _ := strconv.ParseInt(r.PathValue("id"), 10, 64)
- dir := 1
- if r.FormValue("dir") == "up" {
- dir = -1
- }
- if err := blogStore(r).MoveMenuItem(r.Context(), id, dir); err != nil {
- s.serverError(w, err)
- return
- }
- http.Redirect(w, r, s.layoutURL(r)+"#menu", http.StatusSeeOther)
-}
-
-func (s *Server) handleMenuDelete(w http.ResponseWriter, r *http.Request) {
- id, _ := strconv.ParseInt(r.PathValue("id"), 10, 64)
- if err := blogStore(r).DeleteMenuItem(r.Context(), id); err != nil {
- s.serverError(w, err)
- return
- }
- redirectOK(w, r, s.layoutURL(r)+"#menu", s.tr(r, "Removed from the menu."))
-}