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