~gitpocket/gitpocket

1

feat(profile): up to four links and forgejo style identicons

commit
d4538466f4c2e13696df5d5a2886e837a50ce4d8 browse tree
parent
381360f5ede020e5ff71e7fe9111c4a1b0d3cdd6
author
obscurejames <354576047+obscurejames@users.noreply.github.com>
signature
Verified ~james signed with a key registered by ~james
date
2026-11-05 22:26 UTC

8 files changed, +61 −7

flow_test.go modified

@@ -478,3 +478,20 @@ func TestNotifications(t *testing.T) {
478478 t.Errorf("private repository leaked through notifications: %+v", n)
479479 }
480480 }
481+
482+func TestProfileLinks(t *testing.T) {
483+ s, h := testServer(t)
484+ alice, _ := s.store.CreateUser("alice", "correct horse")
485+ c, _ := s.store.CreateSession(alice.ID)
486+ form := url.Values{"display": {"Alice"}, "website": {"example.org", "", "https://a.example/x", "http://b.example", "c.example", "d.example"}}
487+ if w := post(h, "/settings/profile", form, c); w.Code != http.StatusSeeOther {
488+ t.Fatalf("saving profile gave %d", w.Code)
489+ }
490+ links := s.store.Profile(alice.ID).Links()
491+ if len(links) != 4 || links[0] != "https://example.org" || links[2] != "http://b.example" {
492+ t.Errorf("links stored as %q", links)
493+ }
494+ if body := get(h, "/~alice", "").Body.String(); strings.Count(body, `rel="nofollow me"`) != 4 {
495+ t.Error("profile does not show four links")
496+ }
497+}

static/style.css modified

