Signed-off-by: Anirudh Oppiliappan anirudh@tangled.sh
+15
-1
appview/pages/funcmap.go
+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
+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
+21
-23
appview/pages/templates/repo/issues/issue.html
···
4
{{ define "extrameta" }}
5
{{ $title := printf "%s · issue #%d · %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 · issue #%d · %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
+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
+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
+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
+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
+10
appview/pages/templates/user/fragments/picHandle.html