Signed-off-by: oppiliappan me@oppi.li
+597
-88
Diff
round #0
+43
-17
appview/issues/issues.go
+43
-17
appview/issues/issues.go
···
127
127
return
128
128
}
129
129
130
+
vouchRelationships := make(map[string]*models.VouchRelationship)
131
+
if user != nil {
132
+
participants := issue.Participants()
133
+
vouchRelationships, err = db.GetVouchRelationshipsBatch(rp.db, 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[string]*models.VouchRelationship)
995
+
if user != nil {
996
+
dids := make([]string, len(issues))
997
+
for i, u := range issues {
998
+
dids[i] = u.Did
999
+
}
1000
+
vouchRelationships, err = db.GetVouchRelationshipsBatch(rp.db, 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
+42
appview/models/vouch.go
+42
appview/models/vouch.go
···
101
101
return nil
102
102
}
103
103
104
+
func (vr *VouchRelationship) IsIndirectVouch() bool {
105
+
if vr.IsDirectVouch() || vr.IsDirectDenounce() {
106
+
return false
107
+
}
108
+
for _, v := range vr.NetworkVouches {
109
+
if v.Did != vr.ViewerDid && v.Kind == VouchKindVouch {
110
+
return true
111
+
}
112
+
}
113
+
return false
114
+
}
115
+
116
+
func (vr *VouchRelationship) IsIndirectDenounce() bool {
117
+
if vr.IsDirectVouch() || vr.IsDirectDenounce() {
118
+
return false
119
+
}
120
+
for _, v := range vr.NetworkVouches {
121
+
if v.Did != vr.ViewerDid && v.Kind == VouchKindDenounce {
122
+
return true
123
+
}
124
+
}
125
+
return false
126
+
}
127
+
128
+
func (vr *VouchRelationship) IsMixed() bool {
129
+
if vr.IsDirectVouch() || vr.IsDirectDenounce() {
130
+
return false
131
+
}
132
+
hasVouch := false
133
+
hasDenounce := false
134
+
for _, v := range vr.NetworkVouches {
135
+
if v.Did != vr.ViewerDid {
136
+
if v.Kind == VouchKindVouch {
137
+
hasVouch = true
138
+
} else if v.Kind == VouchKindDenounce {
139
+
hasDenounce = true
140
+
}
141
+
}
142
+
}
143
+
return hasVouch && hasDenounce
144
+
}
145
+
104
146
func (vr *VouchRelationship) VouchStrength() int {
105
147
count := 0
106
148
for _, v := range vr.NetworkVouches {
+2
appview/pages/funcmap.go
+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
+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
···
708
710
return p.executeProfile("user/strings", w, params)
709
711
}
710
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)
725
+
}
726
+
711
727
type FollowCard struct {
712
728
UserDid string
713
729
LoggedInUser *oauth.MultiAccountUser
···
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[string]*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[string]*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[string]*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[string]*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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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 }}
+28
-5
appview/pulls/pulls.go
+28
-5
appview/pulls/pulls.go
···
266
266
defs[l.AtUri().String()] = &l
267
267
}
268
268
269
+
vouchRelationships := make(map[string]*models.VouchRelationship)
270
+
if user != nil {
271
+
participants := pull.Participants()
272
+
vouchRelationships, err = db.GetVouchRelationshipsBatch(s.db, 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[string]*models.VouchRelationship)
796
+
if user != nil {
797
+
dids := make([]string, len(pulls))
798
+
for i, p := range pulls {
799
+
dids[i] = p.OwnerDid
800
+
}
801
+
vouchRelationships, err = db.GetVouchRelationshipsBatch(s.db, 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
808
LoggedInUser: s.oauth.GetMultiAccountUser(r),
787
809
RepoInfo: repoInfo,
···
789
811
LabelDefs: defs,
790
812
FilterState: filterState,
791
813
FilterQuery: query.String(),
792
-
Stacks: stacks,
793
-
Pipelines: m,
794
-
Page: page,
795
-
PullCount: totalPulls,
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
+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, loggedInUser.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 {
···
380
391
})
381
392
}
382
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([]string, len(suggestions))
427
+
for i, s := range suggestions {
428
+
suggestionDids[i] = s.Did
429
+
}
430
+
relationships, err := db.GetVouchRelationshipsBatch(s.db, 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
+
}
451
+
}
452
+
383
453
type FollowsPageParams struct {
384
454
Follows []pages.FollowCard
385
455
Card *pages.ProfileCard
+9
input.css
+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
}