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