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