Monorepo for Tangled tangled.org
757
fork

Configure Feed

Select the types of activity you want to include in your feed.

appview/pages: add vouch hats

Signed-off-by: oppiliappan <me@oppi.li>

authored by

oppiliappan and committed by
Tangled
b7df865e c21e94a9

+618 -108
+43 -17
appview/issues/issues.go
··· 127 127 return 128 128 } 129 129 130 + vouchRelationships := make(map[syntax.DID]*models.VouchRelationship) 131 + if user != nil { 132 + participants := issue.Participants() 133 + vouchRelationships, err = db.GetVouchRelationshipsBatch(rp.db, syntax.DID(user.Did), participants) 134 + if err != nil { 135 + l.Error("failed to fetch vouch relationships", "err", err) 136 + } 137 + } 138 + 130 139 defs := make(map[string]*models.LabelDefinition) 131 140 for _, l := range labelDefs { 132 141 defs[l.AtUri().String()] = &l 133 142 } 134 143 135 - rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{ 136 - LoggedInUser: user, 137 - RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 138 - Issue: issue, 139 - CommentList: issue.CommentList(), 140 - Backlinks: backlinks, 141 - Reactions: reactionMap, 142 - UserReacted: userReactions, 143 - LabelDefs: defs, 144 + err = rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{ 145 + LoggedInUser: user, 146 + RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 147 + Issue: issue, 148 + CommentList: issue.CommentList(), 149 + Backlinks: backlinks, 150 + Reactions: reactionMap, 151 + UserReacted: userReactions, 152 + LabelDefs: defs, 153 + VouchRelationships: vouchRelationships, 144 154 }) 155 + if err != nil { 156 + l.Error("failed to render issue", "err", err) 157 + } 145 158 } 146 159 147 160 func (rp *Issues) EditIssue(w http.ResponseWriter, r *http.Request) { ··· 978 991 } 979 992 } 980 993 994 + vouchRelationships := make(map[syntax.DID]*models.VouchRelationship) 995 + if user != nil { 996 + dids := make([]syntax.DID, len(issues)) 997 + for i, u := range issues { 998 + dids[i] = syntax.DID(u.Did) 999 + } 1000 + vouchRelationships, err = db.GetVouchRelationshipsBatch(rp.db, syntax.DID(user.Did), dids) 1001 + if err != nil { 1002 + l.Error("failed to fetch vouch relationships", "err", err) 1003 + } 1004 + } 1005 + 981 1006 rp.pages.RepoIssues(w, pages.RepoIssuesParams{ 982 - LoggedInUser: rp.oauth.GetMultiAccountUser(r), 983 - RepoInfo: repoInfo, 984 - Issues: issues, 985 - IssueCount: totalIssues, 986 - LabelDefs: defs, 987 - FilterState: filterState, 988 - FilterQuery: query.String(), 989 - Page: page, 1007 + LoggedInUser: rp.oauth.GetMultiAccountUser(r), 1008 + RepoInfo: repoInfo, 1009 + Issues: issues, 1010 + IssueCount: totalIssues, 1011 + LabelDefs: defs, 1012 + FilterState: filterState, 1013 + FilterQuery: query.String(), 1014 + Page: page, 1015 + VouchRelationships: vouchRelationships, 990 1016 }) 991 1017 } 992 1018
+6 -6
appview/models/issue.go
··· 135 135 return listing 136 136 } 137 137 138 - func (i *Issue) Participants() []string { 139 - participantSet := make(map[string]struct{}) 140 - participants := []string{} 138 + func (i *Issue) Participants() []syntax.DID { 139 + participantSet := make(map[syntax.DID]struct{}) 140 + participants := []syntax.DID{} 141 141 142 - addParticipant := func(did string) { 142 + addParticipant := func(did syntax.DID) { 143 143 if _, exists := participantSet[did]; !exists { 144 144 participantSet[did] = struct{}{} 145 145 participants = append(participants, did) 146 146 } 147 147 } 148 148 149 - addParticipant(i.Did) 149 + addParticipant(syntax.DID(i.Did)) 150 150 151 151 for _, c := range i.Comments { 152 - addParticipant(c.Did) 152 + addParticipant(syntax.DID(c.Did)) 153 153 } 154 154 155 155 return participants
+6 -6
appview/models/pull.go
··· 386 386 return false 387 387 } 388 388 389 - func (p *Pull) Participants() []string { 390 - participantSet := make(map[string]struct{}) 391 - participants := []string{} 389 + func (p *Pull) Participants() []syntax.DID { 390 + participantSet := make(map[syntax.DID]struct{}) 391 + participants := []syntax.DID{} 392 392 393 - addParticipant := func(did string) { 393 + addParticipant := func(did syntax.DID) { 394 394 if _, exists := participantSet[did]; !exists { 395 395 participantSet[did] = struct{}{} 396 396 participants = append(participants, did) 397 397 } 398 398 } 399 399 400 - addParticipant(p.OwnerDid) 400 + addParticipant(syntax.DID(p.OwnerDid)) 401 401 402 402 for _, s := range p.Submissions { 403 403 for _, sp := range s.Participants() { 404 - addParticipant(sp) 404 + addParticipant(syntax.DID(sp)) 405 405 } 406 406 } 407 407
+43
appview/models/vouch.go
··· 102 102 return nil 103 103 } 104 104 105 + func (vr *VouchRelationship) IsIndirectVouch() bool { 106 + if vr.IsDirectVouch() || vr.IsDirectDenounce() { 107 + return false 108 + } 109 + for _, v := range vr.NetworkVouches { 110 + if v.Did != vr.ViewerDid && v.Kind == VouchKindVouch { 111 + return true 112 + } 113 + } 114 + return false 115 + } 116 + 117 + func (vr *VouchRelationship) IsIndirectDenounce() bool { 118 + if vr.IsDirectVouch() || vr.IsDirectDenounce() { 119 + return false 120 + } 121 + for _, v := range vr.NetworkVouches { 122 + if v.Did != vr.ViewerDid && v.Kind == VouchKindDenounce { 123 + return true 124 + } 125 + } 126 + return false 127 + } 128 + 129 + func (vr *VouchRelationship) IsMixed() bool { 130 + if vr.IsDirectVouch() || vr.IsDirectDenounce() { 131 + return false 132 + } 133 + hasVouch := false 134 + hasDenounce := false 135 + for _, v := range vr.NetworkVouches { 136 + if v.Did != vr.ViewerDid { 137 + switch v.Kind { 138 + case VouchKindVouch: 139 + hasVouch = true 140 + case VouchKindDenounce: 141 + hasDenounce = true 142 + } 143 + } 144 + } 145 + return hasVouch && hasDenounce 146 + } 147 + 105 148 func (vr *VouchRelationship) VouchStrength() int { 106 149 count := 0 107 150 for _, v := range vr.NetworkVouches {
+2 -2
appview/notify/db/db.go
··· 305 305 // - remove those already mentioned 306 306 recipients := sets.Singleton(syntax.DID(repo.Did)) 307 307 for _, p := range pull.Participants() { 308 - recipients.Insert(syntax.DID(p)) 308 + recipients.Insert(p) 309 309 } 310 310 for _, m := range mentions { 311 311 recipients.Remove(m) ··· 439 439 recipients.Insert(c.SubjectDid) 440 440 } 441 441 for _, p := range pull.Participants() { 442 - recipients.Insert(syntax.DID(p)) 442 + recipients.Insert(p) 443 443 } 444 444 445 445 entityType := "pull"
+2
appview/pages/funcmap.go
··· 24 24 "github.com/alecthomas/chroma/v2/lexers" 25 25 "github.com/alecthomas/chroma/v2/styles" 26 26 "github.com/dustin/go-humanize" 27 + "github.com/dustin/go-humanize/english" 27 28 "github.com/go-enry/go-enry/v2" 28 29 "github.com/yuin/goldmark" 29 30 emoji "github.com/yuin/goldmark-emoji" ··· 177 178 return s 178 179 }, 179 180 "commaFmt": humanize.Comma, 181 + "plural": english.Plural, 180 182 "relTimeFmt": humanize.Time, 181 183 "shortRelTimeFmt": func(t time.Time) string { 182 184 return humanize.CustomRelTime(t, time.Now(), "", "", []humanize.RelTimeMagnitude{
+50 -30
appview/pages/pages.go
··· 624 624 } 625 625 626 626 type ProfileCard struct { 627 - UserDid string 628 - HasProfile bool 629 - FollowStatus models.FollowStatus 630 - Punchcard *models.Punchcard 631 - Profile *models.Profile 632 - Stats ProfileStats 633 - Active string 627 + UserDid string 628 + HasProfile bool 629 + FollowStatus models.FollowStatus 630 + VouchRelationship *models.VouchRelationship 631 + Punchcard *models.Punchcard 632 + Profile *models.Profile 633 + Stats ProfileStats 634 + Active string 634 635 } 635 636 636 637 type ProfileStats struct { ··· 647 648 {"repos", "repos", "book-marked", p.Stats.RepoCount}, 648 649 {"starred", "starred", "star", p.Stats.StarredCount}, 649 650 {"strings", "strings", "line-squiggle", p.Stats.StringCount}, 651 + {"vouches", "vouches", "shield", nil}, 650 652 } 651 653 652 654 return tabs ··· 706 708 func (p *Pages) ProfileStrings(w io.Writer, params ProfileStringsParams) error { 707 709 params.Active = "strings" 708 710 return p.executeProfile("user/strings", w, params) 711 + } 712 + 713 + type ProfileVouchesParams struct { 714 + LoggedInUser *oauth.MultiAccountUser 715 + Vouches []models.Vouch 716 + Suggestions []models.VouchSuggestion 717 + Card *ProfileCard 718 + Page pagination.Page 719 + Active string 720 + } 721 + 722 + func (p *Pages) ProfileVouches(w io.Writer, params ProfileVouchesParams) error { 723 + params.Active = "vouches" 724 + return p.executeProfile("user/vouches", w, params) 709 725 } 710 726 711 727 type FollowCard struct { ··· 1125 1141 } 1126 1142 1127 1143 type RepoIssuesParams struct { 1128 - LoggedInUser *oauth.MultiAccountUser 1129 - RepoInfo repoinfo.RepoInfo 1130 - Active string 1131 - Issues []models.Issue 1132 - IssueCount int 1133 - LabelDefs map[string]*models.LabelDefinition 1134 - Page pagination.Page 1135 - FilterState string 1136 - FilterQuery string 1144 + LoggedInUser *oauth.MultiAccountUser 1145 + RepoInfo repoinfo.RepoInfo 1146 + Active string 1147 + Issues []models.Issue 1148 + IssueCount int 1149 + LabelDefs map[string]*models.LabelDefinition 1150 + Page pagination.Page 1151 + FilterState string 1152 + FilterQuery string 1153 + VouchRelationships map[syntax.DID]*models.VouchRelationship 1137 1154 } 1138 1155 1139 1156 func (p *Pages) RepoIssues(w io.Writer, params RepoIssuesParams) error { ··· 1150 1167 Backlinks []models.RichReferenceLink 1151 1168 LabelDefs map[string]*models.LabelDefinition 1152 1169 1153 - Reactions map[models.ReactionKind]models.ReactionDisplayData 1154 - UserReacted map[models.ReactionKind]bool 1170 + Reactions map[models.ReactionKind]models.ReactionDisplayData 1171 + UserReacted map[models.ReactionKind]bool 1172 + VouchRelationships map[syntax.DID]*models.VouchRelationship 1155 1173 } 1156 1174 1157 1175 func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error { ··· 1259 1277 } 1260 1278 1261 1279 type RepoPullsParams struct { 1262 - LoggedInUser *oauth.MultiAccountUser 1263 - RepoInfo repoinfo.RepoInfo 1264 - Pulls []*models.Pull 1265 - Active string 1266 - FilterState string 1267 - FilterQuery string 1268 - Stacks []models.Stack 1269 - Pipelines map[string]models.Pipeline 1270 - LabelDefs map[string]*models.LabelDefinition 1271 - Page pagination.Page 1272 - PullCount int 1280 + LoggedInUser *oauth.MultiAccountUser 1281 + RepoInfo repoinfo.RepoInfo 1282 + Pulls []*models.Pull 1283 + Active string 1284 + FilterState string 1285 + FilterQuery string 1286 + Stacks []models.Stack 1287 + Pipelines map[string]models.Pipeline 1288 + LabelDefs map[string]*models.LabelDefinition 1289 + Page pagination.Page 1290 + PullCount int 1291 + VouchRelationships map[syntax.DID]*models.VouchRelationship 1273 1292 } 1274 1293 1275 1294 func (p *Pages) RepoPulls(w io.Writer, params RepoPullsParams) error { ··· 1314 1333 Reactions map[models.ReactionKind]models.ReactionDisplayData 1315 1334 UserReacted map[models.ReactionKind]bool 1316 1335 1317 - LabelDefs map[string]*models.LabelDefinition 1336 + LabelDefs map[string]*models.LabelDefinition 1337 + VouchRelationships map[syntax.DID]*models.VouchRelationship 1318 1338 } 1319 1339 1320 1340 func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error {
+5
appview/pages/templates/fragments/icons/shield-direct-denounce.html
··· 1 + {{ define "fragments/icons/shield-direct-denounce" }} 2 + <svg class="{{.}}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 3 + <path d="M20 13C20 18 16.5 20.5 12.34 21.95C12.1222 22.0238 11.8855 22.0202 11.67 21.94C7.5 20.5 4 18 4 13V5.99996C4 5.73474 4.10536 5.48039 4.29289 5.29285C4.48043 5.10532 4.73478 4.99996 5 4.99996C7 4.99996 9.5 3.79996 11.24 2.27996C11.4519 2.09896 11.7214 1.99951 12 1.99951C12.2786 1.99951 12.5481 2.09896 12.76 2.27996C14.51 3.80996 17 4.99996 19 4.99996C19.2652 4.99996 19.5196 5.10532 19.7071 5.29285C19.8946 5.48039 20 5.73474 20 5.99996V13Z" fill="#EF4444" stroke="#DC2626" stroke-linecap="round" stroke-linejoin="round"/> 4 + </svg> 5 + {{ end }}
+5
appview/pages/templates/fragments/icons/shield-direct-vouch.html
··· 1 + {{ define "fragments/icons/shield-direct-vouch" }} 2 + <svg class="{{.}}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 3 + <path d="M20 13C20 18 16.5 20.5 12.34 21.95C12.1222 22.0238 11.8855 22.0202 11.67 21.94C7.5 20.5 4 18 4 13V5.99996C4 5.73474 4.10536 5.48039 4.29289 5.29285C4.48043 5.10532 4.73478 4.99996 5 4.99996C7 4.99996 9.5 3.79996 11.24 2.27996C11.4519 2.09896 11.7214 1.99951 12 1.99951C12.2786 1.99951 12.5481 2.09896 12.76 2.27996C14.51 3.80996 17 4.99996 19 4.99996C19.2652 4.99996 19.5196 5.10532 19.7071 5.29285C19.8946 5.48039 20 5.73474 20 5.99996V13Z" fill="#22C55E" stroke="#16A34A" stroke-linecap="round" stroke-linejoin="round"/> 4 + </svg> 5 + {{ end }}
+5
appview/pages/templates/fragments/icons/shield-empty.html
··· 1 + {{ define "fragments/icons/shield-empty" }} 2 + <svg class="{{.}}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 3 + <path d="M20 13C20 18 16.5 20.5 12.34 21.95C12.1222 22.0238 11.8855 22.0202 11.67 21.94C7.5 20.5 4 18 4 13V5.99996C4 5.73474 4.10536 5.48039 4.29289 5.29285C4.48043 5.10532 4.73478 4.99996 5 4.99996C7 4.99996 9.5 3.79996 11.24 2.27996C11.4519 2.09896 11.7214 1.99951 12 1.99951C12.2786 1.99951 12.5481 2.09896 12.76 2.27996C14.51 3.80996 17 4.99996 19 4.99996C19.2652 4.99996 19.5196 5.10532 19.7071 5.29285C19.8946 5.48039 20 5.73474 20 5.99996V13Z" fill="#9CA3AF" stroke="#6B7280" stroke-linecap="round" stroke-linejoin="round"/> 4 + </svg> 5 + {{ end }}
+7
appview/pages/templates/fragments/icons/shield-indirect-denounce.html
··· 1 + {{ define "fragments/icons/shield-indirect-denounce" }} 2 + <svg class="{{.}}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 3 + <path d="M4 13C4 18 7.5 20.5 11.66 21.95C11.8778 22.0238 12.1145 22.0202 12.33 21.94C16.5 20.5 20 18 20 13V5.99996C20 5.73474 19.8946 5.48039 19.7071 5.29285C19.5196 5.10532 19.2652 4.99996 19 4.99996C17 4.99996 14.5 3.79996 12.76 2.27996C12.5481 2.09896 12.2786 1.99951 12 1.99951C11.7214 1.99951 11.4519 2.09896 11.24 2.27996C9.49 3.80996 7 4.99996 5 4.99996C4.73478 4.99996 4.48043 5.10532 4.29289 5.29285C4.10536 5.48039 4 5.73474 4 5.99996V13Z" fill="currentColor" stroke="#DC2626" stroke-linecap="round" stroke-linejoin="round"/> 4 + <path d="M20 13C20 18 16.5 20.5 12.33 21.94C12.2235 21.9796 12.1119 22.0005 12 22.0027L12.0318 1.99994C12.2991 2.00721 12.5562 2.10586 12.76 2.27996C14.5 3.79996 17 4.99996 19 4.99996C19.2652 4.99996 19.5196 5.10532 19.7071 5.29285C19.8946 5.48039 20 5.73474 20 5.99996V13Z" fill="#EF4444"/> 5 + <path d="M12 1.99951C12.0106 1.99951 12.0212 1.99966 12.0318 1.99994M12.0318 1.99994C12.2991 2.00721 12.5562 2.10586 12.76 2.27996C14.5 3.79996 17 4.99996 19 4.99996C19.2652 4.99996 19.5196 5.10532 19.7071 5.29285C19.8946 5.48039 20 5.73474 20 5.99996V13C20 18 16.5 20.5 12.33 21.94C12.2235 21.9796 12.1119 22.0005 12 22.0027L12.0318 1.99994Z" stroke="#DC2626" stroke-linecap="round" stroke-linejoin="round"/> 6 + </svg> 7 + {{ end }}
+6
appview/pages/templates/fragments/icons/shield-indirect-vouch.html
··· 1 + {{ define "fragments/icons/shield-indirect-vouch" }} 2 + <svg class="{{.}}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 3 + <path d="M20 13C20 18 16.5 20.5 12.34 21.95C12.1222 22.0238 11.8855 22.0202 11.67 21.94C7.5 20.5 4 18 4 13V5.99996C4 5.73474 4.10536 5.48039 4.29289 5.29285C4.48043 5.10532 4.73478 4.99996 5 4.99996C7 4.99996 9.5 3.79996 11.24 2.27996C11.4519 2.09896 11.7214 1.99951 12 1.99951C12.2786 1.99951 12.5481 2.09896 12.76 2.27996C14.51 3.80996 17 4.99996 19 4.99996C19.2652 4.99996 19.5196 5.10532 19.7071 5.29285C19.8946 5.48039 20 5.73474 20 5.99996V13Z" fill="currentColor" stroke="#16A34A" stroke-linecap="round" stroke-linejoin="round"/> 4 + <path d="M4 13C4 18 7.5 20.5 11.67 21.94C11.7765 21.9796 11.8881 22.0005 12 22.0027V1.99951C11.7214 1.99951 11.4519 2.09896 11.24 2.27996C9.5 3.79996 7 4.99996 5 4.99996C4.73478 4.99996 4.48043 5.10532 4.29289 5.29285C4.10536 5.48039 4 5.73474 4 5.99996V13Z" fill="#22C55E" stroke="#16A34A" stroke-linecap="round" stroke-linejoin="round"/> 5 + </svg> 6 + {{ end }}
+12
appview/pages/templates/fragments/icons/shield-mixed.html
··· 1 + {{ define "fragments/icons/shield-mixed" }} 2 + <svg class="{{.}}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 3 + <g clip-path="url(#clip0_shield_mixed)"> 4 + <path d="M20 13C20 18 16.5 20.5 12.34 21.95C12.1222 22.0238 11.8855 22.0202 11.67 21.94C7.5 20.5 4 18 4 13V5.99996C4 5.73474 4.10536 5.48039 4.29289 5.29285C4.48043 5.10532 4.73478 4.99996 5 4.99996C7 4.99996 9.5 3.79996 11.24 2.27996C11.4519 2.09896 11.7214 1.99951 12 1.99951C12.2786 1.99951 12.5481 2.09896 12.76 2.27996C14.51 3.80996 17 4.99996 19 4.99996C19.2652 4.99996 19.5196 5.10532 19.7071 5.29285C19.8946 5.48039 20 5.73474 20 5.99996V13Z" fill="#FBBF24" stroke="#F59E0B" stroke-linecap="round" stroke-linejoin="round"/> 5 + </g> 6 + <defs> 7 + <clipPath id="clip0_shield_mixed"> 8 + <rect width="24" height="24" fill="white"/> 9 + </clipPath> 10 + </defs> 11 + </svg> 12 + {{ end }}
+5
appview/pages/templates/fragments/icons/shield-plain.html
··· 1 + {{ define "fragments/icons/shield-plain" }} 2 + <svg class="{{.}}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 3 + <path d="M20 13C20 18 16.5 20.5 12.34 21.95C12.1222 22.0238 11.8855 22.0202 11.67 21.94C7.5 20.5 4 18 4 13V5.99996C4 5.73474 4.10536 5.48039 4.29289 5.29285C4.48043 5.10532 4.73478 4.99996 5 4.99996C7 4.99996 9.5 3.79996 11.24 2.27996C11.4519 2.09896 11.7214 1.99951 12 1.99951C12.2786 1.99951 12.5481 2.09896 12.76 2.27996C14.51 3.80996 17 4.99996 19 4.99996C19.2652 4.99996 19.5196 5.10532 19.7071 5.29285C19.8946 5.48039 20 5.73474 20 5.99996V13Z" fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/> 4 + </svg> 5 + {{ end }}
+8 -5
appview/pages/templates/repo/issues/fragments/commentList.html
··· 14 14 "RepoInfo" $root.RepoInfo 15 15 "LoggedInUser" $root.LoggedInUser 16 16 "Issue" $root.Issue 17 - "Comment" $comment.Self) }} 17 + "Comment" $comment.Self 18 + "VouchRelationship" (index $root.VouchRelationships $comment.Self.Did) 19 + ) }} 18 20 19 21 <div class="rounded border border-gray-200 dark:border-gray-700 w-full overflow-hidden shadow-sm bg-gray-50 dark:bg-gray-800/50"> 20 22 {{ template "topLevelComment" $params }} ··· 28 30 "RepoInfo" $root.RepoInfo 29 31 "LoggedInUser" $root.LoggedInUser 30 32 "Issue" $root.Issue 31 - "Comment" $reply) 32 - }} 33 + "Comment" $reply 34 + "VouchRelationship" (index $root.VouchRelationships $reply.Did) 35 + ) }} 33 36 </div> 34 37 {{ end }} 35 38 </div> ··· 41 44 {{ define "topLevelComment" }} 42 45 <div class="rounded px-6 py-4 bg-white dark:bg-gray-800 flex gap-2 "> 43 46 <div class="flex-shrink-0"> 44 - {{ template "user/fragments/picLink" (list .Comment.Did "size-8 mr-1") }} 47 + {{ template "user/fragments/picLink" (list .Comment.Did "size-8 mr-1" .VouchRelationship) }} 45 48 </div> 46 49 <div class="flex-1 min-w-0"> 47 50 {{ template "repo/issues/fragments/issueCommentHeader" . }} ··· 53 56 {{ define "replyComment" }} 54 57 <div class="py-4 pr-4 w-full mx-auto overflow-hidden flex gap-2 "> 55 58 <div class="flex-shrink-0"> 56 - {{ template "user/fragments/picLink" (list .Comment.Did "size-8 mr-1") }} 59 + {{ template "user/fragments/picLink" (list .Comment.Did "size-8 mr-1" .VouchRelationship) }} 57 60 </div> 58 61 <div class="flex-1 min-w-0"> 59 62 {{ template "repo/issues/fragments/issueCommentHeader" . }}
+3 -2
appview/pages/templates/repo/issues/fragments/issueListing.html
··· 26 26 <span class="text-white dark:text-white text-sm">{{ $state }}</span> 27 27 </span> 28 28 29 - <span class="ml-1"> 30 - {{ template "user/fragments/picHandleLink" .Did }} 29 + <span class="ml-1 flex items-center gap-1"> 30 + {{ template "user/fragments/picLink" (list .Did "size-6" (index $.VouchRelationships .Did)) }} 31 + <a href="/{{ resolve .Did }}">{{ resolve .Did }}</a> 31 32 </span> 32 33 33 34 <span class="before:content-['·']">
+5 -2
appview/pages/templates/repo/issues/issue.html
··· 68 68 69 69 <span class="text-gray-500 dark:text-gray-400 text-sm flex flex-wrap items-center gap-1"> 70 70 opened by 71 - {{ template "user/fragments/picHandleLink" .Issue.Did }} 71 + {{ template "user/fragments/picLink" (list .Issue.Did "size-6" (index .VouchRelationships .Issue.Did)) }} 72 + <a href="/{{ resolve .Issue.Did }}">{{ resolve .Issue.Did }}</a> 72 73 <span class="select-none before:content-['\00B7']"></span> 73 74 {{ if .Issue.Edited }} 74 75 edited {{ template "repo/fragments/time" .Issue.Edited }} ··· 118 119 "RepoInfo" $.RepoInfo 119 120 "LoggedInUser" $.LoggedInUser 120 121 "Issue" $.Issue 121 - "CommentList" $.Issue.CommentList) 122 + "CommentList" $.Issue.CommentList 123 + "VouchRelationships" $.VouchRelationships 124 + ) 122 125 }} 123 126 124 127 {{ template "repo/issues/fragments/newComment" . }}
+7 -1
appview/pages/templates/repo/issues/issues.html
··· 65 65 66 66 {{ define "repoAfter" }} 67 67 <div class="mt-2"> 68 - {{ template "repo/issues/fragments/issueListing" (dict "Issues" .Issues "RepoPrefix" .RepoInfo.FullName "LabelDefs" .LabelDefs) }} 68 + {{ template "repo/issues/fragments/issueListing" 69 + (dict 70 + "Issues" .Issues 71 + "RepoPrefix" .RepoInfo.FullName 72 + "LabelDefs" .LabelDefs 73 + "VouchRelationships" .VouchRelationships 74 + ) }} 69 75 </div> 70 76 {{if gt .IssueCount .Page.Limit }} 71 77 {{ template "fragments/pagination" (dict
+2 -1
appview/pages/templates/repo/pulls/fragments/pullHeader.html
··· 11 11 {{ template "repo/pulls/fragments/pullState" .Pull.State }} 12 12 <span class="text-gray-500 dark:text-gray-400 text-sm flex flex-wrap items-center gap-1"> 13 13 opened by 14 - {{ template "user/fragments/picHandleLink" .Pull.OwnerDid }} 14 + {{ template "user/fragments/picLink" (list .Pull.OwnerDid "size-6" (index .VouchRelationships .Pull.OwnerDid)) }} 15 + <a href="/{{ resolve .Pull.OwnerDid }}">{{ resolve .Pull.OwnerDid }}</a> 15 16 <span class="select-none before:content-['\00B7']"></span> 16 17 {{ template "repo/fragments/time" .Pull.Created }} 17 18
+10 -8
appview/pages/templates/repo/pulls/pull.html
··· 300 300 flex gap-2 sticky top-0 z-20"> 301 301 <!-- left column: just profile picture --> 302 302 <div class="flex-shrink-0 pt-2"> 303 - {{ template "user/fragments/picLink" (list $root.Pull.OwnerDid "size-8") }} 303 + {{ template "user/fragments/picLink" (list $root.Pull.OwnerDid "size-8" (index $root.VouchRelationships $root.Pull.OwnerDid)) }} 304 304 </div> 305 305 <!-- right column --> 306 306 <div class="flex-1 min-w-0 flex flex-col gap-1"> ··· 592 592 </summary> 593 593 <div> 594 594 {{ range $item.Comments }} 595 - {{ template "submissionComment" . }} 595 + {{ template "submissionComment" (list . $root) }} 596 596 {{ end }} 597 597 </div> 598 598 {{ if gt $c 0}} ··· 625 625 {{ end }} 626 626 627 627 {{ define "submissionComment" }} 628 - <div id="comment-{{.ID}}" class="flex gap-2 -ml-4 py-4 w-full mx-auto group/comment"> 628 + {{ $comment := index . 0 }} 629 + {{ $root := index . 1 }} 630 + <div id="comment-{{$comment.ID}}" class="flex gap-2 -ml-4 py-4 w-full mx-auto group/comment"> 629 631 <!-- left column: profile picture --> 630 632 <div class="flex-shrink-0 h-fit relative"> 631 - {{ template "user/fragments/picLink" (list .OwnerDid "size-8") }} 633 + {{ template "user/fragments/picLink" (list $comment.OwnerDid "size-8" (index $root.VouchRelationships $comment.OwnerDid)) }} 632 634 </div> 633 635 <!-- right column: name and body in two rows --> 634 636 <div class="flex-1 min-w-0"> 635 637 <!-- Row 1: Author and timestamp --> 636 638 <div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1 group-target/comment:bg-yellow-200/30 group-target/comment:dark:bg-yellow-600/30"> 637 - {{ $handle := resolve .OwnerDid }} 639 + {{ $handle := resolve $comment.OwnerDid }} 638 640 <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="/{{ $handle }}">{{ $handle }}</a> 639 641 <span class="before:content-['·']"></span> 640 - <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.ID}}"> 641 - {{ template "repo/fragments/shortTime" .Created }} 642 + <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{$comment.ID}}"> 643 + {{ template "repo/fragments/shortTime" $comment.Created }} 642 644 </a> 643 645 </div> 644 646 <!-- Row 2: Body text --> 645 647 <div class="prose dark:prose-invert mt-1"> 646 - {{ .Body | markdown }} 648 + {{ $comment.Body | markdown }} 647 649 </div> 648 650 </div> 649 651 </div>
+3 -2
appview/pages/templates/repo/pulls/pulls.html
··· 81 81 </div> 82 82 <div class="text-sm text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-1"> 83 83 {{ template "repo/pulls/fragments/pullState" $topPR.State }} 84 - <span class="ml-1"> 85 - {{ template "user/fragments/picHandleLink" $topPR.OwnerDid }} 84 + <span class="ml-1 flex items-center gap-1"> 85 + {{ template "user/fragments/picLink" (list $topPR.OwnerDid "size-6" (index $.VouchRelationships $topPR.OwnerDid)) }} 86 + <a href="/{{ resolve $topPR.OwnerDid }}">{{ resolve $topPR.OwnerDid }}</a> 86 87 </span> 87 88 88 89 <span class="before:content-['·']">
+42
appview/pages/templates/user/fragments/networkVouches.html
··· 1 + {{/* Takes a VouchRelationship directly */}} 2 + {{ define "user/fragments/networkVouches" }} 3 + {{ $vouches := (list) }} 4 + {{ $denounces := (list) }} 5 + {{ range .IndirectVouches }} 6 + {{ if .IsVouch }} 7 + {{ $vouches = append $vouches .Did }} 8 + {{ else if .IsDenounce }} 9 + {{ $denounces = append $denounces .Did }} 10 + {{ end }} 11 + {{ end }} 12 + 13 + <div class="rounded overflow-hidden"> 14 + {{ if $vouches }} 15 + <div class=" 16 + bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 17 + p-2 flex items-center justify-between gap-3 18 + border-x border-green-600/25 dark:border-green-400/25 19 + {{ if $denounces }} border-t {{ else }} border-y {{ end }} 20 + "> 21 + <a class="text-sm text-green-800 dark:text-green-200 hover:no-underline" href="/{{ .SubjectDid }}?tab=vouches"> 22 + vouched for by {{ len $vouches }} {{ if eq (len $vouches) 1 }}user{{ else }}users{{ end }} 23 + </a> 24 + {{ template "fragments/tinyAvatarList" (dict "all" $vouches "classes" "size-6") }} 25 + </div> 26 + {{ end }} 27 + 28 + {{ if $denounces }} 29 + <div class=" 30 + bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200 31 + p-2 flex items-center justify-between gap-3 32 + border-x border-red-600/25 dark:border-red-400/25 33 + {{ if $vouches }} border-b {{ else }} border-y {{ end }} 34 + "> 35 + <a class="text-sm text-red-800 dark:text-red-200 hover:no-underline" href="/{{ .SubjectDid }}?tab=vouches"> 36 + denounced by {{ len $denounces }} {{ if eq (len $denounces) 1 }}user{{ else }}users{{ end }} 37 + </a> 38 + {{ template "fragments/tinyAvatarList" (dict "all" $denounces "classes" "size-6") }} 39 + </div> 40 + {{ end }} 41 + </div> 42 + {{ end }}
+42 -10
appview/pages/templates/user/fragments/picLink.html
··· 2 2 {{ $did := index . 0 }} 3 3 {{ $classes := index . 1 }} 4 4 {{ $handle := resolve $did }} 5 - <a href="/{{ $handle }}" title="{{ $handle }}"> 6 - <img 7 - src="{{ tinyAvatar $did }}" 8 - alt="" 9 - class="rounded-full border border-gray-300 dark:border-gray-700 {{ $classes }}" 10 - /> 11 - </a> 5 + {{ $vr := false }} 6 + {{ if ge (len .) 3 }} 7 + {{ $vr = index . 2 }} 8 + {{ end }} 9 + <div class="relative inline-block"> 10 + <a href="/{{ $handle }}" title="{{ $handle }}"> 11 + <img 12 + src="{{ tinyAvatar $did }}" 13 + alt="" 14 + class="rounded-full border border-gray-300 dark:border-gray-700 {{ $classes }}" 15 + /> 16 + </a> 17 + {{ if $vr }} 18 + {{ if ne $did $vr.ViewerDid }} 19 + <button 20 + type="button" 21 + popovertarget="vouch-modal-{{ normalizeForHtmlId $vr.SubjectDid }}" 22 + popovertargetaction="toggle" 23 + class="absolute -bottom-0.5 -right-0.5 w-1/2 h-1/2 cursor-pointer" 24 + title="{{ template "user/fragments/vouchTooltip" $vr }}"> 25 + {{ if $vr.IsDirectVouch }} 26 + {{ template "fragments/icons/shield-direct-vouch" "w-full h-full" }} 27 + {{ else if $vr.IsDirectDenounce }} 28 + {{ template "fragments/icons/shield-direct-denounce" "w-full h-full" }} 29 + {{ else if $vr.IsMixed }} 30 + {{ template "fragments/icons/shield-mixed" "w-full h-full" }} 31 + {{ else if $vr.IsIndirectVouch }} 32 + {{ template "fragments/icons/shield-indirect-vouch" "w-full h-full text-white dark:text-gray-900" }} 33 + {{ else if $vr.IsIndirectDenounce }} 34 + {{ template "fragments/icons/shield-indirect-denounce" "w-full h-full text-white dark:text-gray-900" }} 35 + {{ else }} 36 + {{ template "fragments/icons/shield-empty" "w-full h-full" }} 37 + {{ end }} 38 + </button> 39 + {{ end }} 40 + {{ end }} 41 + </div> 42 + {{ if $vr }} 43 + {{ if ne $did $vr.ViewerDid }} 44 + {{ template "user/fragments/vouchPopover" (dict "VouchRelationship" $vr) }} 45 + {{ end }} 46 + {{ end }} 12 47 {{ end }} 13 - 14 - 15 -
+30
appview/pages/templates/user/fragments/vouchButton.html
··· 1 + {{ define "user/fragments/vouchButton" }} 2 + {{ $isVouched := false }} 3 + {{ $isDenounced := false }} 4 + {{ if .VouchRelationship }} 5 + {{ if .VouchRelationship.IsDirectVouch }} 6 + {{ $isVouched = true }} 7 + {{ else if .VouchRelationship.IsDirectDenounce }} 8 + {{ $isDenounced = true }} 9 + {{ end }} 10 + {{ end }} 11 + 12 + {{ $userDid := "" }} 13 + {{ with .VouchRelationship }} 14 + {{ $userDid = .SubjectDid }} 15 + {{ end }} 16 + <button 17 + id="vouch-btn-{{ normalizeForHtmlId $userDid }}" 18 + type="button" 19 + popovertarget="vouch-modal-{{ normalizeForHtmlId $userDid }}" 20 + popovertargetaction="toggle" 21 + class="{{ if $isVouched }}btn-create{{else if $isDenounced}}btn-cancel{{else}}btn{{end}} w-full flex gap-2 items-center justify-center"> 22 + {{ if $isVouched }} 23 + {{ i "shield-check" "size-4" }} vouched 24 + {{ else if $isDenounced }} 25 + {{ i "shield-off" "size-4" }} denounced 26 + {{ else }} 27 + {{ i "shield-question-mark" "size-4" }} vouch 28 + {{ end }} 29 + </button> 30 + {{ end }}
+151
appview/pages/templates/user/fragments/vouchPopover.html
··· 1 + {{ define "user/fragments/vouchPopover" }} 2 + {{ $isVouched := false }} 3 + {{ $isDenounced := false }} 4 + {{ $isUndecided := false }} 5 + {{ if .VouchRelationship }} 6 + {{ if .VouchRelationship.IsDirectVouch }} 7 + {{ $isVouched = true }} 8 + {{ else if .VouchRelationship.IsDirectDenounce }} 9 + {{ $isDenounced = true }} 10 + {{ else }} 11 + {{ $isUndecided = true }} 12 + {{ end }} 13 + {{ else }} 14 + {{ $isUndecided = true }} 15 + {{ end }} 16 + 17 + {{ $userIdent := "" }} 18 + {{ $userDid := "" }} 19 + {{ with .VouchRelationship }} 20 + {{ $userDid = .SubjectDid }} 21 + {{ $userIdent = resolve .SubjectDid }} 22 + {{ end }} 23 + <div 24 + id="vouch-modal-{{ normalizeForHtmlId $userDid }}" 25 + popover 26 + class="bg-white w-[95%] md:w-96 dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 drop-shadow dark:text-white backdrop:bg-gray-400/50 dark:backdrop:bg-gray-800/50 space-y-2"> 27 + 28 + <form 29 + hx-post="/vouch" 30 + hx-swap="none" 31 + hx-on::after-request="if(event.detail.successful) this.reset()" 32 + class="flex flex-col gap-6 group"> 33 + 34 + <div> 35 + <h3 class="text-lg font-semibold">Vouch for {{ $userIdent }}</h3> 36 + <p class="text-gray-700 dark:text-gray-300"> 37 + {{ with .VouchRelationship }} 38 + {{ with .GetDirectVouch }} 39 + You {{if $isVouched}}vouched{{else}}denounced{{end}} {{ $userIdent }} {{ relTimeFmt .CreatedAt }}. 40 + You can change your descision below. 41 + {{ else }} 42 + Vouching builds a web-of-trust across Tangled. Vouch for users you 43 + have had positive interactions with. 44 + {{ end }} 45 + {{ end }} 46 + </p> 47 + </div> 48 + 49 + {{ if .VouchRelationship }} 50 + {{ if .VouchRelationship.IndirectVouches }} 51 + <div> 52 + <p class="font-semibold mb-1 text-sm">From your network:</p> 53 + {{ template "user/fragments/networkVouches" .VouchRelationship }} 54 + </div> 55 + {{ end }} 56 + {{ end }} 57 + 58 + <input type="hidden" name="subject" value="{{ $userDid }}" /> 59 + 60 + {{ $labelClass := "grid grid-cols-[auto_1fr_auto] items-center gap-2 rounded p-2 py- ring-1 ring-gray-200 dark:ring-gray-700 cursor-pointer" }} 61 + 62 + <div class="grid grid-cols-3 gap-2"> 63 + <input 64 + id="vouch-{{ normalizeForHtmlId $userDid }}" 65 + type="radio" 66 + name="kind" 67 + value="vouch" 68 + {{ if $isVouched }}checked{{ end }} 69 + class="peer/vouch hidden appearance-none" /> 70 + <input 71 + id="denounce-{{ normalizeForHtmlId $userDid }}" 72 + type="radio" 73 + name="kind" 74 + value="denounce" 75 + {{ if $isDenounced }}checked{{ end }} 76 + class="peer/denounce hidden appearance-none" /> 77 + <input 78 + id="none-{{ normalizeForHtmlId $userDid }}" 79 + type="radio" 80 + name="kind" 81 + value="none" 82 + {{ if $isUndecided }}checked{{ end }} 83 + class="peer/none hidden appearance-none" /> 84 + 85 + <label 86 + for="vouch-{{ normalizeForHtmlId $userDid }}" 87 + class=" 88 + {{ $labelClass }} 89 + hover:bg-gray-100 peer-checked/vouch:bg-green-200 peer-checked/vouch:text-green-800 peer-checked/vouch:ring-green-400 90 + dark:hover:bg-white/5 dark:peer-checked/vouch:bg-green-700 dark:peer-checked/vouch:text-green-200 dark:peer-checked/vouch:ring-green-600"> 91 + {{ i "shield-check" "size-5" }} 92 + <span class="text-sm font-medium">Vouch</span> 93 + </label> 94 + <label 95 + for="denounce-{{ normalizeForHtmlId $userDid }}" 96 + class=" 97 + {{ $labelClass }} 98 + hover:bg-gray-100 peer-checked/denounce:bg-red-200 peer-checked/denounce:text-red-800 peer-checked/denounce:ring-red-400 99 + dark:hover:bg-white/5 dark:peer-checked/denounce:bg-red-700 dark:peer-checked/denounce:text-red-200 dark:peer-checked/denounce:ring-red-600"> 100 + {{ i "shield-ban" "size-5" }} 101 + <span class="text-sm font-medium">Denounce</span> 102 + </label> 103 + <label 104 + for="none-{{ normalizeForHtmlId $userDid }}" 105 + class=" 106 + {{ $labelClass }} 107 + hover:bg-gray-100 peer-checked/none:bg-gray-200 peer-checked/none:text-gray-800 peer-checked/none:ring-gray-400 108 + dark:hover:bg-white/5 dark:peer-checked/none:bg-gray-700 dark:peer-checked/none:text-gray-200 dark:peer-checked/none:ring-gray-600"> 109 + {{ i "shield-off" "size-5" }} 110 + <span class="text-sm font-medium">None</span> 111 + </label> 112 + 113 + <textarea 114 + id="reason-{{ normalizeForHtmlId $userDid }}" 115 + name="reason" 116 + rows="1" 117 + maxlength="300" 118 + placeholder="Write a reason..." 119 + class="w-full resize-none col-span-3 peer-checked/none:hidden"> 120 + {{- with .VouchRelationship -}} 121 + {{- with .GetDirectVouch -}} 122 + {{- with .Reason -}} 123 + {{- . -}} 124 + {{- end -}} 125 + {{- end -}} 126 + {{- end -}} 127 + </textarea> 128 + </div> 129 + 130 + <div class="flex gap-2"> 131 + <button 132 + type="button" 133 + popovertarget="vouch-modal-{{ normalizeForHtmlId $userDid }}" 134 + popovertargetaction="hide" 135 + class="btn flex-1 flex items-center justify-center gap-2"> 136 + {{ i "x" "size-4" }} 137 + cancel 138 + </button> 139 + <button 140 + type="submit" 141 + class="btn-create flex-1 flex items-center justify-center gap-2 text-white group"> 142 + {{ i "check" "size-4 inline group-[.htmx-request]:hidden" }} 143 + {{ i "loader-circle" "size-4 animate-spin hidden group-[.htmx-request]:inline" }} 144 + {{ if and .VouchRelationship .VouchRelationship.GetDirectVouch }} update {{ else }} save {{end}} 145 + </button> 146 + </div> 147 + </form> 148 + 149 + <div id="error" class="w-full error"></div> 150 + </div> 151 + {{ end }}
+34 -11
appview/pulls/pulls.go
··· 266 266 defs[l.AtUri().String()] = &l 267 267 } 268 268 269 + vouchRelationships := make(map[syntax.DID]*models.VouchRelationship) 270 + if user != nil { 271 + participants := pull.Participants() 272 + vouchRelationships, err = db.GetVouchRelationshipsBatch(s.db, syntax.DID(user.Did), participants) 273 + if err != nil { 274 + l.Error("failed to fetch vouch relationships", "err", err) 275 + } 276 + } 277 + 269 278 patch := pull.Submissions[roundIdInt].CombinedPatch() 270 279 var diff types.DiffRenderer 271 280 diff = patchutil.AsNiceDiff(patch, pull.TargetBranch) ··· 306 315 Reactions: reactionMap, 307 316 UserReacted: userReactions, 308 317 309 - LabelDefs: defs, 318 + LabelDefs: defs, 319 + VouchRelationships: vouchRelationships, 310 320 }) 311 321 } 312 322 ··· 782 792 filterState = state.String() 783 793 } 784 794 795 + vouchRelationships := make(map[syntax.DID]*models.VouchRelationship) 796 + if user != nil { 797 + dids := make([]syntax.DID, len(pulls)) 798 + for i, p := range pulls { 799 + dids[i] = syntax.DID(p.OwnerDid) 800 + } 801 + vouchRelationships, err = db.GetVouchRelationshipsBatch(s.db, syntax.DID(user.Did), dids) 802 + if err != nil { 803 + l.Error("failed to fetch vouch relationships", "err", err) 804 + } 805 + } 806 + 785 807 s.pages.RepoPulls(w, pages.RepoPullsParams{ 786 - LoggedInUser: s.oauth.GetMultiAccountUser(r), 787 - RepoInfo: repoInfo, 788 - Pulls: pulls, 789 - LabelDefs: defs, 790 - FilterState: filterState, 791 - FilterQuery: query.String(), 792 - Stacks: stacks, 793 - Pipelines: m, 794 - Page: page, 795 - PullCount: totalPulls, 808 + LoggedInUser: s.oauth.GetMultiAccountUser(r), 809 + RepoInfo: repoInfo, 810 + Pulls: pulls, 811 + LabelDefs: defs, 812 + FilterState: filterState, 813 + FilterQuery: query.String(), 814 + Stacks: stacks, 815 + Pipelines: m, 816 + Page: page, 817 + PullCount: totalPulls, 818 + VouchRelationships: vouchRelationships, 796 819 }) 797 820 } 798 821
+75 -5
appview/state/profile.go
··· 43 43 ServeHTTP(w, r) 44 44 case "strings": 45 45 s.stringsPage(w, r) 46 + case "vouches": 47 + middleware. 48 + Paginate(http.HandlerFunc(s.vouchesPage)). 49 + ServeHTTP(w, r) 46 50 default: 47 51 s.profileOverview(w, r) 48 52 } ··· 93 97 loggedInUser := s.oauth.GetMultiAccountUser(r) 94 98 followStatus := models.IsNotFollowing 95 99 var loggedInDid string 100 + var vouchRelationship *models.VouchRelationship 101 + 96 102 if loggedInUser != nil { 97 103 followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, did) 98 104 loggedInDid = loggedInUser.Did 105 + vouchRelationship, err = db.GetVouchRelationship(s.db, syntax.DID(loggedInUser.Did), syntax.DID(did)) 99 106 } 100 107 101 108 showPunchcard := s.shouldShowPunchcard(did, loggedInDid) ··· 116 123 } 117 124 118 125 return &pages.ProfileCard{ 119 - UserDid: did, 120 - HasProfile: hasProfile, 121 - Profile: profile, 122 - FollowStatus: followStatus, 126 + UserDid: did, 127 + HasProfile: hasProfile, 128 + Profile: profile, 129 + FollowStatus: followStatus, 130 + VouchRelationship: vouchRelationship, 123 131 Stats: pages.ProfileStats{ 124 132 RepoCount: repoCount, 125 133 StringCount: stringCount, ··· 177 185 l.Error("failed to create timeline", "err", err) 178 186 } 179 187 180 - s.pages.ProfileOverview(w, pages.ProfileOverviewParams{ 188 + err = s.pages.ProfileOverview(w, pages.ProfileOverviewParams{ 181 189 LoggedInUser: s.oauth.GetMultiAccountUser(r), 182 190 Card: profile, 183 191 Repos: pinnedRepos, 184 192 CollaboratingRepos: pinnedCollaboratingRepos, 185 193 ProfileTimeline: timeline, 186 194 }) 195 + if err != nil { 196 + l.Error("failed to render template", "err", err) 197 + } 187 198 } 188 199 189 200 func (s *State) shouldShowPunchcard(targetDid, requesterDid string) bool { ··· 378 389 Strings: strings, 379 390 Card: profile, 380 391 }) 392 + } 393 + 394 + func (s *State) vouchesPage(w http.ResponseWriter, r *http.Request) { 395 + l := s.logger.With("handler", "vouchesPage") 396 + 397 + profile, err := s.profile(r) 398 + if err != nil { 399 + l.Error("failed to build profile card", "err", err) 400 + s.pages.Error500(w) 401 + return 402 + } 403 + l = l.With("profileDid", profile.UserDid) 404 + 405 + loggedInUser := s.oauth.GetMultiAccountUser(r) 406 + page := pagination.FromContext(r.Context()) 407 + 408 + var vouches []models.Vouch 409 + if loggedInUser != nil { 410 + vouches, err = db.GetNetworkVouchTimeline(s.db, loggedInUser.Did, profile.UserDid, page) 411 + if err != nil { 412 + l.Error("failed to get vouch timeline", "err", err) 413 + s.pages.Error500(w) 414 + return 415 + } 416 + } 417 + 418 + var suggestions []models.VouchSuggestion 419 + if loggedInUser != nil && loggedInUser.Did == profile.UserDid { 420 + suggestions, err = db.GetVouchSuggestions(s.db, profile.UserDid, 5) 421 + if err != nil { 422 + l.Error("failed to get vouch suggestions", "err", err) 423 + } 424 + 425 + if len(suggestions) > 0 { 426 + suggestionDids := make([]syntax.DID, len(suggestions)) 427 + for i, s := range suggestions { 428 + suggestionDids[i] = syntax.DID(s.Did) 429 + } 430 + relationships, err := db.GetVouchRelationshipsBatch(s.db, syntax.DID(loggedInUser.Did), suggestionDids) 431 + if err != nil { 432 + l.Error("failed to get vouch relationships for suggestions", "err", err) 433 + } else { 434 + for i := range suggestions { 435 + suggestions[i].VouchRelationship = relationships[suggestions[i].Did] 436 + } 437 + } 438 + } 439 + } 440 + 441 + err = s.pages.ProfileVouches(w, pages.ProfileVouchesParams{ 442 + LoggedInUser: loggedInUser, 443 + Vouches: vouches, 444 + Suggestions: suggestions, 445 + Card: profile, 446 + Page: page, 447 + }) 448 + if err != nil { 449 + l.Error("failed to render page", "err", err) 450 + } 381 451 } 382 452 383 453 type FollowsPageParams struct {
+9
input.css
··· 147 147 disabled:before:bg-green-400 dark:disabled:before:bg-green-600; 148 148 } 149 149 150 + .btn-cancel { 151 + @apply btn text-white 152 + before:bg-red-600 hover:before:bg-red-700 153 + dark:before:bg-red-700 dark:hover:before:bg-red-800 154 + before:border before:border-red-700 hover:before:border-red-800 155 + focus-visible:before:outline-red-500 156 + disabled:before:bg-red-400 dark:disabled:before:bg-red-600; 157 + } 158 + 150 159 .prose { 151 160 overflow-wrap: anywhere; 152 161 }