Monorepo for Tangled tangled.org
761
fork

Configure Feed

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

appview/timeline: announce vouching in timeline

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

authored by

oppiliappan and committed by
Tangled
ba44ea59 b7df865e

+132 -38
+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
··· 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
··· 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
··· 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
··· 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
··· 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 {{ $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
··· 11 11 12 12 {{ $userDid := "" }} 13 13 {{ with .VouchRelationship }} 14 - {{ $userDid = .SubjectDid }} 14 + {{ $userDid = .SubjectDid.String }} 15 15 {{ end }} 16 16 <button 17 17 id="vouch-btn-{{ normalizeForHtmlId $userDid }}"
+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
··· 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