Signed-off-by: oppiliappan me@oppi.li
+100
-29
Diff
round #1
+9
appview/db/vouch.go
+9
appview/db/vouch.go
···
308
308
return batch[subjectDid], nil
309
309
}
310
310
311
+
func IsVouchSkipped(e Execer, did, subjectDid string) (bool, error) {
312
+
var exists bool
313
+
err := e.QueryRow(
314
+
`select exists(select 1 from vouch_skips where did = ? and subject_did = ?)`,
315
+
did, subjectDid,
316
+
).Scan(&exists)
317
+
return exists, err
318
+
}
319
+
311
320
func SkipVouchSuggestion(e Execer, did, subjectDid string) error {
312
321
_, err := e.Exec(
313
322
`insert or ignore into vouch_skips (did, subject_did) values (?, ?)`,
+1
appview/pages/pages.go
+1
appview/pages/pages.go
+38
appview/pages/templates/repo/pulls/fragments/pullVouchNudge.html
+38
appview/pages/templates/repo/pulls/fragments/pullVouchNudge.html
···
1
+
{{ define "repo/pulls/fragments/pullVouchNudge" }}
2
+
{{ $owner := .Pull.OwnerDid }}
3
+
{{ $vr := index .VouchRelationships (did $owner) }}
4
+
{{ $skipped := index .VouchSkips (did $owner) }}
5
+
6
+
{{ if and
7
+
.LoggedInUser
8
+
(ne .LoggedInUser.Did $owner)
9
+
(or .Pull.State.IsOpen .Pull.State.IsMerged)
10
+
(or (not $vr) $vr.IsEmpty)
11
+
(not $skipped) }}
12
+
13
+
<div class="border border-gray-200 dark:border-gray-700 rounded px-2 pl-4 py-2 flex items-center gap-4 text-sm text-gray-600 dark:text-gray-300 bg-white dark:bg-gray-800">
14
+
<span class="flex-1 flex items-center gap-2 flex-wrap">
15
+
{{ i "shield-question-mark" "size-4" }}
16
+
Would you like to vouch for
17
+
<a href="/{{ resolve $owner }}" class="inline-flex items-center gap-1 font-medium text-gray-900 dark:text-white hover:underline">
18
+
<img src="{{ tinyAvatar $owner }}" class="rounded-full size-4 inline" />
19
+
{{ resolve $owner }}
20
+
</a>
21
+
for their contribution?
22
+
</span>
23
+
<button
24
+
hx-post="/vouch/skip?subject={{ $owner }}"
25
+
hx-trigger="click"
26
+
hx-disabled-elt="this"
27
+
title="Skip suggestion"
28
+
class="group shrink-0 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 p-0.5"
29
+
>
30
+
<span class="group-[.htmx-request]:hidden">dismiss</span>
31
+
{{ i "loader-circle" "size-4 animate-spin hidden group-[.htmx-request]:block" }}
32
+
</button>
33
+
<div class="shrink-0">
34
+
{{ template "user/fragments/vouch" (dict "VouchRelationship" $vr "Evidences" (list .Pull.AtUri.String) "PopoverId" (printf "vouch-nudge-modal-%s" (normalizeForHtmlId $owner))) }}
35
+
</div>
36
+
</div>
37
+
{{ end }}
38
+
{{ end }}
+6
-3
appview/pages/templates/repo/pulls/pull.html
+6
-3
appview/pages/templates/repo/pulls/pull.html
···
80
80
81
81
{{ define "repoContentLayout" }}
82
82
<div class="grid grid-cols-1 md:grid-cols-10 gap-4 w-full">
83
-
<section class="bg-white col-span-1 md:col-span-8 dark:bg-gray-800 p-6 rounded relative w-full mx-auto dark:text-white h-full flex-shrink">
84
-
{{ block "repoContent" . }}{{ end }}
85
-
</section>
83
+
<div class="col-span-1 md:col-span-8 flex flex-col gap-4">
84
+
<section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto dark:text-white h-full flex-shrink">
85
+
{{ block "repoContent" . }}{{ end }}
86
+
</section>
87
+
{{ template "repo/pulls/fragments/pullVouchNudge" . }}
88
+
</div>
86
89
<div class="flex flex-col gap-6 col-span-1 md:col-span-2">
87
90
{{ template "repo/fragments/labelPanel"
88
91
(dict "RepoInfo" $.RepoInfo
+1
-1
appview/pages/templates/timeline/fragments/vouchSuggestions.html
+1
-1
appview/pages/templates/timeline/fragments/vouchSuggestions.html
···
19
19
<span class="text-xs text-gray-500 dark:text-gray-400 truncate" title="{{ .Reason }}">{{ .Reason }}</span>
20
20
</div>
21
21
<div class="shrink-0">
22
-
{{ template "user/fragments/vouch" . }}
22
+
{{ template "user/fragments/vouch" (dict "VouchRelationship" .VouchRelationship) }}
23
23
</div>
24
24
</div>
25
25
{{ end }}
+1
-1
appview/pages/templates/user/fragments/profileCard.html
+1
-1
appview/pages/templates/user/fragments/profileCard.html
···
109
109
</div>
110
110
111
111
{{ if ne .FollowStatus.String "IsSelf" }}
112
-
{{ template "user/fragments/vouch" . }}
112
+
{{ template "user/fragments/vouch" (dict "VouchRelationship" .VouchRelationship) }}
113
113
114
114
{{ if .VouchRelationship }}
115
115
{{ if .VouchRelationship.IndirectVouches }}
+2
-2
appview/pages/templates/user/fragments/profilePopover.html
+2
-2
appview/pages/templates/user/fragments/profilePopover.html
···
39
39
{{ if and .LoggedInUser (ne .FollowStatus.String "IsSelf") }}
40
40
<div class="flex items-center gap-2">
41
41
{{ template "user/fragments/follow" . }}
42
-
{{ template "user/fragments/vouchButton" . }}
42
+
{{ template "user/fragments/vouchButton" (dict "VouchRelationship" .VouchRelationship) }}
43
43
</div>
44
44
45
45
{{ if .VouchRelationship }}
···
54
54
55
55
{{/* render the vouch modal outside the popover content div so the popover api places it in the top layer correctly */}}
56
56
{{ if and .LoggedInUser (ne .FollowStatus.String "IsSelf") }}
57
-
{{ template "user/fragments/vouchPopover" . }}
57
+
{{ template "user/fragments/vouchPopover" (dict "VouchRelationship" .VouchRelationship) }}
58
58
{{ end }}
59
59
{{ end }}
+4
-1
appview/pages/templates/user/fragments/vouch.html
+4
-1
appview/pages/templates/user/fragments/vouch.html
···
1
1
{{ define "user/fragments/vouch" }}
2
2
<div class="relative">
3
+
{{ $vr := index . "VouchRelationship" }}
4
+
{{ $ev := index . "Evidences" }}
5
+
{{ $popoverId := index . "PopoverId" }}
3
6
{{ template "user/fragments/vouchButton" . }}
4
-
{{ template "user/fragments/vouchPopover" . }}
7
+
{{ template "user/fragments/vouchPopover" (dict "VouchRelationship" $vr "Evidences" $ev "PopoverId" $popoverId) }}
5
8
</div>
6
9
{{ end }}
+3
-1
appview/pages/templates/user/fragments/vouchButton.html
+3
-1
appview/pages/templates/user/fragments/vouchButton.html
···
13
13
{{ with .VouchRelationship }}
14
14
{{ $userDid = .SubjectDid.String }}
15
15
{{ end }}
16
+
{{ $popoverId := index . "PopoverId" }}
17
+
{{ if not $popoverId }}{{ $popoverId = printf "vouch-modal-%s" (normalizeForHtmlId $userDid) }}{{ end }}
16
18
<button
17
19
id="vouch-btn-{{ normalizeForHtmlId $userDid }}"
18
20
type="button"
19
-
popovertarget="vouch-modal-{{ normalizeForHtmlId $userDid }}"
21
+
popovertarget="{{ $popoverId }}"
20
22
popovertargetaction="toggle"
21
23
class="{{ if $isVouched }}btn-create{{else if $isDenounced}}btn-cancel{{else}}btn{{end}} w-full flex gap-2 items-center justify-center">
22
24
{{ if $isVouched }}
+26
-19
appview/pages/templates/user/fragments/vouchPopover.html
+26
-19
appview/pages/templates/user/fragments/vouchPopover.html
···
1
1
{{ define "user/fragments/vouchPopover" }}
2
+
{{ $vr := index . "VouchRelationship" }}
3
+
{{ $ev := index . "Evidences" }}
2
4
{{ $isVouched := false }}
3
5
{{ $isDenounced := false }}
4
6
{{ $isUndecided := false }}
5
-
{{ if .VouchRelationship }}
6
-
{{ if .VouchRelationship.IsDirectVouch }}
7
+
{{ if $vr }}
8
+
{{ if $vr.IsDirectVouch }}
7
9
{{ $isVouched = true }}
8
-
{{ else if .VouchRelationship.IsDirectDenounce }}
10
+
{{ else if $vr.IsDirectDenounce }}
9
11
{{ $isDenounced = true }}
10
12
{{ else }}
11
13
{{ $isUndecided = true }}
···
16
18
17
19
{{ $userIdent := "" }}
18
20
{{ $userDid := "" }}
19
-
{{ with .VouchRelationship }}
21
+
{{ with $vr }}
20
22
{{ $userDid = .SubjectDid.String }}
21
23
{{ $userIdent = resolve .SubjectDid.String }}
22
24
{{ end }}
25
+
{{ $popoverId := index . "PopoverId" }}
26
+
{{ if not $popoverId }}{{ $popoverId = printf "vouch-modal-%s" (normalizeForHtmlId $userDid) }}{{ end }}
23
27
<div
24
-
id="vouch-modal-{{ normalizeForHtmlId $userDid }}"
28
+
id="{{ $popoverId }}"
25
29
popover
26
30
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
31
···
34
38
<div>
35
39
<p class="font-semibold mb-1 text-base">Vouch for {{ $userIdent }}</p>
36
40
<p class="text-gray-600 dark:text-gray-400 text-sm">
37
-
{{ with .VouchRelationship }}
41
+
{{ with $vr }}
38
42
{{ with .GetDirectVouch }}
39
43
You {{if $isVouched}}vouched{{else}}denounced{{end}} {{ $userIdent }} {{ relTimeFmt .CreatedAt }}.
40
44
You can change your decision below.
···
46
50
</p>
47
51
</div>
48
52
49
-
{{ if .VouchRelationship }}
50
-
{{ if .VouchRelationship.IndirectVouches }}
53
+
{{ if $vr }}
54
+
{{ if $vr.IndirectVouches }}
51
55
<div>
52
56
<p class="font-semibold mb-1 text-sm">From your network:</p>
53
-
{{ template "user/fragments/networkVouches" .VouchRelationship }}
57
+
{{ template "user/fragments/networkVouches" $vr }}
54
58
</div>
55
59
{{ end }}
56
60
{{ end }}
57
61
58
62
<input type="hidden" name="subject" value="{{ $userDid }}" />
63
+
{{ range $ev }}
64
+
<input type="hidden" name="evidences" value="{{ . }}" />
65
+
{{ end }}
59
66
60
67
{{ $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
68
62
69
<div class="grid grid-cols-3 gap-2">
63
70
<input
64
-
id="vouch-{{ normalizeForHtmlId $userDid }}"
71
+
id="vouch-{{ $popoverId }}"
65
72
type="radio"
66
73
name="kind"
67
74
value="vouch"
68
75
{{ if $isVouched }}checked{{ end }}
69
76
class="peer/vouch hidden appearance-none" />
70
77
<input
71
-
id="denounce-{{ normalizeForHtmlId $userDid }}"
78
+
id="denounce-{{ $popoverId }}"
72
79
type="radio"
73
80
name="kind"
74
81
value="denounce"
75
82
{{ if $isDenounced }}checked{{ end }}
76
83
class="peer/denounce hidden appearance-none" />
77
84
<input
78
-
id="none-{{ normalizeForHtmlId $userDid }}"
85
+
id="none-{{ $popoverId }}"
79
86
type="radio"
80
87
name="kind"
81
88
value="none"
···
83
90
class="peer/none hidden appearance-none" />
84
91
85
92
<label
86
-
for="vouch-{{ normalizeForHtmlId $userDid }}"
93
+
for="vouch-{{ $popoverId }}"
87
94
class="
88
95
{{ $labelClass }}
89
96
hover:bg-gray-100 peer-checked/vouch:bg-green-200 peer-checked/vouch:text-green-800 peer-checked/vouch:ring-green-400
···
92
99
<span class="text-sm font-medium">Vouch</span>
93
100
</label>
94
101
<label
95
-
for="denounce-{{ normalizeForHtmlId $userDid }}"
102
+
for="denounce-{{ $popoverId }}"
96
103
class="
97
104
{{ $labelClass }}
98
105
hover:bg-gray-100 peer-checked/denounce:bg-red-200 peer-checked/denounce:text-red-800 peer-checked/denounce:ring-red-400
···
101
108
<span class="text-sm font-medium">Denounce</span>
102
109
</label>
103
110
<label
104
-
for="none-{{ normalizeForHtmlId $userDid }}"
111
+
for="none-{{ $popoverId }}"
105
112
class="
106
113
{{ $labelClass }}
107
114
hover:bg-gray-100 peer-checked/none:bg-gray-200 peer-checked/none:text-gray-800 peer-checked/none:ring-gray-400
···
111
118
</label>
112
119
113
120
<textarea
114
-
id="reason-{{ normalizeForHtmlId $userDid }}"
121
+
id="reason-{{ $popoverId }}"
115
122
name="reason"
116
123
rows="1"
117
124
maxlength="300"
118
125
placeholder="Write a reason..."
119
126
class="w-full resize-none col-span-3 peer-checked/none:hidden">
120
-
{{- with .VouchRelationship -}}
127
+
{{- with $vr -}}
121
128
{{- with .GetDirectVouch -}}
122
129
{{- with .Reason -}}
123
130
{{- . -}}
···
130
137
<div class="flex gap-2">
131
138
<button
132
139
type="button"
133
-
popovertarget="vouch-modal-{{ normalizeForHtmlId $userDid }}"
140
+
popovertarget="{{ $popoverId }}"
134
141
popovertargetaction="hide"
135
142
class="btn flex-1 flex items-center justify-center gap-2">
136
143
{{ i "x" "size-4" }}
···
141
148
class="btn-create flex-1 flex items-center justify-center gap-2 text-white group">
142
149
{{ i "check" "size-4 inline group-[.htmx-request]:hidden" }}
143
150
{{ i "loader-circle" "size-4 animate-spin hidden group-[.htmx-request]:inline" }}
144
-
{{ if and .VouchRelationship .VouchRelationship.GetDirectVouch }} update {{ else }} save {{end}}
151
+
{{ if and $vr $vr.GetDirectVouch }} update {{ else }} save {{end}}
145
152
</button>
146
153
</div>
147
154
</form>
+1
-1
appview/pages/templates/user/vouches.html
+1
-1
appview/pages/templates/user/vouches.html
···
50
50
<span class="text-sm text-gray-500 dark:text-gray-400">{{ .Reason }}</span>
51
51
</div>
52
52
<div class="shrink-0 w-32">
53
-
{{ template "user/fragments/vouch" . }}
53
+
{{ template "user/fragments/vouch" (dict "VouchRelationship" .VouchRelationship) }}
54
54
</div>
55
55
<button
56
56
hx-post="/vouch/skip?subject={{ .Did.String }}"
+8
appview/pulls/single.go
+8
appview/pulls/single.go
···
189
189
}
190
190
191
191
vouchRelationships := make(map[syntax.DID]*models.VouchRelationship)
192
+
vouchSkips := make(map[syntax.DID]bool)
192
193
if user != nil {
193
194
participants := pull.Participants()
194
195
vouchRelationships, err = db.GetVouchRelationshipsBatch(s.db, syntax.DID(user.Did), participants)
195
196
if err != nil {
196
197
l.Error("failed to fetch vouch relationships", "err", err)
197
198
}
199
+
ownerDid := syntax.DID(pull.OwnerDid)
200
+
skipped, err := db.IsVouchSkipped(s.db, user.Did, pull.OwnerDid)
201
+
if err != nil {
202
+
l.Error("failed to check vouch skip", "err", err)
203
+
}
204
+
vouchSkips[ownerDid] = skipped
198
205
}
199
206
200
207
patch := pull.Submissions[roundIdInt].CombinedPatch()
···
239
246
240
247
LabelDefs: defs,
241
248
VouchRelationships: vouchRelationships,
249
+
VouchSkips: vouchSkips,
242
250
})
243
251
if err != nil {
244
252
l.Error("failed to render page", "err", err)