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/home.go +15−42
| @@ -2,11 +2,12 @@ package web | |||
| 2 | 2 | ||
| 3 | 3 | // The landing page and the instance-wide activity feed. | |
| 4 | 4 | // | |
| 5 | // The landing page is a heading named by the instance, an introduction card an | ||
| 6 | // instance admin can edit in place, and then the repository grid beside a rail | ||
| 7 | // of what is happening and what the code is written in. The cards and the rail | ||
| 8 | // come from one read per repository, so listing a repository does not cost a | ||
| 9 | // second walk over its history. | ||
| 5 | // The landing page is the instance introduction, then the repository grid beside | ||
| 6 | // a rail of what is happening and what the code is written in. The introduction | ||
| 7 | // is read from the instance repository like every other customization, so there | ||
| 8 | // is no second copy of the instance name or an editor to keep in sync. The cards | ||
| 9 | // and the rail come from one read per repository, so listing a repository does | ||
| 10 | // not cost a second walk over its history. | ||
| 10 | 11 | ||
| 11 | 12 | import ( | |
| 12 | 13 | "html/template" | |
| @@ -51,15 +52,9 @@ type repoCard struct { | |||
| 51 | 52 | ||
| 52 | 53 | type homeData struct { | |
| 53 | 54 | Page | |
| 54 | // InstanceName is the landing page heading. | ||
| 55 | InstanceName string | ||
| 56 | // Intro is the rendered, sanitized introduction. Empty means the card has no | ||
| 57 | // body; an instance admin still sees it, with an invitation to write one. | ||
| 55 | // Intro is the rendered, sanitized introduction. Empty means there is no | ||
| 56 | // introduction to show. | ||
| 58 | 57 | Intro template.HTML | |
| 59 | // IntroText is the markdown shown in the edit form, and Editing says whether | ||
| 60 | // that form is open. Only an instance admin ever gets both. | ||
| 61 | IntroText string | ||
| 62 | Editing bool | ||
| 63 | 58 | // RepoReady reports that the instance repository has a usable snapshot, so | |
| 64 | 59 | // the page can be customized at all. | |
| 65 | 60 | RepoReady bool | |
| @@ -80,7 +75,7 @@ type instanceActivityData struct { | |||
| 80 | 75 | // repository rather than a table row. | |
| 81 | 76 | func (w *Web) IndexPage(rw http.ResponseWriter, r *http.Request) { | |
| 82 | 77 | query := strings.TrimSpace(r.URL.Query().Get("q")) | |
| 83 | data, err := w.homeView(r, query, r.URL.Query().Get("edit") == "1", nil, "") | ||
| 78 | data, err := w.homeView(r, query) | ||
| 84 | 79 | if err != nil { | |
| 85 | 80 | w.serverError(rw, r, "index unavailable: "+err.Error()) | |
| 86 | 81 | return | |
| @@ -88,24 +83,17 @@ func (w *Web) IndexPage(rw http.ResponseWriter, r *http.Request) { | |||
| 88 | 83 | w.render(rw, "index", data, 0) | |
| 89 | 84 | } | |
| 90 | 85 | ||
| 91 | // homeView builds the landing page. A save that failed passes its text back in | ||
| 92 | // submitted so the editor reopens with the paragraph still in it; msg is the | ||
| 93 | // error the page shows above it. | ||
| 94 | func (w *Web) homeView(r *http.Request, query string, wantEdit bool, submitted *string, msg string) (homeData, error) { | ||
| 86 | // homeView builds the landing page: the introduction, the repository grid, and | ||
| 87 | // the activity rail. | ||
| 88 | func (w *Web) homeView(r *http.Request, query string) (homeData, error) { | ||
| 95 | 89 | entries, err := w.visibleRepos(r, query) | |
| 96 | 90 | if err != nil { | |
| 97 | 91 | return homeData{}, err | |
| 98 | 92 | } | |
| 99 | 93 | page := w.page(r, "Repositories") | |
| 100 | 94 | page.Query = query | |
| 101 | page.Error = msg | ||
| 102 | // Only an instance admin can edit, so ?edit=1 from anybody else is ignored | ||
| 103 | // rather than refused: the page they asked for is the page they get. | ||
| 104 | editing := wantEdit && page.InstanceAdmin | ||
| 105 | 95 | data := homeData{ | |
| 106 | 96 | Page: page, | |
| 107 | InstanceName: w.instanceName(), | ||
| 108 | Editing: editing, | ||
| 109 | 97 | OpenRegistration: w.Cfg != nil && w.Cfg.OpenRegistration, | |
| 110 | 98 | } | |
| 111 | 99 | var snap *instancerepo.Snapshot | |
| @@ -113,31 +101,16 @@ func (w *Web) homeView(r *http.Request, query string, wantEdit bool, submitted * | |||
| 113 | 101 | snap = w.InstanceRepo.Current() | |
| 114 | 102 | } | |
| 115 | 103 | data.RepoReady = snap != nil | |
| 116 | var readme string | ||
| 117 | 104 | if snap != nil { | |
| 118 | readme = string(snap.Readme) | ||
| 119 | } | ||
| 120 | if !data.RepoReady { | ||
| 121 | editing = false | ||
| 122 | data.Editing = false | ||
| 123 | } | ||
| 124 | if editing { | ||
| 125 | // The editor always writes the public file. A private README is for | ||
| 126 | // signed-in readers, not a second editing surface. | ||
| 127 | data.IntroText = strings.TrimSpace(readme) | ||
| 128 | } else if snap != nil { | ||
| 129 | 105 | // A signed-in viewer sees the member-only README when one exists. | |
| 130 | shown := readme | ||
| 106 | shown := snap.Readme | ||
| 131 | 107 | if page.LoggedIn && len(snap.PrivateReadme) > 0 { | |
| 132 | shown = string(snap.PrivateReadme) | ||
| 108 | shown = snap.PrivateReadme | ||
| 133 | 109 | } | |
| 134 | if text := strings.TrimSpace(shown); text != "" { | ||
| 110 | if text := strings.TrimSpace(string(shown)); text != "" { | ||
| 135 | 111 | data.Intro = w.renderInstanceMarkdown(text) | |
| 136 | 112 | } | |
| 137 | 113 | } | |
| 138 | if editing && submitted != nil { | ||
| 139 | data.IntroText = *submitted | ||
| 140 | } | ||
| 141 | 114 | ||
| 142 | 115 | var events []activityEvent | |
| 143 | 116 | memo := memoOf(r) | |