~gitpocket/gitpocket

1

refactor(profile): pin from repository settings

commit
64de526a72f53b0b288c7048f66102747da26571 browse tree
parent
4c63af699375e5e1cc2c82b1488d28d33c701cb8
author
obscurejames <354576047+obscurejames@users.noreply.github.com>
signature
Verified ~james signed with a key registered by ~james
date
2026-10-26 23:00 UTC

7 files changed, +26 −34

flow_test.go modified

@@ -1,7 +1,6 @@
11 package main
22
33 import (
4- "fmt"
54 "net/http"
65 "net/http/httptest"
76 "net/url"
@@ -281,16 +280,20 @@ func TestPins(t *testing.T) {
281280 }
282281 own, _ := s.store.CreateSession(alice.ID)
283282 other, _ := s.store.CreateSession(mallory.ID)
283+ names := map[int64]string{}
284+ for i, id := range ids {
285+ names[id] = string(rune('a' + i))
286+ }
284287 pin := func(id int64, cookie string) *httptest.ResponseRecorder {
285- return post(h, "/~alice/repositories", url.Values{"repo": {fmt.Sprint(id)}, "action": {"pin"}}, cookie)
288+ return post(h, "/~alice/"+names[id]+"/settings", url.Values{"visibility": {"public"}, "pinned": {"on"}}, cookie)
286289 }
287- if w := pin(ids[0], other); w.Code != 404 {
288- t.Errorf("someone else pinned alice's repo: %d", w.Code)
290+ if pin(ids[0], other); len(s.store.Pins(alice.ID)) != 0 {
291+ t.Error("someone else pinned alice's repo")
289292 }
290293 pin(ids[2], own)
291294 pin(ids[0], own)
292295 body := get(h, "/~alice", "").Body.String()
293- if !strings.Contains(body, "Pinned") || strings.Index(body, `href="/~alice/c"`) > strings.Index(body, `href="/~alice/a"`) || strings.Contains(body, `href="/~alice/b"`) {
296+ if !strings.Contains(body, `href="/~alice/c"`) || strings.Index(body, `href="/~alice/c"`) > strings.Index(body, `href="/~alice/a"`) || strings.Contains(body, `href="/~alice/b"`) {
294297 t.Error("profile does not show exactly the pinned repos in pin order")
295298 }
296299 for _, id := range ids[1:] {
@@ -299,6 +302,10 @@ func TestPins(t *testing.T) {
299302 if n := len(s.store.Pins(alice.ID)); n != maxPins {
300303 t.Errorf("%d pins, want the cap of %d", n, maxPins)
301304 }
305+ post(h, "/~alice/a/settings", url.Values{"visibility": {"public"}}, own)
306+ if s.store.Pins(alice.ID)[ids[0]] != 0 {
307+ t.Error("unticking the box did not unpin")
308+ }
302309 if w := get(h, "/~alice/repositories", ""); w.Code != 200 || !strings.Contains(w.Body.String(), `href="/~alice/g"`) {
303310 t.Error("repositories page does not list every repo")
304311 }

static/style.css modified

@@ -187,7 +187,7 @@ input[type=file]{border:0;padding:0}
187187 .twocol{display:grid;grid-template-columns:1fr 1fr;gap:2rem;margin-bottom:2rem}
188188 .twocol label{display:flex;flex-direction:column;gap:.25rem;margin-bottom:.8rem}
189189 .twocol .legend{display:block;margin-bottom:.4rem}
190-.twocol label.radio,.modes label.radio{flex-direction:row;flex-wrap:wrap;align-items:center;gap:.4rem;margin-bottom:.6rem}
190+.twocol label.radio,.twocol label.check,.modes label.radio{flex-direction:row;flex-wrap:wrap;align-items:center;gap:.4rem;margin-bottom:.6rem}
191191 .twocol label.radio small,.modes label.radio small{flex-basis:100%;color:var(--muted);padding-left:1.4rem}
192192 .modes input[type=radio]{width:auto;margin:0}
193193 h3.plain{border:0;font-size:1.25rem}
@@ -436,7 +436,3 @@ pre.rangediff,pre.body{white-space:pre-wrap;overflow-wrap:anywhere}
436436 form.inline{display:inline-flex;align-items:center;gap:.4rem;margin:0 0 .6rem}
437437 table.keys form.inline{margin:0}
438438 input.rank{width:4rem}
439-.pinhead{display:flex;align-items:baseline;justify-content:space-between;font-weight:400;color:var(--muted);margin:1rem 0 .5rem}
440-.pinhead a{font-size:.9rem;color:var(--accent)}
441-.event h4 .pinform{margin:0 0 0 auto}
442-.event h4 .starcount~.pinform{margin-left:.6rem}

templates/org.html modified

@@ -18,7 +18,7 @@
1818 <div class="columns orgbody">
1919 <div class="main">
2020 {{with .Data.Activity}}<section class="activity"><div class="heathead"><p class="muted small">{{.Total}} commit{{if ne .Total 1}}s{{end}} across the organisation {{if .Year}}in {{.Year}}{{else}}in the past 12 months{{end}}</p><nav class="years"><a href="?"{{if not .Year}} class="active"{{end}}>past 12 months</a>{{range .Years}}<a href="?year={{.}}"{{if eq . $.Data.Activity.Year}} class="active"{{end}}>{{.}}</a>{{end}}</nav></div>{{.Grid}}<p class="legend muted small">less <i class="h0"></i><i class="h1"></i><i class="h2"></i><i class="h3"></i><i class="h4"></i> more</p></section>{{end}}
21-{{if .Data.Repos}}<h4 class="pinhead">{{if .Data.Pinned}}Pinned{{else}}Recently updated{{end}}<a href="/~{{.Data.Owner.Name}}/repositories">all repositories ▸</a></h4><div class="events">{{range .Data.Repos}}{{template "repoitem" .}}{{end}}</div>{{else}}<p class="info">This organisation does not have any repositories.</p>{{end}}
21+{{if .Data.Repos}}<div class="events">{{range .Data.Repos}}{{template "repoitem" .}}{{end}}</div>{{else}}<p class="info">This organisation does not have any repositories.</p>{{end}}
2222 </div>
2323 <aside class="people">
2424 <h4>People <span class="count">{{len .Data.Members}}</span></h4>

templates/reposettings.html modified

@@ -5,6 +5,7 @@
55 <div>
66 <label>Description <input name="description" value="{{$p.Repo.Description}}" maxlength="200"></label>
77 <label>Tags <input name="tags" value="{{range $i, $t := $p.Repo.Tags}}{{if $i}}, {{end}}{{$t}}{{end}}" placeholder="Up to three comma-separated topics"></label>
8+<label class="check"><input type="checkbox" name="pinned"{{if .Data.Pinned}} checked{{end}}> Pin to profile</label>
89 <div class="actions"><button class="btn primary">Save ▸</button></div>
910 </div>
1011 <div>

templates/repositories.html modified

@@ -4,6 +4,5 @@
44 <h2>~{{$d.Owner.Name}}</h2>
55 <nav class="tabs small"><a href="/~{{$d.Owner.Name}}">about</a><a class="active" href="/~{{$d.Owner.Name}}/repositories">repositories{{if $d.Repos}} <span class="count">{{len $d.Repos}}</span>{{end}}</a>{{if not $d.Org}}<a href="/~{{$d.Owner.Name}}/stars">stars{{if $d.Count}} <span class="count">{{$d.Count}}</span>{{end}}</a>{{end}}<a href="/~{{$d.Owner.Name}}/feed">feed</a></nav>
66 </div>
7-{{if $d.Member}}<p class="muted small">Pinned repositories show on the profile page, up to {{$d.Max}} of them in the order they were pinned.</p>{{end}}
8-{{if $d.Repos}}<div class="events">{{range $d.Repos}}{{$pin := index $d.Pins .ID}}<div class="event"><h4><a href="/{{.Path}}">{{.Name}}</a>{{if .Private}} <span class="tag">private</span>{{else if .Unlisted}} <span class="tag">unlisted</span>{{end}}{{if $pin}} <span class="tag">pinned</span>{{end}}{{if .Stars}}<span class="starcount" title="{{.Stars}} stars">☆ {{.Stars}}</span>{{end}}{{if $d.Member}}<form method="post" action="/~{{$d.Owner.Name}}/repositories" class="pinform"><input type="hidden" name="repo" value="{{.ID}}"><button class="btn small" name="action" value="{{if $pin}}unpin{{else}}pin{{end}}">{{if $pin}}Unpin{{else}}Pin{{end}}</button></form>{{end}}</h4>{{if .Description}}<p>{{.Description}}</p>{{end}}{{if .Tags}}<p class="tags">{{range .Tags}}<a href="/search?q=tag:{{.}}">#{{.}}</a> {{end}}</p>{{end}}</div>{{end}}</div>{{else}}<p class="info">No repositories yet.</p>{{end}}
7+{{if $d.Repos}}<div class="events">{{range $d.Repos}}{{$pin := index $d.Pins .ID}}<div class="event"><h4><a href="/{{.Path}}">{{.Name}}</a>{{if .Private}} <span class="tag">private</span>{{else if .Unlisted}} <span class="tag">unlisted</span>{{end}}{{if $pin}} <span class="tag">pinned</span>{{end}}{{if .Stars}}<span class="starcount" title="{{.Stars}} stars">☆ {{.Stars}}</span>{{end}}</h4>{{if .Description}}<p>{{.Description}}</p>{{end}}{{if .Tags}}<p class="tags">{{range .Tags}}<a href="/search?q=tag:{{.}}">#{{.}}</a> {{end}}</p>{{end}}</div>{{end}}</div>{{else}}<p class="info">No repositories yet.</p>{{end}}
98 {{end}}

templates/user.html modified

@@ -21,7 +21,7 @@
2121 </aside>
2222 <div class="main">
2323 {{with .Data.Activity}}<section class="activity"><div class="heathead"><p class="muted small">{{.Total}} commit{{if ne .Total 1}}s{{end}} {{if .Year}}in {{.Year}}{{else}}in the past 12 months{{end}}</p><nav class="years"><a href="?"{{if not .Year}} class="active"{{end}}>past 12 months</a>{{range .Years}}<a href="?year={{.}}"{{if eq . $.Data.Activity.Year}} class="active"{{end}}>{{.}}</a>{{end}}</nav></div>{{.Grid}}<p class="legend muted small">less <i class="h0"></i><i class="h1"></i><i class="h2"></i><i class="h3"></i><i class="h4"></i> more</p></section>{{end}}
24-{{if .Data.Repos}}<h4 class="pinhead">{{if .Data.Pinned}}Pinned{{else}}Recently updated{{end}}<a href="/~{{.Data.Owner.Name}}/repositories">all repositories ▸</a></h4><div class="events">{{range .Data.Repos}}{{template "repoitem" .}}{{end}}</div>{{else}}<p class="info">This user does not have any repositories.</p>{{end}}
24+{{if .Data.Repos}}<div class="events">{{range .Data.Repos}}{{template "repoitem" .}}{{end}}</div>{{else}}<p class="info">This user does not have any repositories.</p>{{end}}
2525 </div>
2626 </div>
2727 {{end}}

web.go modified

@@ -868,7 +868,7 @@ func (s *Server) route(w http.ResponseWriter, r *http.Request) {
868868 s.orgSettings(w, r, owner, name)
869869 return
870870 }
871- if name == "repositories" && len(parts) == 2 {
871+ if name == "repositories" && len(parts) == 2 && r.Method == "GET" {
872872 s.repositories(w, r, owner, user)
873873 return
874874 }
@@ -1382,7 +1382,7 @@ func (s *Server) repoSettings(w http.ResponseWriter, r *http.Request, repo Repo)
13821382 rp.Tab = "settings"
13831383 collabs, _ := s.store.Collaborators(repo.ID)
13841384 labels, _ := s.store.Labels(repo.ID)
1385- s.render(w, r, "reposettings", page{Title: repo.Path() + " settings", Error: r.URL.Query().Get("error"), Data: map[string]any{"Page": rp, "Collaborators": collabs, "Labels": labels, "Protections": s.store.Protections(repo.ID)}})
1385+ s.render(w, r, "reposettings", page{Title: repo.Path() + " settings", Error: r.URL.Query().Get("error"), Data: map[string]any{"Page": rp, "Collaborators": collabs, "Labels": labels, "Protections": s.store.Protections(repo.ID), "Pinned": s.store.Pins(repo.Owner.ID)[repo.ID] > 0}})
13861386 }
13871387
13881388 func (s *Server) collaborators(w http.ResponseWriter, r *http.Request, repo Repo) {
@@ -1413,7 +1413,11 @@ func (s *Server) updateRepo(w http.ResponseWriter, r *http.Request, repo Repo) {
14131413 s.fail(w, r, 403, "Not your repository")
14141414 return
14151415 }
1416- if err := s.store.UpdateRepo(repo.ID, strings.TrimSpace(r.FormValue("description")), visibility(r.FormValue("visibility")), r.FormValue("tags")); err != nil {
1416+ err := s.store.UpdateRepo(repo.ID, strings.TrimSpace(r.FormValue("description")), visibility(r.FormValue("visibility")), r.FormValue("tags"))
1417+ if pin := r.FormValue("pinned") != ""; err == nil && pin != (s.store.Pins(repo.Owner.ID)[repo.ID] > 0) {
1418+ err = s.store.Pin(repo.Owner.ID, repo.ID, pin)
1419+ }
1420+ if err != nil {
14171421 rp.Tab = "settings"
14181422 w.WriteHeader(400)
14191423 s.render(w, r, "reposettings", page{Title: repo.Path() + " settings", Error: err.Error(), Data: map[string]any{"Page": rp}})
@@ -1601,27 +1605,12 @@ func pinnedRepos(repos []Repo, pins map[int64]int) []Repo {
16011605
16021606 func (s *Server) repositories(w http.ResponseWriter, r *http.Request, owner User, user *User) {
16031607 member := user != nil && (user.ID == owner.ID || s.store.IsMember(owner.ID, user.ID))
1604- if r.Method == "POST" {
1605- if !member {
1606- s.fail(w, r, 404, "Not found")
1607- return
1608- }
1609- id, _ := strconv.ParseInt(r.FormValue("repo"), 10, 64)
1610- if err := s.store.Pin(owner.ID, id, r.FormValue("action") == "pin"); err != nil {
1611- http.Redirect(w, r, "/~"+owner.Name+"/repositories?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
1612- return
1613- }
1614- http.Redirect(w, r, "/~"+owner.Name+"/repositories", http.StatusSeeOther)
1615- return
1616- }
16171608 repos, err := s.store.ReposByOwner(owner.ID, member)
16181609 if err != nil {
16191610 s.fail(w, r, 500, err.Error())
16201611 return
16211612 }
1622- repos = s.byUpdate(repos)
16231613 starred, _ := s.store.StarredRepos(owner.ID)
1624- pins := s.store.Pins(owner.ID)
1625- data := map[string]any{"Owner": owner, "Repos": repos, "Pins": pins, "Member": member, "Count": len(starred), "Org": s.store.IsOrg(owner.ID), "Max": maxPins}
1626- s.render(w, r, "repositories", page{Title: "~" + owner.Name + " repositories", User: user, Error: r.URL.Query().Get("error"), Data: data})
1614+ data := map[string]any{"Owner": owner, "Repos": s.byUpdate(repos), "Pins": s.store.Pins(owner.ID), "Count": len(starred), "Org": s.store.IsOrg(owner.ID)}
1615+ s.render(w, r, "repositories", page{Title: "~" + owner.Name + " repositories", User: user, Data: data})
16271616 }