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