diff --git a/appview/issues/issues.go b/appview/issues/issues.go
index f2cbf8bb..a96a0185 100644
--- a/appview/issues/issues.go
+++ b/appview/issues/issues.go
@@ -127,21 +127,34 @@ func (rp *Issues) RepoSingleIssue(w http.ResponseWriter, r *http.Request) {
return
}
+ vouchRelationships := make(map[syntax.DID]*models.VouchRelationship)
+ if user != nil {
+ participants := issue.Participants()
+ vouchRelationships, err = db.GetVouchRelationshipsBatch(rp.db, syntax.DID(user.Did), participants)
+ if err != nil {
+ l.Error("failed to fetch vouch relationships", "err", err)
+ }
+ }
+
defs := make(map[string]*models.LabelDefinition)
for _, l := range labelDefs {
defs[l.AtUri().String()] = &l
}
- rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{
- LoggedInUser: user,
- RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
- Issue: issue,
- CommentList: issue.CommentList(),
- Backlinks: backlinks,
- Reactions: reactionMap,
- UserReacted: userReactions,
- LabelDefs: defs,
+ err = rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{
+ LoggedInUser: user,
+ RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
+ Issue: issue,
+ CommentList: issue.CommentList(),
+ Backlinks: backlinks,
+ Reactions: reactionMap,
+ UserReacted: userReactions,
+ LabelDefs: defs,
+ VouchRelationships: vouchRelationships,
})
+ if err != nil {
+ l.Error("failed to render issue", "err", err)
+ }
}
func (rp *Issues) EditIssue(w http.ResponseWriter, r *http.Request) {
@@ -978,15 +991,28 @@ func (rp *Issues) RepoIssues(w http.ResponseWriter, r *http.Request) {
}
}
+ vouchRelationships := make(map[syntax.DID]*models.VouchRelationship)
+ if user != nil {
+ dids := make([]syntax.DID, len(issues))
+ for i, u := range issues {
+ dids[i] = syntax.DID(u.Did)
+ }
+ vouchRelationships, err = db.GetVouchRelationshipsBatch(rp.db, syntax.DID(user.Did), dids)
+ if err != nil {
+ l.Error("failed to fetch vouch relationships", "err", err)
+ }
+ }
+
rp.pages.RepoIssues(w, pages.RepoIssuesParams{
- LoggedInUser: rp.oauth.GetMultiAccountUser(r),
- RepoInfo: repoInfo,
- Issues: issues,
- IssueCount: totalIssues,
- LabelDefs: defs,
- FilterState: filterState,
- FilterQuery: query.String(),
- Page: page,
+ LoggedInUser: rp.oauth.GetMultiAccountUser(r),
+ RepoInfo: repoInfo,
+ Issues: issues,
+ IssueCount: totalIssues,
+ LabelDefs: defs,
+ FilterState: filterState,
+ FilterQuery: query.String(),
+ Page: page,
+ VouchRelationships: vouchRelationships,
})
}
diff --git a/appview/models/issue.go b/appview/models/issue.go
index 58cf07a6..84659ae7 100644
--- a/appview/models/issue.go
+++ b/appview/models/issue.go
@@ -135,21 +135,21 @@ func (i *Issue) CommentList() []CommentListItem {
return listing
}
-func (i *Issue) Participants() []string {
- participantSet := make(map[string]struct{})
- participants := []string{}
+func (i *Issue) Participants() []syntax.DID {
+ participantSet := make(map[syntax.DID]struct{})
+ participants := []syntax.DID{}
- addParticipant := func(did string) {
+ addParticipant := func(did syntax.DID) {
if _, exists := participantSet[did]; !exists {
participantSet[did] = struct{}{}
participants = append(participants, did)
}
}
- addParticipant(i.Did)
+ addParticipant(syntax.DID(i.Did))
for _, c := range i.Comments {
- addParticipant(c.Did)
+ addParticipant(syntax.DID(c.Did))
}
return participants
diff --git a/appview/models/pull.go b/appview/models/pull.go
index e39c0bcc..5723b1fa 100644
--- a/appview/models/pull.go
+++ b/appview/models/pull.go
@@ -386,22 +386,22 @@ func (p *Pull) IsForkBased() bool {
return false
}
-func (p *Pull) Participants() []string {
- participantSet := make(map[string]struct{})
- participants := []string{}
+func (p *Pull) Participants() []syntax.DID {
+ participantSet := make(map[syntax.DID]struct{})
+ participants := []syntax.DID{}
- addParticipant := func(did string) {
+ addParticipant := func(did syntax.DID) {
if _, exists := participantSet[did]; !exists {
participantSet[did] = struct{}{}
participants = append(participants, did)
}
}
- addParticipant(p.OwnerDid)
+ addParticipant(syntax.DID(p.OwnerDid))
for _, s := range p.Submissions {
for _, sp := range s.Participants() {
- addParticipant(sp)
+ addParticipant(syntax.DID(sp))
}
}
diff --git a/appview/models/vouch.go b/appview/models/vouch.go
index 1ce9d3a9..fa27068f 100644
--- a/appview/models/vouch.go
+++ b/appview/models/vouch.go
@@ -102,6 +102,49 @@ func (vr *VouchRelationship) GetDirectVouch() *Vouch {
return nil
}
+func (vr *VouchRelationship) IsIndirectVouch() bool {
+ if vr.IsDirectVouch() || vr.IsDirectDenounce() {
+ return false
+ }
+ for _, v := range vr.NetworkVouches {
+ if v.Did != vr.ViewerDid && v.Kind == VouchKindVouch {
+ return true
+ }
+ }
+ return false
+}
+
+func (vr *VouchRelationship) IsIndirectDenounce() bool {
+ if vr.IsDirectVouch() || vr.IsDirectDenounce() {
+ return false
+ }
+ for _, v := range vr.NetworkVouches {
+ if v.Did != vr.ViewerDid && v.Kind == VouchKindDenounce {
+ return true
+ }
+ }
+ return false
+}
+
+func (vr *VouchRelationship) IsMixed() bool {
+ if vr.IsDirectVouch() || vr.IsDirectDenounce() {
+ return false
+ }
+ hasVouch := false
+ hasDenounce := false
+ for _, v := range vr.NetworkVouches {
+ if v.Did != vr.ViewerDid {
+ switch v.Kind {
+ case VouchKindVouch:
+ hasVouch = true
+ case VouchKindDenounce:
+ hasDenounce = true
+ }
+ }
+ }
+ return hasVouch && hasDenounce
+}
+
func (vr *VouchRelationship) VouchStrength() int {
count := 0
for _, v := range vr.NetworkVouches {
diff --git a/appview/notify/db/db.go b/appview/notify/db/db.go
index bc5f6658..d3e01ae0 100644
--- a/appview/notify/db/db.go
+++ b/appview/notify/db/db.go
@@ -305,7 +305,7 @@ func (n *databaseNotifier) NewPullComment(ctx context.Context, comment *models.P
// - remove those already mentioned
recipients := sets.Singleton(syntax.DID(repo.Did))
for _, p := range pull.Participants() {
- recipients.Insert(syntax.DID(p))
+ recipients.Insert(p)
}
for _, m := range mentions {
recipients.Remove(m)
@@ -439,7 +439,7 @@ func (n *databaseNotifier) NewPullState(ctx context.Context, actor syntax.DID, p
recipients.Insert(c.SubjectDid)
}
for _, p := range pull.Participants() {
- recipients.Insert(syntax.DID(p))
+ recipients.Insert(p)
}
entityType := "pull"
diff --git a/appview/pages/funcmap.go b/appview/pages/funcmap.go
index f686381b..9bce3fa6 100644
--- a/appview/pages/funcmap.go
+++ b/appview/pages/funcmap.go
@@ -24,6 +24,7 @@ import (
"github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles"
"github.com/dustin/go-humanize"
+ "github.com/dustin/go-humanize/english"
"github.com/go-enry/go-enry/v2"
"github.com/yuin/goldmark"
emoji "github.com/yuin/goldmark-emoji"
@@ -177,6 +178,7 @@ func (p *Pages) funcMap() template.FuncMap {
return s
},
"commaFmt": humanize.Comma,
+ "plural": english.Plural,
"relTimeFmt": humanize.Time,
"shortRelTimeFmt": func(t time.Time) string {
return humanize.CustomRelTime(t, time.Now(), "", "", []humanize.RelTimeMagnitude{
diff --git a/appview/pages/pages.go b/appview/pages/pages.go
index fe4d7c8a..02bf0766 100644
--- a/appview/pages/pages.go
+++ b/appview/pages/pages.go
@@ -624,13 +624,14 @@ func (p *Pages) ForkRepo(w io.Writer, params ForkRepoParams) error {
}
type ProfileCard struct {
- UserDid string
- HasProfile bool
- FollowStatus models.FollowStatus
- Punchcard *models.Punchcard
- Profile *models.Profile
- Stats ProfileStats
- Active string
+ UserDid string
+ HasProfile bool
+ FollowStatus models.FollowStatus
+ VouchRelationship *models.VouchRelationship
+ Punchcard *models.Punchcard
+ Profile *models.Profile
+ Stats ProfileStats
+ Active string
}
type ProfileStats struct {
@@ -647,6 +648,7 @@ func (p *ProfileCard) GetTabs() [][]any {
{"repos", "repos", "book-marked", p.Stats.RepoCount},
{"starred", "starred", "star", p.Stats.StarredCount},
{"strings", "strings", "line-squiggle", p.Stats.StringCount},
+ {"vouches", "vouches", "shield", nil},
}
return tabs
@@ -708,6 +710,20 @@ func (p *Pages) ProfileStrings(w io.Writer, params ProfileStringsParams) error {
return p.executeProfile("user/strings", w, params)
}
+type ProfileVouchesParams struct {
+ LoggedInUser *oauth.MultiAccountUser
+ Vouches []models.Vouch
+ Suggestions []models.VouchSuggestion
+ Card *ProfileCard
+ Page pagination.Page
+ Active string
+}
+
+func (p *Pages) ProfileVouches(w io.Writer, params ProfileVouchesParams) error {
+ params.Active = "vouches"
+ return p.executeProfile("user/vouches", w, params)
+}
+
type FollowCard struct {
UserDid string
LoggedInUser *oauth.MultiAccountUser
@@ -1125,15 +1141,16 @@ func (p *Pages) RepoSiteSettings(w io.Writer, params RepoSiteSettingsParams) err
}
type RepoIssuesParams struct {
- LoggedInUser *oauth.MultiAccountUser
- RepoInfo repoinfo.RepoInfo
- Active string
- Issues []models.Issue
- IssueCount int
- LabelDefs map[string]*models.LabelDefinition
- Page pagination.Page
- FilterState string
- FilterQuery string
+ LoggedInUser *oauth.MultiAccountUser
+ RepoInfo repoinfo.RepoInfo
+ Active string
+ Issues []models.Issue
+ IssueCount int
+ LabelDefs map[string]*models.LabelDefinition
+ Page pagination.Page
+ FilterState string
+ FilterQuery string
+ VouchRelationships map[syntax.DID]*models.VouchRelationship
}
func (p *Pages) RepoIssues(w io.Writer, params RepoIssuesParams) error {
@@ -1150,8 +1167,9 @@ type RepoSingleIssueParams struct {
Backlinks []models.RichReferenceLink
LabelDefs map[string]*models.LabelDefinition
- Reactions map[models.ReactionKind]models.ReactionDisplayData
- UserReacted map[models.ReactionKind]bool
+ Reactions map[models.ReactionKind]models.ReactionDisplayData
+ UserReacted map[models.ReactionKind]bool
+ VouchRelationships map[syntax.DID]*models.VouchRelationship
}
func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error {
@@ -1259,17 +1277,18 @@ func (p *Pages) RepoNewPull(w io.Writer, params RepoNewPullParams) error {
}
type RepoPullsParams struct {
- LoggedInUser *oauth.MultiAccountUser
- RepoInfo repoinfo.RepoInfo
- Pulls []*models.Pull
- Active string
- FilterState string
- FilterQuery string
- Stacks []models.Stack
- Pipelines map[string]models.Pipeline
- LabelDefs map[string]*models.LabelDefinition
- Page pagination.Page
- PullCount int
+ LoggedInUser *oauth.MultiAccountUser
+ RepoInfo repoinfo.RepoInfo
+ Pulls []*models.Pull
+ Active string
+ FilterState string
+ FilterQuery string
+ Stacks []models.Stack
+ Pipelines map[string]models.Pipeline
+ LabelDefs map[string]*models.LabelDefinition
+ Page pagination.Page
+ PullCount int
+ VouchRelationships map[syntax.DID]*models.VouchRelationship
}
func (p *Pages) RepoPulls(w io.Writer, params RepoPullsParams) error {
@@ -1314,7 +1333,8 @@ type RepoSinglePullParams struct {
Reactions map[models.ReactionKind]models.ReactionDisplayData
UserReacted map[models.ReactionKind]bool
- LabelDefs map[string]*models.LabelDefinition
+ LabelDefs map[string]*models.LabelDefinition
+ VouchRelationships map[syntax.DID]*models.VouchRelationship
}
func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error {
diff --git a/appview/pages/templates/fragments/icons/shield-direct-denounce.html b/appview/pages/templates/fragments/icons/shield-direct-denounce.html
new file mode 100644
index 00000000..0e18a59e
--- /dev/null
+++ b/appview/pages/templates/fragments/icons/shield-direct-denounce.html
@@ -0,0 +1,5 @@
+{{ define "fragments/icons/shield-direct-denounce" }}
+
+{{ end }}
diff --git a/appview/pages/templates/fragments/icons/shield-direct-vouch.html b/appview/pages/templates/fragments/icons/shield-direct-vouch.html
new file mode 100644
index 00000000..64fb9abc
--- /dev/null
+++ b/appview/pages/templates/fragments/icons/shield-direct-vouch.html
@@ -0,0 +1,5 @@
+{{ define "fragments/icons/shield-direct-vouch" }}
+
+{{ end }}
diff --git a/appview/pages/templates/fragments/icons/shield-empty.html b/appview/pages/templates/fragments/icons/shield-empty.html
new file mode 100644
index 00000000..afe078cb
--- /dev/null
+++ b/appview/pages/templates/fragments/icons/shield-empty.html
@@ -0,0 +1,5 @@
+{{ define "fragments/icons/shield-empty" }}
+
+{{ end }}
diff --git a/appview/pages/templates/fragments/icons/shield-indirect-denounce.html b/appview/pages/templates/fragments/icons/shield-indirect-denounce.html
new file mode 100644
index 00000000..84590ccb
--- /dev/null
+++ b/appview/pages/templates/fragments/icons/shield-indirect-denounce.html
@@ -0,0 +1,7 @@
+{{ define "fragments/icons/shield-indirect-denounce" }}
+
+{{ end }}
diff --git a/appview/pages/templates/fragments/icons/shield-indirect-vouch.html b/appview/pages/templates/fragments/icons/shield-indirect-vouch.html
new file mode 100644
index 00000000..9aa0ed49
--- /dev/null
+++ b/appview/pages/templates/fragments/icons/shield-indirect-vouch.html
@@ -0,0 +1,6 @@
+{{ define "fragments/icons/shield-indirect-vouch" }}
+
+{{ end }}
diff --git a/appview/pages/templates/fragments/icons/shield-mixed.html b/appview/pages/templates/fragments/icons/shield-mixed.html
new file mode 100644
index 00000000..5870e20f
--- /dev/null
+++ b/appview/pages/templates/fragments/icons/shield-mixed.html
@@ -0,0 +1,12 @@
+{{ define "fragments/icons/shield-mixed" }}
+
+{{ end }}
diff --git a/appview/pages/templates/fragments/icons/shield-plain.html b/appview/pages/templates/fragments/icons/shield-plain.html
new file mode 100644
index 00000000..c7fb9636
--- /dev/null
+++ b/appview/pages/templates/fragments/icons/shield-plain.html
@@ -0,0 +1,5 @@
+{{ define "fragments/icons/shield-plain" }}
+
+{{ end }}
diff --git a/appview/pages/templates/repo/issues/fragments/commentList.html b/appview/pages/templates/repo/issues/fragments/commentList.html
index 765c397c..39492331 100644
--- a/appview/pages/templates/repo/issues/fragments/commentList.html
+++ b/appview/pages/templates/repo/issues/fragments/commentList.html
@@ -14,7 +14,9 @@
"RepoInfo" $root.RepoInfo
"LoggedInUser" $root.LoggedInUser
"Issue" $root.Issue
- "Comment" $comment.Self) }}
+ "Comment" $comment.Self
+ "VouchRelationship" (index $root.VouchRelationships $comment.Self.Did)
+ ) }}
{{ template "topLevelComment" $params }}
@@ -28,8 +30,9 @@
"RepoInfo" $root.RepoInfo
"LoggedInUser" $root.LoggedInUser
"Issue" $root.Issue
- "Comment" $reply)
- }}
+ "Comment" $reply
+ "VouchRelationship" (index $root.VouchRelationships $reply.Did)
+ ) }}
{{ end }}
@@ -41,7 +44,7 @@
{{ define "topLevelComment" }}
- {{ template "user/fragments/picLink" (list .Comment.Did "size-8 mr-1") }}
+ {{ template "user/fragments/picLink" (list .Comment.Did "size-8 mr-1" .VouchRelationship) }}
{{ template "repo/issues/fragments/issueCommentHeader" . }}
@@ -53,7 +56,7 @@
{{ define "replyComment" }}
- {{ template "user/fragments/picLink" (list .Comment.Did "size-8 mr-1") }}
+ {{ template "user/fragments/picLink" (list .Comment.Did "size-8 mr-1" .VouchRelationship) }}
{{ template "repo/issues/fragments/issueCommentHeader" . }}
diff --git a/appview/pages/templates/repo/issues/fragments/issueListing.html b/appview/pages/templates/repo/issues/fragments/issueListing.html
index 3d892b53..39fafb07 100644
--- a/appview/pages/templates/repo/issues/fragments/issueListing.html
+++ b/appview/pages/templates/repo/issues/fragments/issueListing.html
@@ -26,8 +26,9 @@
{{ $state }}
-
- {{ template "user/fragments/picHandleLink" .Did }}
+
+ {{ template "user/fragments/picLink" (list .Did "size-6" (index $.VouchRelationships .Did)) }}
+ {{ resolve .Did }}
diff --git a/appview/pages/templates/repo/issues/issue.html b/appview/pages/templates/repo/issues/issue.html
index e17656f8..9b317552 100644
--- a/appview/pages/templates/repo/issues/issue.html
+++ b/appview/pages/templates/repo/issues/issue.html
@@ -68,7 +68,8 @@
opened by
- {{ template "user/fragments/picHandleLink" .Issue.Did }}
+ {{ template "user/fragments/picLink" (list .Issue.Did "size-6" (index .VouchRelationships .Issue.Did)) }}
+ {{ resolve .Issue.Did }}
{{ if .Issue.Edited }}
edited {{ template "repo/fragments/time" .Issue.Edited }}
@@ -118,7 +119,9 @@
"RepoInfo" $.RepoInfo
"LoggedInUser" $.LoggedInUser
"Issue" $.Issue
- "CommentList" $.Issue.CommentList)
+ "CommentList" $.Issue.CommentList
+ "VouchRelationships" $.VouchRelationships
+ )
}}
{{ template "repo/issues/fragments/newComment" . }}
diff --git a/appview/pages/templates/repo/issues/issues.html b/appview/pages/templates/repo/issues/issues.html
index ad9ea64e..3dbd89f0 100644
--- a/appview/pages/templates/repo/issues/issues.html
+++ b/appview/pages/templates/repo/issues/issues.html
@@ -65,7 +65,13 @@
{{ define "repoAfter" }}
- {{ template "repo/issues/fragments/issueListing" (dict "Issues" .Issues "RepoPrefix" .RepoInfo.FullName "LabelDefs" .LabelDefs) }}
+ {{ template "repo/issues/fragments/issueListing"
+ (dict
+ "Issues" .Issues
+ "RepoPrefix" .RepoInfo.FullName
+ "LabelDefs" .LabelDefs
+ "VouchRelationships" .VouchRelationships
+ ) }}
{{if gt .IssueCount .Page.Limit }}
{{ template "fragments/pagination" (dict
diff --git a/appview/pages/templates/repo/pulls/fragments/pullHeader.html b/appview/pages/templates/repo/pulls/fragments/pullHeader.html
index bd670842..36b6aae6 100644
--- a/appview/pages/templates/repo/pulls/fragments/pullHeader.html
+++ b/appview/pages/templates/repo/pulls/fragments/pullHeader.html
@@ -11,7 +11,8 @@
{{ template "repo/pulls/fragments/pullState" .Pull.State }}
opened by
- {{ template "user/fragments/picHandleLink" .Pull.OwnerDid }}
+ {{ template "user/fragments/picLink" (list .Pull.OwnerDid "size-6" (index .VouchRelationships .Pull.OwnerDid)) }}
+ {{ resolve .Pull.OwnerDid }}
{{ template "repo/fragments/time" .Pull.Created }}
diff --git a/appview/pages/templates/repo/pulls/pull.html b/appview/pages/templates/repo/pulls/pull.html
index 6d0773ab..177d7148 100644
--- a/appview/pages/templates/repo/pulls/pull.html
+++ b/appview/pages/templates/repo/pulls/pull.html
@@ -300,7 +300,7 @@
flex gap-2 sticky top-0 z-20">
- {{ template "user/fragments/picLink" (list $root.Pull.OwnerDid "size-8") }}
+ {{ template "user/fragments/picLink" (list $root.Pull.OwnerDid "size-8" (index $root.VouchRelationships $root.Pull.OwnerDid)) }}
@@ -592,7 +592,7 @@
{{ range $item.Comments }}
- {{ template "submissionComment" . }}
+ {{ template "submissionComment" (list . $root) }}
{{ end }}
{{ if gt $c 0}}
@@ -625,25 +625,27 @@
{{ end }}
{{ define "submissionComment" }}
-
{{ template "repo/pulls/fragments/pullState" $topPR.State }}
-
- {{ template "user/fragments/picHandleLink" $topPR.OwnerDid }}
+
+ {{ template "user/fragments/picLink" (list $topPR.OwnerDid "size-6" (index $.VouchRelationships $topPR.OwnerDid)) }}
+ {{ resolve $topPR.OwnerDid }}
diff --git a/appview/pages/templates/user/fragments/networkVouches.html b/appview/pages/templates/user/fragments/networkVouches.html
new file mode 100644
index 00000000..a8afe576
--- /dev/null
+++ b/appview/pages/templates/user/fragments/networkVouches.html
@@ -0,0 +1,42 @@
+{{/* Takes a VouchRelationship directly */}}
+{{ define "user/fragments/networkVouches" }}
+ {{ $vouches := (list) }}
+ {{ $denounces := (list) }}
+ {{ range .IndirectVouches }}
+ {{ if .IsVouch }}
+ {{ $vouches = append $vouches .Did }}
+ {{ else if .IsDenounce }}
+ {{ $denounces = append $denounces .Did }}
+ {{ end }}
+ {{ end }}
+
+
+ {{ if $vouches }}
+
+ {{ end }}
+
+ {{ if $denounces }}
+
+ {{ end }}
+
+{{ end }}
diff --git a/appview/pages/templates/user/fragments/picLink.html b/appview/pages/templates/user/fragments/picLink.html
index 096c7d9f..073af802 100644
--- a/appview/pages/templates/user/fragments/picLink.html
+++ b/appview/pages/templates/user/fragments/picLink.html
@@ -2,14 +2,46 @@
{{ $did := index . 0 }}
{{ $classes := index . 1 }}
{{ $handle := resolve $did }}
-
-
-
+ {{ $vr := false }}
+ {{ if ge (len .) 3 }}
+ {{ $vr = index . 2 }}
+ {{ end }}
+
+
+
+
+ {{ if $vr }}
+ {{ if ne $did $vr.ViewerDid }}
+
+ {{ end }}
+ {{ end }}
+
+ {{ if $vr }}
+ {{ if ne $did $vr.ViewerDid }}
+ {{ template "user/fragments/vouchPopover" (dict "VouchRelationship" $vr) }}
+ {{ end }}
+ {{ end }}
{{ end }}
-
-
-
diff --git a/appview/pages/templates/user/fragments/vouchButton.html b/appview/pages/templates/user/fragments/vouchButton.html
new file mode 100644
index 00000000..dbc76e84
--- /dev/null
+++ b/appview/pages/templates/user/fragments/vouchButton.html
@@ -0,0 +1,30 @@
+{{ define "user/fragments/vouchButton" }}
+{{ $isVouched := false }}
+{{ $isDenounced := false }}
+{{ if .VouchRelationship }}
+ {{ if .VouchRelationship.IsDirectVouch }}
+ {{ $isVouched = true }}
+ {{ else if .VouchRelationship.IsDirectDenounce }}
+ {{ $isDenounced = true }}
+ {{ end }}
+{{ end }}
+
+{{ $userDid := "" }}
+{{ with .VouchRelationship }}
+ {{ $userDid = .SubjectDid }}
+{{ end }}
+
+{{ end }}
diff --git a/appview/pages/templates/user/fragments/vouchPopover.html b/appview/pages/templates/user/fragments/vouchPopover.html
new file mode 100644
index 00000000..f98f3fa2
--- /dev/null
+++ b/appview/pages/templates/user/fragments/vouchPopover.html
@@ -0,0 +1,151 @@
+{{ define "user/fragments/vouchPopover" }}
+{{ $isVouched := false }}
+{{ $isDenounced := false }}
+{{ $isUndecided := false }}
+{{ if .VouchRelationship }}
+ {{ if .VouchRelationship.IsDirectVouch }}
+ {{ $isVouched = true }}
+ {{ else if .VouchRelationship.IsDirectDenounce }}
+ {{ $isDenounced = true }}
+ {{ else }}
+ {{ $isUndecided = true }}
+ {{ end }}
+{{ else }}
+ {{ $isUndecided = true }}
+{{ end }}
+
+{{ $userIdent := "" }}
+{{ $userDid := "" }}
+{{ with .VouchRelationship }}
+ {{ $userDid = .SubjectDid }}
+ {{ $userIdent = resolve .SubjectDid }}
+{{ end }}
+
+{{ end }}
diff --git a/appview/pulls/pulls.go b/appview/pulls/pulls.go
index 3faf8495..d6bb8d01 100644
--- a/appview/pulls/pulls.go
+++ b/appview/pulls/pulls.go
@@ -266,6 +266,15 @@ func (s *Pulls) repoPullHelper(w http.ResponseWriter, r *http.Request, interdiff
defs[l.AtUri().String()] = &l
}
+ vouchRelationships := make(map[syntax.DID]*models.VouchRelationship)
+ if user != nil {
+ participants := pull.Participants()
+ vouchRelationships, err = db.GetVouchRelationshipsBatch(s.db, syntax.DID(user.Did), participants)
+ if err != nil {
+ l.Error("failed to fetch vouch relationships", "err", err)
+ }
+ }
+
patch := pull.Submissions[roundIdInt].CombinedPatch()
var diff types.DiffRenderer
diff = patchutil.AsNiceDiff(patch, pull.TargetBranch)
@@ -306,7 +315,8 @@ func (s *Pulls) repoPullHelper(w http.ResponseWriter, r *http.Request, interdiff
Reactions: reactionMap,
UserReacted: userReactions,
- LabelDefs: defs,
+ LabelDefs: defs,
+ VouchRelationships: vouchRelationships,
})
}
@@ -782,17 +792,30 @@ func (s *Pulls) RepoPulls(w http.ResponseWriter, r *http.Request) {
filterState = state.String()
}
+ vouchRelationships := make(map[syntax.DID]*models.VouchRelationship)
+ if user != nil {
+ dids := make([]syntax.DID, len(pulls))
+ for i, p := range pulls {
+ dids[i] = syntax.DID(p.OwnerDid)
+ }
+ vouchRelationships, err = db.GetVouchRelationshipsBatch(s.db, syntax.DID(user.Did), dids)
+ if err != nil {
+ l.Error("failed to fetch vouch relationships", "err", err)
+ }
+ }
+
s.pages.RepoPulls(w, pages.RepoPullsParams{
- LoggedInUser: s.oauth.GetMultiAccountUser(r),
- RepoInfo: repoInfo,
- Pulls: pulls,
- LabelDefs: defs,
- FilterState: filterState,
- FilterQuery: query.String(),
- Stacks: stacks,
- Pipelines: m,
- Page: page,
- PullCount: totalPulls,
+ LoggedInUser: s.oauth.GetMultiAccountUser(r),
+ RepoInfo: repoInfo,
+ Pulls: pulls,
+ LabelDefs: defs,
+ FilterState: filterState,
+ FilterQuery: query.String(),
+ Stacks: stacks,
+ Pipelines: m,
+ Page: page,
+ PullCount: totalPulls,
+ VouchRelationships: vouchRelationships,
})
}
diff --git a/appview/state/profile.go b/appview/state/profile.go
index 8f2d5468..88600455 100644
--- a/appview/state/profile.go
+++ b/appview/state/profile.go
@@ -43,6 +43,10 @@ func (s *State) Profile(w http.ResponseWriter, r *http.Request) {
ServeHTTP(w, r)
case "strings":
s.stringsPage(w, r)
+ case "vouches":
+ middleware.
+ Paginate(http.HandlerFunc(s.vouchesPage)).
+ ServeHTTP(w, r)
default:
s.profileOverview(w, r)
}
@@ -93,9 +97,12 @@ func (s *State) profile(r *http.Request) (*pages.ProfileCard, error) {
loggedInUser := s.oauth.GetMultiAccountUser(r)
followStatus := models.IsNotFollowing
var loggedInDid string
+ var vouchRelationship *models.VouchRelationship
+
if loggedInUser != nil {
followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, did)
loggedInDid = loggedInUser.Did
+ vouchRelationship, err = db.GetVouchRelationship(s.db, syntax.DID(loggedInUser.Did), syntax.DID(did))
}
showPunchcard := s.shouldShowPunchcard(did, loggedInDid)
@@ -116,10 +123,11 @@ func (s *State) profile(r *http.Request) (*pages.ProfileCard, error) {
}
return &pages.ProfileCard{
- UserDid: did,
- HasProfile: hasProfile,
- Profile: profile,
- FollowStatus: followStatus,
+ UserDid: did,
+ HasProfile: hasProfile,
+ Profile: profile,
+ FollowStatus: followStatus,
+ VouchRelationship: vouchRelationship,
Stats: pages.ProfileStats{
RepoCount: repoCount,
StringCount: stringCount,
@@ -177,13 +185,16 @@ func (s *State) profileOverview(w http.ResponseWriter, r *http.Request) {
l.Error("failed to create timeline", "err", err)
}
- s.pages.ProfileOverview(w, pages.ProfileOverviewParams{
+ err = s.pages.ProfileOverview(w, pages.ProfileOverviewParams{
LoggedInUser: s.oauth.GetMultiAccountUser(r),
Card: profile,
Repos: pinnedRepos,
CollaboratingRepos: pinnedCollaboratingRepos,
ProfileTimeline: timeline,
})
+ if err != nil {
+ l.Error("failed to render template", "err", err)
+ }
}
func (s *State) shouldShowPunchcard(targetDid, requesterDid string) bool {
@@ -380,6 +391,65 @@ func (s *State) stringsPage(w http.ResponseWriter, r *http.Request) {
})
}
+func (s *State) vouchesPage(w http.ResponseWriter, r *http.Request) {
+ l := s.logger.With("handler", "vouchesPage")
+
+ profile, err := s.profile(r)
+ if err != nil {
+ l.Error("failed to build profile card", "err", err)
+ s.pages.Error500(w)
+ return
+ }
+ l = l.With("profileDid", profile.UserDid)
+
+ loggedInUser := s.oauth.GetMultiAccountUser(r)
+ page := pagination.FromContext(r.Context())
+
+ var vouches []models.Vouch
+ if loggedInUser != nil {
+ vouches, err = db.GetNetworkVouchTimeline(s.db, loggedInUser.Did, profile.UserDid, page)
+ if err != nil {
+ l.Error("failed to get vouch timeline", "err", err)
+ s.pages.Error500(w)
+ return
+ }
+ }
+
+ var suggestions []models.VouchSuggestion
+ if loggedInUser != nil && loggedInUser.Did == profile.UserDid {
+ suggestions, err = db.GetVouchSuggestions(s.db, profile.UserDid, 5)
+ if err != nil {
+ l.Error("failed to get vouch suggestions", "err", err)
+ }
+
+ if len(suggestions) > 0 {
+ suggestionDids := make([]syntax.DID, len(suggestions))
+ for i, s := range suggestions {
+ suggestionDids[i] = syntax.DID(s.Did)
+ }
+ relationships, err := db.GetVouchRelationshipsBatch(s.db, syntax.DID(loggedInUser.Did), suggestionDids)
+ if err != nil {
+ l.Error("failed to get vouch relationships for suggestions", "err", err)
+ } else {
+ for i := range suggestions {
+ suggestions[i].VouchRelationship = relationships[suggestions[i].Did]
+ }
+ }
+ }
+ }
+
+ err = s.pages.ProfileVouches(w, pages.ProfileVouchesParams{
+ LoggedInUser: loggedInUser,
+ Vouches: vouches,
+ Suggestions: suggestions,
+ Card: profile,
+ Page: page,
+ })
+ if err != nil {
+ l.Error("failed to render page", "err", err)
+ }
+}
+
type FollowsPageParams struct {
Follows []pages.FollowCard
Card *pages.ProfileCard
diff --git a/input.css b/input.css
index dc094b35..023b7866 100644
--- a/input.css
+++ b/input.css
@@ -147,6 +147,15 @@
disabled:before:bg-green-400 dark:disabled:before:bg-green-600;
}
+ .btn-cancel {
+ @apply btn text-white
+ before:bg-red-600 hover:before:bg-red-700
+ dark:before:bg-red-700 dark:hover:before:bg-red-800
+ before:border before:border-red-700 hover:before:border-red-800
+ focus-visible:before:outline-red-500
+ disabled:before:bg-red-400 dark:disabled:before:bg-red-600;
+ }
+
.prose {
overflow-wrap: anywhere;
}