Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Simplify the home page around the instance introduction

ff9870f1a26a17d54c21d411000a6c7d58ab5c0d · Keenan Nemetz · 1d ago · parent 6ce635a8

Simplify the home page around the instance introduction

Drop the page header and its hero actions. The instance name and sign-in links already live in the top bar, and the introduction is the place to say what the forge is. Move New repository into the Repositories heading, where it sits with the list it creates.

Remove the introduction card header and its in-page editor. The introduction is profile/README.md in the instance repository, so it is read and pushed like every other customization. The POST / handler and its editor code go with it; an instance admin without an instance repository still gets the setup hint.

server/web/landing.go −95

@@ -1,95 +0,0 @@
1 package web
2
3 // The landing page's introduction, edited from the home page itself. The text is
4 // committed to the instance repository (forge/instancerepo), not written to
5 // vole.toml: the packaged service cannot write its config file, and the theme
6 // repository is where a customization belongs. The commit is the history; the
7 // audit log records who changed the page.
8 //
9 // The introduction is markdown written by an instance admin rather than pushed,
10 // but it still ends up in a page, so it is rendered through the same parser and
11 // sanitizer as an issue body.
12
13 import (
14 "net/http"
15 "strconv"
16 "strings"
17
18 "github.com/rodent-software/vole/core/git"
19 "github.com/rodent-software/vole/forge/instancerepo"
20 )
21
22 const (
23 // maxIntroBytes bounds the saved markdown. The body bound leaves room for it
24 // plus the form overhead.
25 maxIntroBytes = 20000
26 maxIntroForm = 64 << 10
27 )
28
29 // SaveHomeIntro answers the introduction card's form on the home page. An empty
30 // introduction removes profile/README.md, which is how the card's body goes away;
31 // the markdown is kept as written and rendered on the way out.
32 func (w *Web) SaveHomeIntro(rw http.ResponseWriter, r *http.Request) {
33 pr, ok := w.beginAdminPost(rw, r, maxIntroForm)
34 if !ok {
35 return
36 }
37 if w.InstanceRepo == nil || !w.InstanceRepo.Ready() {
38 w.serverError(rw, r, "the instance repository is not ready")
39 return
40 }
41 next := normalizeIntro(r.PostFormValue("intro"))
42 if len(next) > maxIntroBytes {
43 msg := "The introduction is longer than " + strconv.Itoa(maxIntroBytes) + " bytes."
44 w.renderIntroEditor(rw, r, next, msg, http.StatusBadRequest)
45 return
46 }
47 m, err := w.Repos.Open(w.InstanceRepo.Name())
48 if err != nil {
49 w.renderIntroEditor(rw, r, next, "The instance repository could not be opened: "+err.Error(), http.StatusInternalServerError)
50 return
51 }
52 prev := ""
53 if snap := w.InstanceRepo.Current(); snap != nil {
54 prev = string(snap.Readme)
55 }
56 if normalizeIntro(prev) == next {
57 // Nothing changed: do not add a commit that says nothing.
58 http.Redirect(rw, r, "/", http.StatusSeeOther)
59 return
60 }
61 if _, err := git.CommitFilesBy(m.Git(), "refs/heads/"+m.DefaultBranch, func(files map[string][]byte) (string, error) {
62 if next == "" {
63 delete(files, instancerepo.ReadmePath)
64 } else {
65 files[instancerepo.ReadmePath] = []byte(next)
66 }
67 return pr.account.Name, nil
68 }, "Update the landing page introduction"); err != nil {
69 w.renderIntroEditor(rw, r, next, "The introduction could not be saved: "+err.Error(), http.StatusInternalServerError)
70 return
71 }
72 if err := w.InstanceRepo.Reload(); err != nil && w.Logger != nil {
73 w.Logger("instance repository reload after intro save: %v", err)
74 }
75 w.audit("web.home.intro", pr.account.Name, "", instancerepo.ReadmePath)
76 http.Redirect(rw, r, "/", http.StatusSeeOther)
77 }
78
79 // normalizeIntro is the one shape the store and the page agree on: no carriage
80 // returns, and no whitespace that would render as an empty block.
81 func normalizeIntro(raw string) string {
82 return strings.TrimSpace(strings.ReplaceAll(raw, "\r\n", "\n"))
83 }
84
85 // renderIntroEditor re-renders the home page with the editor open and the
86 // submitted text still in it, so a rejected save does not make the admin retype
87 // a paragraph.
88 func (w *Web) renderIntroEditor(rw http.ResponseWriter, r *http.Request, submitted, msg string, status int) {
89 data, err := w.homeView(r, "", true, &submitted, msg)
90 if err != nil {
91 w.serverError(rw, r, "index unavailable: "+err.Error())
92 return
93 }
94 w.render(rw, "index", data, status)
95 }