296 lines
6.7 kB
1
package pages
2
3
import (
4
"embed"
5
"fmt"
6
"html/template"
7
"io"
8
"io/fs"
9
"log"
10
"net/http"
11
"path"
12
"strings"
13
14
"github.com/dustin/go-humanize"
15
"github.com/sotangled/tangled/appview/auth"
16
"github.com/sotangled/tangled/appview/db"
17
"github.com/sotangled/tangled/types"
18
)
19
20
//go:embed templates/* static/*
21
var files embed.FS
22
23
type Pages struct {
24
t map[string]*template.Template
25
}
26
27
func funcMap() template.FuncMap {
28
return template.FuncMap{
29
"split": func(s string) []string {
30
return strings.Split(s, "\n")
31
},
32
"add": func(a, b int) int {
33
return a + b
34
},
35
"didOrHandle": func(did, handle string) string {
36
if handle != "" {
37
return fmt.Sprintf("@%s", handle)
38
} else {
39
return did
40
}
41
},
42
"assoc": func(values ...string) ([][]string, error) {
43
if len(values)%2 != 0 {
44
return nil, fmt.Errorf("invalid assoc call, must have an even number of arguments")
45
}
46
pairs := make([][]string, 0)
47
for i := 0; i < len(values); i += 2 {
48
pairs = append(pairs, []string{values[i], values[i+1]})
49
}
50
return pairs, nil
51
},
52
"timeFmt": humanize.Time,
53
}
54
}
55
56
func NewPages() *Pages {
57
templates := make(map[string]*template.Template)
58
59
// Walk through embedded templates directory and parse all .html files
60
err := fs.WalkDir(files, "templates", func(path string, d fs.DirEntry, err error) error {
61
if err != nil {
62
return err
63
}
64
65
if !d.IsDir() && strings.HasSuffix(path, ".html") {
66
name := strings.TrimPrefix(path, "templates/")
67
name = strings.TrimSuffix(name, ".html")
68
69
if !strings.HasPrefix(path, "templates/layouts/") {
70
// Add the page template on top of the base
71
tmpl, err := template.New(name).
72
Funcs(funcMap()).
73
ParseFS(files, "templates/layouts/*.html", path)
74
if err != nil {
75
return fmt.Errorf("setting up template: %w", err)
76
}
77
78
templates[name] = tmpl
79
log.Printf("loaded template: %s", name)
80
}
81
82
return nil
83
}
84
return nil
85
})
86
if err != nil {
87
log.Fatalf("walking template dir: %v", err)
88
}
89
90
log.Printf("total templates loaded: %d", len(templates))
91
92
return &Pages{
93
t: templates,
94
}
95
}
96
97
type LoginParams struct {
98
}
99
100
func (p *Pages) execute(name string, w io.Writer, params any) error {
101
return p.t[name].ExecuteTemplate(w, "layouts/base", params)
102
}
103
104
func (p *Pages) executePlain(name string, w io.Writer, params any) error {
105
return p.t[name].Execute(w, params)
106
}
107
108
func (p *Pages) executeRepo(name string, w io.Writer, params any) error {
109
return p.t[name].ExecuteTemplate(w, "layouts/repobase", params)
110
}
111
112
func (p *Pages) Login(w io.Writer, params LoginParams) error {
113
return p.executePlain("user/login", w, params)
114
}
115
116
type TimelineParams struct {
117
LoggedInUser *auth.User
118
}
119
120
func (p *Pages) Timeline(w io.Writer, params TimelineParams) error {
121
return p.execute("timeline", w, params)
122
}
123
124
type SettingsParams struct {
125
LoggedInUser *auth.User
126
PubKeys []db.PublicKey
127
}
128
129
func (p *Pages) Settings(w io.Writer, params SettingsParams) error {
130
return p.execute("settings/keys", w, params)
131
}
132
133
type KnotsParams struct {
134
LoggedInUser *auth.User
135
Registrations []db.Registration
136
}
137
138
func (p *Pages) Knots(w io.Writer, params KnotsParams) error {
139
return p.execute("knots", w, params)
140
}
141
142
type KnotParams struct {
143
LoggedInUser *auth.User
144
Registration *db.Registration
145
Members []string
146
IsOwner bool
147
}
148
149
func (p *Pages) Knot(w io.Writer, params KnotParams) error {
150
return p.execute("knot", w, params)
151
}
152
153
type NewRepoParams struct {
154
LoggedInUser *auth.User
155
Knots []string
156
}
157
158
func (p *Pages) NewRepo(w io.Writer, params NewRepoParams) error {
159
return p.execute("repo/new", w, params)
160
}
161
162
type ProfilePageParams struct {
163
LoggedInUser *auth.User
164
UserDid string
165
UserHandle string
166
Repos []db.Repo
167
}
168
169
func (p *Pages) ProfilePage(w io.Writer, params ProfilePageParams) error {
170
return p.execute("user/profile", w, params)
171
}
172
173
type RepoInfo struct {
174
Name string
175
OwnerDid string
176
OwnerHandle string
177
Description string
178
SettingsAllowed bool
179
}
180
181
func (r RepoInfo) OwnerWithAt() string {
182
if r.OwnerHandle != "" {
183
return fmt.Sprintf("@%s", r.OwnerHandle)
184
} else {
185
return r.OwnerDid
186
}
187
}
188
189
func (r RepoInfo) FullName() string {
190
return path.Join(r.OwnerWithAt(), r.Name)
191
}
192
193
type RepoIndexParams struct {
194
LoggedInUser *auth.User
195
RepoInfo RepoInfo
196
Active string
197
types.RepoIndexResponse
198
}
199
200
func (p *Pages) RepoIndexPage(w io.Writer, params RepoIndexParams) error {
201
params.Active = "overview"
202
return p.executeRepo("repo/index", w, params)
203
}
204
205
type RepoLogParams struct {
206
LoggedInUser *auth.User
207
RepoInfo RepoInfo
208
types.RepoLogResponse
209
}
210
211
func (p *Pages) RepoLog(w io.Writer, params RepoLogParams) error {
212
return p.execute("repo/log", w, params)
213
}
214
215
type RepoCommitParams struct {
216
LoggedInUser *auth.User
217
RepoInfo RepoInfo
218
types.RepoCommitResponse
219
}
220
221
func (p *Pages) RepoCommit(w io.Writer, params RepoCommitParams) error {
222
return p.executeRepo("repo/commit", w, params)
223
}
224
225
type RepoTreeParams struct {
226
LoggedInUser *auth.User
227
RepoInfo RepoInfo
228
types.RepoTreeResponse
229
}
230
231
func (p *Pages) RepoTree(w io.Writer, params RepoTreeParams) error {
232
return p.execute("repo/tree", w, params)
233
}
234
235
type RepoBranchesParams struct {
236
LoggedInUser *auth.User
237
RepoInfo RepoInfo
238
types.RepoBranchesResponse
239
}
240
241
func (p *Pages) RepoBranches(w io.Writer, params RepoBranchesParams) error {
242
return p.executeRepo("repo/branches", w, params)
243
}
244
245
type RepoTagsParams struct {
246
LoggedInUser *auth.User
247
RepoInfo RepoInfo
248
types.RepoTagsResponse
249
}
250
251
func (p *Pages) RepoTags(w io.Writer, params RepoTagsParams) error {
252
return p.executeRepo("repo/tags", w, params)
253
}
254
255
type RepoBlobParams struct {
256
LoggedInUser *auth.User
257
RepoInfo RepoInfo
258
types.RepoBlobResponse
259
}
260
261
func (p *Pages) RepoBlob(w io.Writer, params RepoBlobParams) error {
262
return p.executeRepo("repo/blob", w, params)
263
}
264
265
type RepoSettingsParams struct {
266
LoggedInUser *auth.User
267
RepoInfo RepoInfo
268
Collaborators [][]string
269
Active string
270
IsCollaboratorInviteAllowed bool
271
}
272
273
func (p *Pages) RepoSettings(w io.Writer, params RepoSettingsParams) error {
274
params.Active = "settings"
275
return p.executeRepo("repo/settings", w, params)
276
}
277
278
func (p *Pages) Static() http.Handler {
279
sub, err := fs.Sub(files, "static")
280
if err != nil {
281
log.Fatalf("no static dir found? that's crazy: %v", err)
282
}
283
return http.StripPrefix("/static/", http.FileServer(http.FS(sub)))
284
}
285
286
func (p *Pages) Error500(w io.Writer) error {
287
return p.execute("errors/500", w, nil)
288
}
289
290
func (p *Pages) Error404(w io.Writer) error {
291
return p.execute("errors/404", w, nil)
292
}
293
294
func (p *Pages) Error503(w io.Writer) error {
295
return p.execute("errors/503", w, nil)
296
}
297