Monorepo for Tangled tangled.org

appview/pages: show mini avatar next to user handle

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.sh>

anirudh.fi 4da043b7 2b3304bd

verified
Changed files
+72 -66
appview
pages
templates
repo
user
fragments
+15 -1
appview/pages/funcmap.go
··· 1 1 package pages 2 2 3 3 import ( 4 + "crypto/hmac" 5 + "crypto/sha256" 6 + "encoding/hex" 4 7 "errors" 5 8 "fmt" 6 9 "html" ··· 19 22 "tangled.sh/tangled.sh/core/appview/pages/markup" 20 23 ) 21 24 22 - func funcMap() template.FuncMap { 25 + func (p *Pages) funcMap() template.FuncMap { 23 26 return template.FuncMap{ 24 27 "split": func(s string) []string { 25 28 return strings.Split(s, "\n") ··· 246 249 u, _ := url.PathUnescape(s) 247 250 return u 248 251 }, 252 + 253 + "tinyAvatar": p.tinyAvatar, 249 254 } 255 + } 256 + 257 + func (p *Pages) tinyAvatar(handle string) string { 258 + handle = strings.TrimPrefix(handle, "@") 259 + secret := p.avatar.SharedSecret 260 + h := hmac.New(sha256.New, []byte(secret)) 261 + h.Write([]byte(handle)) 262 + signature := hex.EncodeToString(h.Sum(nil)) 263 + return fmt.Sprintf("%s/%s/%s?size=tiny", p.avatar.Host, signature, handle) 250 264 } 251 265 252 266 func icon(name string, classes []string) (template.HTML, error) {
+5 -3
appview/pages/pages.go
··· 40 40 41 41 type Pages struct { 42 42 t map[string]*template.Template 43 + avatar config.AvatarConfig 43 44 dev bool 44 45 embedFS embed.FS 45 46 templateDir string // Path to templates on disk for dev mode ··· 57 58 p := &Pages{ 58 59 t: make(map[string]*template.Template), 59 60 dev: config.Core.Dev, 61 + avatar: config.Avatar, 60 62 embedFS: Files, 61 63 rctx: rctx, 62 64 templateDir: "appview/pages", ··· 90 92 name := strings.TrimPrefix(path, "templates/") 91 93 name = strings.TrimSuffix(name, ".html") 92 94 tmpl, err := template.New(name). 93 - Funcs(funcMap()). 95 + Funcs(p.funcMap()). 94 96 ParseFS(p.embedFS, path) 95 97 if err != nil { 96 98 log.Fatalf("setting up fragment: %v", err) ··· 131 133 allPaths = append(allPaths, fragmentPaths...) 132 134 allPaths = append(allPaths, path) 133 135 tmpl, err := template.New(name). 134 - Funcs(funcMap()). 136 + Funcs(p.funcMap()). 135 137 ParseFS(p.embedFS, allPaths...) 136 138 if err != nil { 137 139 return fmt.Errorf("setting up template: %w", err) ··· 185 187 } 186 188 187 189 // Create a new template 188 - tmpl := template.New(name).Funcs(funcMap()) 190 + tmpl := template.New(name).Funcs(p.funcMap()) 189 191 190 192 // Parse layouts 191 193 layoutGlob := filepath.Join(p.templateDir, "templates", "layouts", "*.html")
+21 -23
appview/pages/templates/repo/issues/issue.html
··· 4 4 {{ define "extrameta" }} 5 5 {{ $title := printf "%s &middot; issue #%d &middot; %s" .Issue.Title .Issue.IssueId .RepoInfo.FullName }} 6 6 {{ $url := printf "https://tangled.sh/%s/issues/%d" .RepoInfo.FullName .Issue.IssueId }} 7 - 7 + 8 8 {{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo "Title" $title "Url" $url) }} 9 9 {{ end }} 10 10 ··· 30 30 {{ i $icon "w-4 h-4 mr-1.5 text-white" }} 31 31 <span class="text-white">{{ .State }}</span> 32 32 </div> 33 - <span class="text-gray-500 dark:text-gray-400 text-sm"> 33 + <span class="text-gray-500 dark:text-gray-400 text-sm flex flex-wrap items-center gap-1"> 34 34 opened by 35 35 {{ $owner := didOrHandle .Issue.OwnerDid .IssueOwnerHandle }} 36 - <a href="/{{ $owner }}" class="no-underline hover:underline" 37 - >{{ $owner }}</a 38 - > 39 - <span class="px-1 select-none before:content-['\00B7']"></span> 36 + {{ template "user/fragments/picHandle" $owner }} 37 + <span class="select-none before:content-['\00B7']"></span> 40 38 <time title="{{ .Issue.Created | longTimeFmt }}"> 41 39 {{ .Issue.Created | timeFmt }} 42 40 </time> ··· 71 69 72 70 {{ define "newComment" }} 73 71 {{ if .LoggedInUser }} 74 - <form 75 - id="comment-form" 72 + <form 73 + id="comment-form" 76 74 hx-post="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/comment" 77 75 hx-on::after-request="if(event.detail.successful) this.reset()" 78 76 > ··· 90 88 <div id="issue-comment"></div> 91 89 <div id="issue-action" class="error"></div> 92 90 </div> 93 - 91 + 94 92 <div class="flex gap-2 mt-2"> 95 - <button 93 + <button 96 94 id="comment-button" 97 95 hx-post="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/comment" 98 96 type="submit" ··· 109 107 {{ $isRepoCollaborator := .RepoInfo.Roles.IsCollaborator }} 110 108 {{ $isRepoOwner := .RepoInfo.Roles.IsOwner }} 111 109 {{ if and (or $isIssueAuthor $isRepoCollaborator $isRepoOwner) (eq .State "open") }} 112 - <button 110 + <button 113 111 id="close-button" 114 - type="button" 112 + type="button" 115 113 class="btn flex items-center gap-2" 116 114 hx-indicator="#close-spinner" 117 115 hx-trigger="click" ··· 122 120 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 123 121 </span> 124 122 </button> 125 - <div 126 - id="close-with-comment" 127 - hx-post="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/comment" 128 - hx-trigger="click from:#close-button" 123 + <div 124 + id="close-with-comment" 125 + hx-post="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/comment" 126 + hx-trigger="click from:#close-button" 129 127 hx-disabled-elt="#close-with-comment" 130 128 hx-target="#issue-comment" 131 129 hx-indicator="#close-spinner" ··· 133 131 hx-swap="none" 134 132 > 135 133 </div> 136 - <div 137 - id="close-issue" 134 + <div 135 + id="close-issue" 138 136 hx-disabled-elt="#close-issue" 139 - hx-post="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/close" 140 - hx-trigger="click from:#close-button" 137 + hx-post="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/close" 138 + hx-trigger="click from:#close-button" 141 139 hx-target="#issue-action" 142 140 hx-indicator="#close-spinner" 143 141 hx-swap="none" ··· 155 153 }); 156 154 </script> 157 155 {{ else if and (or $isIssueAuthor $isRepoCollaborator $isRepoOwner) (eq .State "closed") }} 158 - <button 159 - type="button" 156 + <button 157 + type="button" 160 158 class="btn flex items-center gap-2" 161 159 hx-post="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/reopen" 162 160 hx-indicator="#reopen-spinner" ··· 206 204 }); 207 205 </script> 208 206 </div> 209 - </form> 207 + </form> 210 208 {{ else }} 211 209 <div class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-4 px-4 relative w-fit"> 212 210 <a href="/login" class="underline">login</a> to join the discussion
+5 -5
appview/pages/templates/repo/issues/issues.html
··· 3 3 {{ define "extrameta" }} 4 4 {{ $title := "issues"}} 5 5 {{ $url := printf "https://tangled.sh/%s/issues" .RepoInfo.FullName }} 6 - 6 + 7 7 {{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo "Title" $title "Url" $url) }} 8 8 {{ end }} 9 9 ··· 49 49 <span class="text-gray-500">#{{ .IssueId }}</span> 50 50 </a> 51 51 </div> 52 - <p class="text-sm text-gray-500 dark:text-gray-400"> 52 + <p class="text-sm text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-1"> 53 53 {{ $bgColor := "bg-gray-800 dark:bg-gray-700" }} 54 54 {{ $icon := "ban" }} 55 55 {{ $state := "closed" }} ··· 64 64 <span class="text-white dark:text-white">{{ $state }}</span> 65 65 </span> 66 66 67 - <span> 68 - {{ $owner := index $.DidHandleMap .OwnerDid }} 69 - <a href="/{{ $owner }}">{{ $owner }}</a> 67 + <span class="ml-1"> 68 + {{ $owner := index $.DidHandleMap .OwnerDid }} 69 + {{ template "user/fragments/picHandle" $owner }} 70 70 </span> 71 71 72 72 <span class="before:content-['·']">
+2 -4
appview/pages/templates/repo/pulls/fragments/pullHeader.html
··· 26 26 {{ i $icon "w-4 h-4 mr-1.5 text-white" }} 27 27 <span class="text-white">{{ .Pull.State.String }}</span> 28 28 </div> 29 - <span class="text-gray-500 dark:text-gray-400 text-sm"> 29 + <span class="text-gray-500 dark:text-gray-400 text-sm flex flex-wrap items-center gap-1"> 30 30 opened by 31 31 {{ $owner := index $.DidHandleMap .Pull.OwnerDid }} 32 - <a href="/{{ $owner }}" class="no-underline hover:underline" 33 - >{{ $owner }}</a 34 - > 32 + {{ template "user/fragments/picHandle" $owner }} 35 33 <span class="select-none before:content-['\00B7']"></span> 36 34 <time>{{ .Pull.Created | timeFmt }}</time> 37 35
+5 -5
appview/pages/templates/repo/pulls/pulls.html
··· 3 3 {{ define "extrameta" }} 4 4 {{ $title := "pulls"}} 5 5 {{ $url := printf "https://tangled.sh/%s/pulls" .RepoInfo.FullName }} 6 - 6 + 7 7 {{ template "repo/fragments/og" (dict "RepoInfo" .RepoInfo "Title" $title "Url" $url) }} 8 8 {{ end }} 9 9 ··· 54 54 <span class="text-gray-500 dark:text-gray-400">#{{ .PullId }}</span> 55 55 </a> 56 56 </div> 57 - <p class="text-sm text-gray-500 dark:text-gray-400"> 57 + <p class="text-sm text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-1"> 58 58 {{ $owner := index $.DidHandleMap .OwnerDid }} 59 59 {{ $bgColor := "bg-gray-800 dark:bg-gray-700" }} 60 60 {{ $icon := "ban" }} ··· 75 75 <span class="text-white">{{ .State.String }}</span> 76 76 </span> 77 77 78 - <span> 79 - <a href="/{{ $owner }}" class="dark:text-gray-300">{{ $owner }}</a> 78 + <span class="ml-1"> 79 + {{ template "user/fragments/picHandle" $owner }} 80 80 </span> 81 81 82 - <span class="before:content-['·']"> 82 + <span> 83 83 <time> 84 84 {{ .Created | timeFmt }} 85 85 </time>
+9 -25
appview/pages/templates/timeline.html
··· 60 60 {{ if .Repo }} 61 61 {{ $userHandle := index $.DidHandleMap .Repo.Did }} 62 62 <div class="flex items-center"> 63 - <p class="text-gray-600 dark:text-gray-300"> 64 - <a 65 - href="/{{ $userHandle }}" 66 - class="no-underline hover:underline" 67 - >{{ $userHandle | truncateAt30 }}</a 68 - > 63 + <p class="text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2"> 64 + {{ template "user/fragments/picHandle" $userHandle }} 69 65 {{ if .Source }} 70 66 forked 71 67 <a 72 68 href="/{{ index $.DidHandleMap .Source.Did }}/{{ .Source.Name }}" 73 69 class="no-underline hover:underline" 74 70 > 75 - {{ index $.DidHandleMap .Source.Did }}/{{ .Source.Name }} 76 - </a> 71 + {{ index $.DidHandleMap .Source.Did }}/{{ .Source.Name }}</a 72 + > 77 73 to 78 74 <a 79 75 href="/{{ $userHandle }}/{{ .Repo.Name }}" ··· 98 94 {{ $userHandle := index $.DidHandleMap .Follow.UserDid }} 99 95 {{ $subjectHandle := index $.DidHandleMap .Follow.SubjectDid }} 100 96 <div class="flex items-center"> 101 - <p class="text-gray-600 dark:text-gray-300"> 102 - <a 103 - href="/{{ $userHandle }}" 104 - class="no-underline hover:underline" 105 - >{{ $userHandle | truncateAt30 }}</a 106 - > 97 + <p class="text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2"> 98 + {{ template "user/fragments/picHandle" $userHandle }} 107 99 followed 108 - <a 109 - href="/{{ $subjectHandle }}" 110 - class="no-underline hover:underline" 111 - >{{ $subjectHandle | truncateAt30 }}</a 112 - > 100 + {{ template "user/fragments/picHandle" $subjectHandle }} 113 101 <time 114 102 class="text-gray-700 dark:text-gray-400 text-xs" 115 103 >{{ .Follow.FollowedAt | timeFmt }}</time ··· 120 108 {{ $starrerHandle := index $.DidHandleMap .Star.StarredByDid }} 121 109 {{ $repoOwnerHandle := index $.DidHandleMap .Star.Repo.Did }} 122 110 <div class="flex items-center"> 123 - <p class="text-gray-600 dark:text-gray-300"> 124 - <a 125 - href="/{{ $starrerHandle }}" 126 - class="no-underline hover:underline" 127 - >{{ $starrerHandle | truncateAt30 }}</a 128 - > 111 + {{ template "user/fragments/picHandle" $starrerHandle }} 112 + <p class="text-gray-600 dark:text-gray-300 flex items-center gap-2"> 129 113 starred 130 114 <a 131 115 href="/{{ $repoOwnerHandle }}/{{ .Star.Repo.Name }}"
+10
appview/pages/templates/user/fragments/picHandle.html
··· 1 + {{ define "user/fragments/picHandle" }} 2 + <a href="/{{ . }}" class="flex items-center"> 3 + <img 4 + src="{{ tinyAvatar . }}" 5 + alt="{{ . }}" 6 + class="rounded-full h-6 w-6 mr-1" 7 + /> 8 + {{ . | truncateAt30 }} 9 + </a> 10 + {{ end }}