Signed-off-by: oppiliappan me@oppi.li
+132
-38
Diff
round #2
+6
-5
appview/pages/pages.go
+6
-5
appview/pages/pages.go
···
402
402
}
403
403
404
404
type TimelineParams struct {
405
-
LoggedInUser *oauth.MultiAccountUser
406
-
Timeline []models.TimelineEvent
407
-
Repos []models.Repo
408
-
GfiLabel *models.LabelDefinition
409
-
BlueskyPosts []models.BskyPost
405
+
LoggedInUser *oauth.MultiAccountUser
406
+
Timeline []models.TimelineEvent
407
+
Repos []models.Repo
408
+
GfiLabel *models.LabelDefinition
409
+
BlueskyPosts []models.BskyPost
410
+
VouchSuggestions []models.VouchSuggestion
410
411
// ShowNewsletter controls whether the newsletter widget/CTA is rendered.
411
412
// For logged-in users it reflects their newsletter_preferences row; for
412
413
// anonymous visitors it is always true (dismissal falls back to
+39
appview/pages/templates/timeline/fragments/announcements.html
+39
appview/pages/templates/timeline/fragments/announcements.html
···
1
+
{{ define "timeline/fragments/announcements" }}
2
+
<div>
3
+
<h3 class="text-xl font-bold dark:text-white hidden md:flex items-center gap-4 px-4 pb-4">
4
+
{{ i "megaphone" "size-5 flex-shrink-0" }}
5
+
Announcements
6
+
</h3>
7
+
<div class="flex flex-col gap-3">
8
+
{{ template "vouchAnnouncement" . }}
9
+
</div>
10
+
</div>
11
+
{{ end }}
12
+
13
+
{{ define "vouchAnnouncement" }}
14
+
<div class="relative overflow-hidden border border-teal-300 dark:border-teal-600 bg-gradient-to-b from-teal-50 to-teal-100 dark:from-teal-900 dark:to-teal-800 rounded-sm p-6">
15
+
<div class="relative z-10">
16
+
<p class="font-semibold text-teal-800 dark:text-teal-300 mb-1">Build a web of trust</p>
17
+
<p class="text-teal-700 dark:text-teal-400">
18
+
Vouch for trustworthy users that make open-source a better place. Visit a user's
19
+
profile to vouch for them.
20
+
</p>
21
+
22
+
<div class="mt-3 flex items-center justify-between">
23
+
<a class="mt-3 no-underline inline-flex items-center gap-1 text-sm font-medium text-teal-700 dark:text-teal-300 hover:text-teal-900 dark:hover:text-teal-100 hover:underline" href="/vouching">
24
+
Read more
25
+
</a>
26
+
{{ if and .LoggedInUser .VouchSuggestions }}
27
+
<a class="md:hidden mt-3 no-underline inline-flex items-center gap-1 text-sm font-medium text-teal-700 dark:text-teal-300 hover:text-teal-900 dark:hover:text-teal-100 hover:underline" href="/{{ .LoggedInUser.Did }}?tab=vouches">
28
+
View suggestions {{ i "arrow-right" "size-3.5" }}
29
+
</a>
30
+
{{ end }}
31
+
</div>
32
+
33
+
</div>
34
+
35
+
<div class="pointer-events-none absolute -bottom-8 -right-16 text-teal-200 dark:text-teal-700 opacity-60">
36
+
{{ i "shield-plus" "size-48" }}
37
+
</div>
38
+
</div>
39
+
{{ end }}
+4
-3
appview/pages/templates/timeline/fragments/timeline.html
+4
-3
appview/pages/templates/timeline/fragments/timeline.html
···
1
1
{{ define "timeline/fragments/timeline" }}
2
2
<div class="py-4">
3
-
<div class="px-6 pb-4">
4
-
<p class="text-xl font-bold dark:text-white">Timeline</p>
5
-
</div>
3
+
<h3 class="text-xl font-bold dark:text-white flex items-center gap-4 px-4 pb-4">
4
+
{{ i "gallery-vertical" "size-5 flex-shrink-0" }}
5
+
Timeline
6
+
</h3>
6
7
7
8
<div class="flex flex-col gap-4">
8
9
{{ range $i, $e := .Timeline }}
+12
-12
appview/pages/templates/timeline/fragments/trending.html
+12
-12
appview/pages/templates/timeline/fragments/trending.html
···
1
1
{{ define "timeline/fragments/trending" }}
2
-
<div class="pt-4">
3
-
<h3 class="text-xl font-bold dark:text-white flex items-center gap-2 px-6 pb-4">
4
-
Trending
5
-
{{ i "trending-up" "size-4 flex-shrink-0" }}
2
+
<div>
3
+
<h3 class="text-xl font-bold dark:text-white flex items-center gap-4 px-4 pb-4">
4
+
{{ i "trending-up" "size-5 flex-shrink-0" }}
5
+
Trending
6
6
</h3>
7
7
{{ if .Repos }}
8
-
<div class="flex gap-4 overflow-x-auto scrollbar-hide items-stretch lg:flex-col lg:overflow-x-visible">
9
-
{{ range $index, $repo := .Repos }}
10
-
<div class="flex-none w-96 border border-gray-200 dark:border-gray-700 rounded-sm lg:flex-none lg:w-auto">
11
-
{{ template "user/fragments/repoCard" (list $ $repo true) }}
12
-
</div>
13
-
{{ end }}
8
+
<div class="flex gap-4 overflow-x-auto scrollbar-hide items-stretch md:flex-col md:overflow-x-visible">
9
+
{{ range $index, $repo := .Repos }}
10
+
<div class="flex-none w-96 border border-gray-200 dark:border-gray-700 rounded-sm md:flex-none md:w-auto">
11
+
{{ template "user/fragments/repoCard" (list $ $repo true) }}
12
+
</div>
13
+
{{ end }}
14
14
</div>
15
15
{{ else }}
16
16
<div class="p-6 border border-gray-200 dark:border-gray-700 rounded-sm text-sm text-gray-500 dark:text-gray-400 text-center">
17
-
No trending repositories this week
17
+
No trending repositories this week
18
18
</div>
19
19
{{ end }}
20
-
</div>
20
+
</div>
21
21
{{ end }}
+24
appview/pages/templates/timeline/fragments/vouchSuggestions.html
+24
appview/pages/templates/timeline/fragments/vouchSuggestions.html
···
1
+
{{ define "timeline/fragments/vouchSuggestions" }}
2
+
{{ if and .LoggedInUser .VouchSuggestions }}
3
+
<div>
4
+
<h3 class="text-xl font-bold dark:text-white flex items-center gap-4 px-4 pb-4">
5
+
{{ i "shield-plus" "size-5 flex-shrink-0" }}
6
+
Suggested
7
+
</h3>
8
+
<div class="flex flex-col gap-4 bg-white dark:bg-gray-800 p-4 border border-gray-200 dark:border-gray-700 shadow-sm">
9
+
{{ range .VouchSuggestions }}
10
+
<div class="flex items-center gap-3 ">
11
+
{{ template "user/fragments/picLink" (list .Did.String "size-10") }}
12
+
<div class="flex flex-col min-w-0 flex-1">
13
+
<a href="/{{ resolve .Did.String }}" class="text-sm font-medium dark:text-white hover:underline truncate">{{ resolve .Did.String }}</a>
14
+
<span class="text-xs text-gray-500 dark:text-gray-400 truncate" title="{{ .Reason }}">{{ .Reason }}</span>
15
+
</div>
16
+
<div class="shrink-0">
17
+
{{ template "user/fragments/vouch" . }}
18
+
</div>
19
+
</div>
20
+
{{ end }}
21
+
</div>
22
+
</div>
23
+
{{ end }}
24
+
{{ end }}
+8
-4
appview/pages/templates/timeline/timeline.html
+8
-4
appview/pages/templates/timeline/timeline.html
···
16
16
{{ end }}
17
17
18
18
{{ define "content" }}
19
-
<div id="timeline-grid" class="flex flex-col gap-4 lg:grid lg:grid-cols-3">
19
+
<div id="timeline-grid" class="flex flex-col gap-4 md:grid md:grid-cols-3">
20
20
21
-
<div class="order-2 lg:order-none lg:col-span-2 lg:row-start-1">
21
+
<div class="order-3 md:order-none md:col-span-2 md:row-start-1">
22
22
{{ template "timeline/fragments/timeline" . }}
23
23
</div>
24
24
25
-
<div class="order-1 lg:order-none flex flex-col gap-6 lg:col-start-3 lg:row-start-1">
25
+
<div class="order-1 md:order-none flex flex-col gap-6 md:col-start-3 md:row-start-1 md:pt-4">
26
26
{{ if .ShowNewsletter }}
27
-
<div id="newsletter-col" class="order-first lg:order-last">
27
+
<div id="newsletter-col" class="order-first md:order-last">
28
28
{{ template "timeline/fragments/newsletterWidget" . }}
29
29
</div>
30
30
{{ end }}
31
+
{{ template "timeline/fragments/announcements" . }}
32
+
<div class="hidden md:block">
33
+
{{ template "timeline/fragments/vouchSuggestions" . }}
34
+
</div>
31
35
{{ template "timeline/fragments/trending" . }}
32
36
</div>
33
37
</div>
+4
-4
appview/pages/templates/user/fragments/networkVouches.html
+4
-4
appview/pages/templates/user/fragments/networkVouches.html
···
4
4
{{ $denounces := (list) }}
5
5
{{ range .IndirectVouches }}
6
6
{{ if .IsVouch }}
7
-
{{ $vouches = append $vouches .Did }}
7
+
{{ $vouches = append $vouches .Did.String }}
8
8
{{ else if .IsDenounce }}
9
-
{{ $denounces = append $denounces .Did }}
9
+
{{ $denounces = append $denounces .Did.String }}
10
10
{{ end }}
11
11
{{ end }}
12
12
···
18
18
border-x border-green-600/25 dark:border-green-400/25
19
19
{{ if $denounces }} border-t {{ else }} border-y {{ end }}
20
20
">
21
-
<a class="text-sm text-green-800 dark:text-green-200 hover:no-underline" href="/{{ .SubjectDid }}?tab=vouches">
21
+
<a class="text-sm text-green-800 dark:text-green-200 hover:no-underline" href="/{{ .SubjectDid.String }}?tab=vouches">
22
22
vouched for by {{ len $vouches }} {{ if eq (len $vouches) 1 }}user{{ else }}users{{ end }}
23
23
</a>
24
24
{{ template "fragments/tinyAvatarList" (dict "all" $vouches "classes" "size-6") }}
···
32
32
border-x border-red-600/25 dark:border-red-400/25
33
33
{{ if $vouches }} border-b {{ else }} border-y {{ end }}
34
34
">
35
-
<a class="text-sm text-red-800 dark:text-red-200 hover:no-underline" href="/{{ .SubjectDid }}?tab=vouches">
35
+
<a class="text-sm text-red-800 dark:text-red-200 hover:no-underline" href="/{{ .SubjectDid.String }}?tab=vouches">
36
36
denounced by {{ len $denounces }} {{ if eq (len $denounces) 1 }}user{{ else }}users{{ end }}
37
37
</a>
38
38
{{ template "fragments/tinyAvatarList" (dict "all" $denounces "classes" "size-6") }}
+1
-1
appview/pages/templates/user/fragments/vouchButton.html
+1
-1
appview/pages/templates/user/fragments/vouchButton.html
+4
-4
appview/pages/templates/user/fragments/vouchPopover.html
+4
-4
appview/pages/templates/user/fragments/vouchPopover.html
···
17
17
{{ $userIdent := "" }}
18
18
{{ $userDid := "" }}
19
19
{{ with .VouchRelationship }}
20
-
{{ $userDid = .SubjectDid }}
21
-
{{ $userIdent = resolve .SubjectDid }}
20
+
{{ $userDid = .SubjectDid.String }}
21
+
{{ $userIdent = resolve .SubjectDid.String }}
22
22
{{ end }}
23
23
<div
24
24
id="vouch-modal-{{ normalizeForHtmlId $userDid }}"
···
32
32
class="flex flex-col gap-6 group">
33
33
34
34
<div>
35
-
<h3 class="text-lg font-semibold">Vouch for {{ $userIdent }}</h3>
36
-
<p class="text-gray-700 dark:text-gray-300">
35
+
<p class="font-semibold mb-1 text-base">Vouch for {{ $userIdent }}</p>
36
+
<p class="text-gray-600 dark:text-gray-400 text-sm">
37
37
{{ with .VouchRelationship }}
38
38
{{ with .GetDirectVouch }}
39
39
You {{if $isVouched}}vouched{{else}}denounced{{end}} {{ $userIdent }} {{ relTimeFmt .CreatedAt }}.
+30
-5
appview/state/timeline.go
+30
-5
appview/state/timeline.go
···
3
3
import (
4
4
"net/http"
5
5
6
+
"github.com/bluesky-social/indigo/atproto/syntax"
6
7
"tangled.org/core/appview/db"
8
+
"tangled.org/core/appview/models"
7
9
"tangled.org/core/appview/oauth"
8
10
"tangled.org/core/appview/pages"
9
11
"tangled.org/core/orm"
···
70
72
// non-fatal
71
73
}
72
74
75
+
var vouchSuggestions []models.VouchSuggestion
76
+
if user != nil {
77
+
vouchSuggestions, err = db.GetVouchSuggestions(s.db, user.Did, 3)
78
+
if err != nil {
79
+
s.logger.Error("failed to get vouch suggestions", "err", err)
80
+
}
81
+
if len(vouchSuggestions) > 0 {
82
+
suggestionDids := make([]syntax.DID, len(vouchSuggestions))
83
+
for i, sv := range vouchSuggestions {
84
+
suggestionDids[i] = syntax.DID(sv.Did)
85
+
}
86
+
relationships, err := db.GetVouchRelationshipsBatch(s.db, syntax.DID(user.Did), suggestionDids)
87
+
if err != nil {
88
+
s.logger.Error("failed to get vouch relationships for suggestions", "err", err)
89
+
} else {
90
+
for i := range vouchSuggestions {
91
+
vouchSuggestions[i].VouchRelationship = relationships[vouchSuggestions[i].Did]
92
+
}
93
+
}
94
+
}
95
+
}
96
+
73
97
s.pages.Timeline(w, pages.TimelineParams{
74
-
LoggedInUser: user,
75
-
Timeline: timeline,
76
-
Repos: repos,
77
-
GfiLabel: gfiLabel,
78
-
ShowNewsletter: s.showNewsletter(user),
98
+
LoggedInUser: user,
99
+
Timeline: timeline,
100
+
Repos: repos,
101
+
GfiLabel: gfiLabel,
102
+
VouchSuggestions: vouchSuggestions,
103
+
ShowNewsletter: s.showNewsletter(user),
79
104
})
80
105
}
81
106