@@ -500,3 +500,5 @@ nav.site .bell .badge{position:absolute;top:-.35rem;left:.85rem;min-width:1rem;p
500500 .inbox .title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
501501 .inbox .who{font-size:.82rem;white-space:nowrap}
502502 @media(max-width:700px){.inbox li a{grid-template-columns:1.1rem minmax(0,1fr)}.inbox .who{grid-column:2}}
503+fieldset.links{border:0;padding:0;margin:0 0 .8rem;display:flex;flex-direction:column;gap:.35rem;min-width:0}
504+fieldset.links legend{padding:0;margin-bottom:.25rem}

store.go modified

@@ -696,10 +696,36 @@ func (s *Store) Profile(user int64) Profile {
696696 return p
697697 }
698698
699+const maxLinks = 4
700+
701+func (p Profile) Links() []string {
702+ if p.Website == "" {
703+ return nil
704+ }
705+ return strings.Split(p.Website, "\n")
706+}
707+
708+func (p Profile) LinkSlots() []string {
709+ links := p.Links()
710+ for len(links) < maxLinks {
711+ links = append(links, "")
712+ }
713+ return links
714+}
715+
699716 func (s *Store) UpdateProfile(user int64, p Profile) error {
700- if p.Website != "" && !strings.HasPrefix(p.Website, "https://") && !strings.HasPrefix(p.Website, "http://") {
701- p.Website = "https://" + p.Website
717+ var links []string
718+ for _, l := range strings.Split(p.Website, "\n") {
719+ l = strings.TrimSpace(l)
720+ if l == "" || len(links) == maxLinks {
721+ continue
722+ }
723+ if !strings.HasPrefix(l, "https://") && !strings.HasPrefix(l, "http://") {
724+ l = "https://" + l
725+ }
726+ links = append(links, l)
702727 }
728+ p.Website = strings.Join(links, "\n")
703729 _, err := s.db.Exec(`update users set display = ?, bio = ?, location = ?, workplace = ?, website = ? where id = ?`, p.Display, p.Bio, p.Location, p.Workplace, p.Website, user)
704730 return err
705731 }

templates/org.html modified

@@ -9,7 +9,7 @@
99 {{with .Data.Bio}}<div class="bio readme">{{.}}</div>{{end}}
1010 <ul class="facts">
1111 {{if $p.Location}}<li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 15s5-4.6 5-8.5A5 5 0 0 0 3 6.5C3 10.4 8 15 8 15z"/><circle cx="8" cy="6.5" r="1.8"/></svg>{{$p.Location}}</li>{{end}}
12-{{if $p.Website}}<li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6.8 9.2a3 3 0 0 0 4.2 0l2.2-2.2a3 3 0 0 0-4.2-4.2l-.9.9"/><path d="M9.2 6.8a3 3 0 0 0-4.2 0L2.8 9a3 3 0 0 0 4.2 4.2l.9-.9"/></svg><a href="{{$p.Website}}" rel="nofollow me">{{bare $p.Website}}</a></li>{{end}}
12+{{range $p.Links}}<li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6.8 9.2a3 3 0 0 0 4.2 0l2.2-2.2a3 3 0 0 0-4.2-4.2l-.9.9"/><path d="M9.2 6.8a3 3 0 0 0-4.2 0L2.8 9a3 3 0 0 0 4.2 4.2l.9-.9"/></svg><a href="{{.}}" rel="nofollow me">{{bare .}}</a></li>{{end}}
1313 <li title="{{date $o.Created}}"><svg viewBox="0 0 16 16" aria-hidden="true"><rect x="2" y="3" width="12" height="11" rx="1.5"/><path d="M2 6.5h12M5.5 1.5v3M10.5 1.5v3"/></svg>Since {{$o.Created.Format "January 2006"}}</li>
1414 </ul>
1515 </div>

templates/orgsettings.html modified

@@ -23,7 +23,7 @@
2323 </div>
2424 <div class="pair">
2525 <label>Location <input name="location" value="{{$p.Location}}" maxlength="100"></label>
26-<label>URL <input name="website" value="{{$p.Website}}" maxlength="200" placeholder="https://"></label>
26+<fieldset class="links"><legend>URLs</legend>{{range $i, $l := $p.LinkSlots}}<input name="website" value="{{$l}}" maxlength="200" placeholder="https://" aria-label="Link">{{end}}</fieldset>
2727 </div>
2828 <label>Bio <textarea name="bio" rows="5" maxlength="4000" placeholder="Markdown supported, paste or drop images to attach them" data-md>{{$p.Bio}}</textarea></label>
2929 <div class="actions"><button class="btn primary">Save changes ▸</button></div>

templates/settings.html modified

@@ -34,7 +34,7 @@
3434 <label>Location <input name="location" value="{{$p.Location}}" maxlength="100"></label>
3535 <label>Workplace <input name="workplace" value="{{$p.Workplace}}" maxlength="100"></label>
3636 </div>
37-<label>URL <input name="website" value="{{$p.Website}}" maxlength="200" placeholder="https://"></label>
37+<fieldset class="links"><legend>URLs</legend>{{range $i, $l := $p.LinkSlots}}<input name="website" value="{{$l}}" maxlength="200" placeholder="https://" aria-label="Link">{{end}}</fieldset>
3838 <label>Bio <textarea name="bio" rows="6" maxlength="4000" placeholder="Markdown supported, paste or drop images to attach them" data-md>{{$p.Bio}}</textarea></label>
3939 <div class="actions"><button class="btn primary">Save changes ▸</button></div>
4040 </form>

templates/user.html modified

@@ -13,7 +13,7 @@
1313 <ul class="facts">
1414 {{if $p.Location}}<li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 15s5-4.6 5-8.5A5 5 0 0 0 3 6.5C3 10.4 8 15 8 15z"/><circle cx="8" cy="6.5" r="1.8"/></svg>{{$p.Location}}</li>{{end}}
1515 {{if $p.Workplace}}<li><svg viewBox="0 0 16 16" aria-hidden="true"><rect x="2" y="5" width="12" height="9" rx="1.5"/><path d="M6 5V3.5A1 1 0 0 1 7 2.5h2a1 1 0 0 1 1 1V5"/></svg>{{with .Data.Workplace}}<a href="/~{{.}}">{{$p.Workplace}}</a>{{else}}{{$p.Workplace}}{{end}}</li>{{end}}
16-{{if $p.Website}}<li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6.8 9.2a3 3 0 0 0 4.2 0l2.2-2.2a3 3 0 0 0-4.2-4.2l-.9.9"/><path d="M9.2 6.8a3 3 0 0 0-4.2 0L2.8 9a3 3 0 0 0 4.2 4.2l.9-.9"/></svg><a href="{{$p.Website}}" rel="nofollow me">{{bare $p.Website}}</a></li>{{end}}
16+{{range $p.Links}}<li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6.8 9.2a3 3 0 0 0 4.2 0l2.2-2.2a3 3 0 0 0-4.2-4.2l-.9.9"/><path d="M9.2 6.8a3 3 0 0 0-4.2 0L2.8 9a3 3 0 0 0 4.2 4.2l.9-.9"/></svg><a href="{{.}}" rel="nofollow me">{{bare .}}</a></li>{{end}}
1717 <li title="{{date .Data.Owner.Created}}"><svg viewBox="0 0 16 16" aria-hidden="true"><rect x="2" y="3" width="12" height="11" rx="1.5"/><path d="M2 6.5h12M5.5 1.5v3M10.5 1.5v3"/></svg>Joined {{.Data.Owner.Created.Format "January 2006"}}</li>
1818 </ul>
1919 {{with .Data.Orgs}}<div class="orglist"><h4>Organisations</h4><div class="orgicons">{{range .}}<a href="/~{{.Name}}" title="{{with .Profile.Display}}{{.}}{{else}}~{{.Name}}{{end}}" aria-label="~{{.Name}}"><img src="/~{{.Name}}/avatar?v={{.Profile.Avatar}}" alt=""></a>{{end}}</div></div>{{end}}

web.go modified

@@ -716,6 +716,15 @@ func (s *Server) profile(w http.ResponseWriter, r *http.Request) {
716716 if u == nil {
717717 return
718718 }
719+ links := func(vals []string) string {
720+ var out []string
721+ for _, v := range vals {
722+ if v = strings.TrimSpace(v); v != "" && len(v) <= 200 && !strings.ContainsAny(v, "\r\n") {
723+ out = append(out, v)
724+ }
725+ }
726+ return strings.Join(out, "\n")
727+ }
719728 field := func(name string, max int) string {
720729 v := strings.TrimSpace(r.FormValue(name))
721730 if len(v) > max {
@@ -723,7 +732,7 @@ func (s *Server) profile(w http.ResponseWriter, r *http.Request) {
723732 }
724733 return v
725734 }
726- p := Profile{Display: field("display", 100), Bio: field("bio", 4000), Location: field("location", 100), Workplace: field("workplace", 100), Website: field("website", 200)}
735+ p := Profile{Display: field("display", 100), Bio: field("bio", 4000), Location: field("location", 100), Workplace: field("workplace", 100), Website: links(r.Form["website"])}
727736 if err := s.store.UpdateProfile(u.ID, p); err != nil {
728737 http.Redirect(w, r, back+"?error="+template.URLQueryEscaper(err.Error()), http.StatusSeeOther)
729738 return