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/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

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

Add repository search, list pagination, and theme/a11y polish

5cab7b18548e5263c01549cb61415fd7777d1758 · Keenan Nemetz · 15d ago · parent 0b26547e

Add repository search, list pagination, and theme/a11y polish

The chrome and the index page search repository names and descriptions after the same read filter, so a private repository cannot appear. Issues and changes paginate at 25 rows with filters and HTMX navigation preserved.

Polish from the UI plan: a light token block follows prefers-color-scheme, a skip link and focus-visible outlines help keyboard users, panels scroll horizontally on narrow screens, and the empty index/search states explain what is visible. Also fixes a flaky key assertion in the admin users test.

server/web/issues.go +31

@@ -4,8 +4,10 @@ import (
4 4 "bytes"
5 5 "html/template"
6 6 "net/http"
7 "net/url"
7 8 "regexp"
8 9 "sort"
10 "strconv"
9 11 "strings"
10 12
11 13 "github.com/microcosm-cc/bluemonday"
@@ -112,6 +114,13 @@ func (w *Web) Issues(rw http.ResponseWriter, r *http.Request) {
112 114 }
113 115 return rows[i].ID > rows[j].ID
114 116 })
117 page, _ := strconv.Atoi(r.URL.Query().Get("page"))
118 pg := newPager(len(rows), 25, page, func(n int) string {
119 return issueListURL(m.Name, state, filter.Query, filter.Label, filter.Assignee, n)
120 })
121 if pg.Total > 0 {
122 rows = rows[(pg.Page-1)*25 : pg.To]
123 }
115 124 labels, err := w.Index.Labels(m.ID)
116 125 if err != nil {
117 126 labels = nil
@@ -134,6 +143,7 @@ func (w *Web) Issues(rw http.ResponseWriter, r *http.Request) {
134 143 Closed int
135 144 Filtered bool
136 145 CanDiscuss bool
146 Pager pager
137 147 }{
138 148 Page: w.page(r, m.Name+" · issues"),
139 149 Name: m.Name,
@@ -147,6 +157,7 @@ func (w *Web) Issues(rw http.ResponseWriter, r *http.Request) {
147 157 Closed: len(all) - open,
148 158 Filtered: filter.Label != "" || filter.Assignee != "" || filter.Query != "" || (state != "open" && len(all) > 0),
149 159 CanDiscuss: acl != nil && w.canDiscuss(r, acl),
160 Pager: pg,
150 161 }
151 162 if htmxRequest(r) {
152 163 w.render(rw, "issue_list", view, 0)
@@ -155,6 +166,26 @@ func (w *Web) Issues(rw http.ResponseWriter, r *http.Request) {
155 166 w.render(rw, "issues", view, 0)
156 167 }
157 168
169 func issueListURL(name, state, query, label, assignee string, page int) string {
170 v := url.Values{}
171 if state != "" {
172 v.Set("state", state)
173 }
174 if query != "" {
175 v.Set("q", query)
176 }
177 if label != "" {
178 v.Set("label", label)
179 }
180 if assignee != "" {
181 v.Set("assignee", assignee)
182 }
183 if page > 1 {
184 v.Set("page", strconv.Itoa(page))
185 }
186 return "/r/" + name + "/issues?" + v.Encode()
187 }
188
158 189 // issueCommentView is a comment with its body already rendered through the
159 190 // markdown boundary.
160 191 type issueCommentView struct {