appview/pages: show mini avatar next to user handle #267

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