diff options
Diffstat (limited to 'internal/web/handlers_layout.go')
| -rw-r--r-- | internal/web/handlers_layout.go | 315 |
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.")) -} |
