this repo has no description
1package pages 2 3import ( 4 "bytes" 5 "embed" 6 "fmt" 7 "html/template" 8 "io" 9 "io/fs" 10 "log" 11 "net/http" 12 "path" 13 "path/filepath" 14 "strings" 15 16 "github.com/alecthomas/chroma/v2" 17 chromahtml "github.com/alecthomas/chroma/v2/formatters/html" 18 "github.com/alecthomas/chroma/v2/lexers" 19 "github.com/alecthomas/chroma/v2/styles" 20 "github.com/sotangled/tangled/appview/auth" 21 "github.com/sotangled/tangled/appview/db" 22 "github.com/sotangled/tangled/types" 23) 24 25//go:embed templates/* static/* 26var files embed.FS 27 28type Pages struct { 29 t map[string]*template.Template 30} 31 32func NewPages() *Pages { 33 templates := make(map[string]*template.Template) 34 35 // Walk through embedded templates directory and parse all .html files 36 err := fs.WalkDir(files, "templates", func(path string, d fs.DirEntry, err error) error { 37 if err != nil { 38 return err 39 } 40 41 if !d.IsDir() && strings.HasSuffix(path, ".html") { 42 name := strings.TrimPrefix(path, "templates/") 43 name = strings.TrimSuffix(name, ".html") 44 45 if !strings.HasPrefix(path, "templates/layouts/") { 46 // Add the page template on top of the base 47 tmpl, err := template.New(name). 48 Funcs(funcMap()). 49 ParseFS(files, "templates/layouts/*.html", path) 50 if err != nil { 51 return fmt.Errorf("setting up template: %w", err) 52 } 53 54 templates[name] = tmpl 55 log.Printf("loaded template: %s", name) 56 } 57 58 return nil 59 } 60 return nil 61 }) 62 if err != nil { 63 log.Fatalf("walking template dir: %v", err) 64 } 65 66 log.Printf("total templates loaded: %d", len(templates)) 67 68 return &Pages{ 69 t: templates, 70 } 71} 72 73type LoginParams struct { 74} 75 76func (p *Pages) execute(name string, w io.Writer, params any) error { 77 return p.t[name].ExecuteTemplate(w, "layouts/base", params) 78} 79 80func (p *Pages) executePlain(name string, w io.Writer, params any) error { 81 return p.t[name].Execute(w, params) 82} 83 84func (p *Pages) executeRepo(name string, w io.Writer, params any) error { 85 return p.t[name].ExecuteTemplate(w, "layouts/repobase", params) 86} 87 88func (p *Pages) Login(w io.Writer, params LoginParams) error { 89 return p.executePlain("user/login", w, params) 90} 91 92type TimelineParams struct { 93 LoggedInUser *auth.User 94 Timeline []db.TimelineEvent 95 DidHandleMap map[string]string 96} 97 98func (p *Pages) Timeline(w io.Writer, params TimelineParams) error { 99 return p.execute("timeline", w, params) 100} 101 102type SettingsParams struct { 103 LoggedInUser *auth.User 104 PubKeys []db.PublicKey 105} 106 107func (p *Pages) Settings(w io.Writer, params SettingsParams) error { 108 return p.execute("settings/keys", w, params) 109} 110 111type KnotsParams struct { 112 LoggedInUser *auth.User 113 Registrations []db.Registration 114} 115 116func (p *Pages) Knots(w io.Writer, params KnotsParams) error { 117 return p.execute("knots", w, params) 118} 119 120type KnotParams struct { 121 LoggedInUser *auth.User 122 Registration *db.Registration 123 Members []string 124 IsOwner bool 125} 126 127func (p *Pages) Knot(w io.Writer, params KnotParams) error { 128 return p.execute("knot", w, params) 129} 130 131type NewRepoParams struct { 132 LoggedInUser *auth.User 133 Knots []string 134} 135 136func (p *Pages) NewRepo(w io.Writer, params NewRepoParams) error { 137 return p.execute("repo/new", w, params) 138} 139 140type ProfilePageParams struct { 141 LoggedInUser *auth.User 142 UserDid string 143 UserHandle string 144 Repos []db.Repo 145 CollaboratingRepos []db.Repo 146 ProfileStats ProfileStats 147 FollowStatus db.FollowStatus 148} 149 150type ProfileStats struct { 151 Followers int 152 Following int 153} 154 155func (p *Pages) ProfilePage(w io.Writer, params ProfilePageParams) error { 156 return p.execute("user/profile", w, params) 157} 158 159type RepoInfo struct { 160 Name string 161 OwnerDid string 162 OwnerHandle string 163 Description string 164 SettingsAllowed bool 165} 166 167func (r RepoInfo) OwnerWithAt() string { 168 if r.OwnerHandle != "" { 169 return fmt.Sprintf("@%s", r.OwnerHandle) 170 } else { 171 return r.OwnerDid 172 } 173} 174 175func (r RepoInfo) FullName() string { 176 return path.Join(r.OwnerWithAt(), r.Name) 177} 178 179func (r RepoInfo) GetTabs() [][]string { 180 tabs := [][]string{ 181 {"overview", "/"}, 182 {"issues", "/issues"}, 183 {"pulls", "/pulls"}, 184 } 185 186 if r.SettingsAllowed { 187 tabs = append(tabs, []string{"settings", "/settings"}) 188 } 189 190 return tabs 191} 192 193type RepoIndexParams struct { 194 LoggedInUser *auth.User 195 RepoInfo RepoInfo 196 Active string 197 types.RepoIndexResponse 198} 199 200func (p *Pages) RepoIndexPage(w io.Writer, params RepoIndexParams) error { 201 params.Active = "overview" 202 if params.IsEmpty { 203 return p.executeRepo("repo/empty", w, params) 204 } 205 return p.executeRepo("repo/index", w, params) 206} 207 208type RepoLogParams struct { 209 LoggedInUser *auth.User 210 RepoInfo RepoInfo 211 types.RepoLogResponse 212 Active string 213} 214 215func (p *Pages) RepoLog(w io.Writer, params RepoLogParams) error { 216 params.Active = "overview" 217 return p.execute("repo/log", w, params) 218} 219 220type RepoCommitParams struct { 221 LoggedInUser *auth.User 222 RepoInfo RepoInfo 223 Active string 224 types.RepoCommitResponse 225} 226 227func (p *Pages) RepoCommit(w io.Writer, params RepoCommitParams) error { 228 params.Active = "overview" 229 return p.executeRepo("repo/commit", w, params) 230} 231 232type RepoTreeParams struct { 233 LoggedInUser *auth.User 234 RepoInfo RepoInfo 235 Active string 236 BreadCrumbs [][]string 237 BaseTreeLink string 238 BaseBlobLink string 239 types.RepoTreeResponse 240} 241 242type RepoTreeStats struct { 243 NumFolders uint64 244 NumFiles uint64 245} 246 247func (r RepoTreeParams) TreeStats() RepoTreeStats { 248 numFolders, numFiles := 0, 0 249 for _, f := range r.Files { 250 if !f.IsFile { 251 numFolders += 1 252 } else if f.IsFile { 253 numFiles += 1 254 } 255 } 256 257 return RepoTreeStats{ 258 NumFolders: uint64(numFolders), 259 NumFiles: uint64(numFiles), 260 } 261} 262 263func (p *Pages) RepoTree(w io.Writer, params RepoTreeParams) error { 264 params.Active = "overview" 265 return p.execute("repo/tree", w, params) 266} 267 268type RepoBranchesParams struct { 269 LoggedInUser *auth.User 270 RepoInfo RepoInfo 271 types.RepoBranchesResponse 272} 273 274func (p *Pages) RepoBranches(w io.Writer, params RepoBranchesParams) error { 275 return p.executeRepo("repo/branches", w, params) 276} 277 278type RepoTagsParams struct { 279 LoggedInUser *auth.User 280 RepoInfo RepoInfo 281 types.RepoTagsResponse 282} 283 284func (p *Pages) RepoTags(w io.Writer, params RepoTagsParams) error { 285 return p.executeRepo("repo/tags", w, params) 286} 287 288type RepoBlobParams struct { 289 LoggedInUser *auth.User 290 RepoInfo RepoInfo 291 Active string 292 BreadCrumbs [][]string 293 types.RepoBlobResponse 294} 295 296func (p *Pages) RepoBlob(w io.Writer, params RepoBlobParams) error { 297 style := styles.Get("bw") 298 b := style.Builder() 299 b.Add(chroma.LiteralString, "noitalic") 300 style, _ = b.Build() 301 302 if params.Lines < 5000 { 303 c := params.Contents 304 formatter := chromahtml.New( 305 chromahtml.InlineCode(true), 306 chromahtml.WithLineNumbers(true), 307 chromahtml.WithLinkableLineNumbers(true, "L"), 308 chromahtml.Standalone(false), 309 ) 310 311 lexer := lexers.Get(filepath.Base(params.Path)) 312 if lexer == nil { 313 lexer = lexers.Fallback 314 } 315 316 iterator, err := lexer.Tokenise(nil, c) 317 if err != nil { 318 return fmt.Errorf("chroma tokenize: %w", err) 319 } 320 321 var code bytes.Buffer 322 err = formatter.Format(&code, style, iterator) 323 if err != nil { 324 return fmt.Errorf("chroma format: %w", err) 325 } 326 327 params.Contents = code.String() 328 } 329 330 params.Active = "overview" 331 return p.executeRepo("repo/blob", w, params) 332} 333 334type Collaborator struct { 335 Did string 336 Handle string 337 Role string 338} 339 340type RepoSettingsParams struct { 341 LoggedInUser *auth.User 342 RepoInfo RepoInfo 343 Collaborators []Collaborator 344 Active string 345 IsCollaboratorInviteAllowed bool 346} 347 348func (p *Pages) RepoSettings(w io.Writer, params RepoSettingsParams) error { 349 params.Active = "settings" 350 return p.executeRepo("repo/settings", w, params) 351} 352 353type RepoIssuesParams struct { 354 LoggedInUser *auth.User 355 RepoInfo RepoInfo 356 Active string 357 Issues []db.Issue 358 DidHandleMap map[string]string 359} 360 361func (p *Pages) RepoIssues(w io.Writer, params RepoIssuesParams) error { 362 params.Active = "issues" 363 return p.executeRepo("repo/issues/issues", w, params) 364} 365 366type RepoSingleIssueParams struct { 367 LoggedInUser *auth.User 368 RepoInfo RepoInfo 369 Active string 370 Issue db.Issue 371 Comments []db.Comment 372 IssueOwnerHandle string 373 DidHandleMap map[string]string 374 375 State string 376} 377 378func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error { 379 params.Active = "issues" 380 if params.Issue.Open { 381 params.State = "open" 382 } else { 383 params.State = "closed" 384 } 385 return p.execute("repo/issues/issue", w, params) 386} 387 388type RepoNewIssueParams struct { 389 LoggedInUser *auth.User 390 RepoInfo RepoInfo 391 Active string 392} 393 394func (p *Pages) RepoNewIssue(w io.Writer, params RepoNewIssueParams) error { 395 params.Active = "issues" 396 return p.executeRepo("repo/issues/new", w, params) 397} 398 399type RepoPullsParams struct { 400 LoggedInUser *auth.User 401 RepoInfo RepoInfo 402 Active string 403} 404 405func (p *Pages) RepoPulls(w io.Writer, params RepoPullsParams) error { 406 params.Active = "pulls" 407 return p.executeRepo("repo/pulls/pulls", w, params) 408} 409 410func (p *Pages) Static() http.Handler { 411 sub, err := fs.Sub(files, "static") 412 if err != nil { 413 log.Fatalf("no static dir found? that's crazy: %v", err) 414 } 415 return http.StripPrefix("/static/", http.FileServer(http.FS(sub))) 416} 417 418func (p *Pages) Error500(w io.Writer) error { 419 return p.execute("errors/500", w, nil) 420} 421 422func (p *Pages) Error404(w io.Writer) error { 423 return p.execute("errors/404", w, nil) 424} 425 426func (p *Pages) Error503(w io.Writer) error { 427 return p.execute("errors/503", w, nil) 428}