This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / appview / pages / pages.go
44 kB 1668 lines
1package pages 2 3import ( 4 "crypto/sha256" 5 "embed" 6 "encoding/hex" 7 "fmt" 8 "html/template" 9 "io" 10 "io/fs" 11 "log/slog" 12 "net/http" 13 "os" 14 "path/filepath" 15 "strings" 16 "sync" 17 "time" 18 19 "tangled.org/core/api/tangled" 20 "tangled.org/core/appview/commitverify" 21 "tangled.org/core/appview/config" 22 "tangled.org/core/appview/db" 23 "tangled.org/core/appview/models" 24 "tangled.org/core/appview/oauth" 25 "tangled.org/core/appview/pages/markup" 26 "tangled.org/core/appview/pages/repoinfo" 27 "tangled.org/core/appview/pagination" 28 "tangled.org/core/idresolver" 29 "tangled.org/core/patchutil" 30 "tangled.org/core/types" 31 32 "github.com/bluesky-social/indigo/atproto/identity" 33 "github.com/bluesky-social/indigo/atproto/syntax" 34 "github.com/go-git/go-git/v5/plumbing" 35) 36 37//go:embed templates/* static legal 38var Files embed.FS 39 40type Pages struct { 41 mu sync.RWMutex 42 cache *TmplCache[string, *template.Template] 43 44 avatar config.AvatarConfig 45 resolver *idresolver.Resolver 46 db *db.DB 47 dev bool 48 embedFS fs.FS 49 templateDir string // Path to templates on disk for dev mode 50 rctx *markup.RenderContext 51 logger *slog.Logger 52} 53 54func NewPages(config *config.Config, res *idresolver.Resolver, database *db.DB, logger *slog.Logger) *Pages { 55 // initialized with safe defaults, can be overriden per use 56 rctx := &markup.RenderContext{ 57 IsDev: config.Core.Dev, 58 Hostname: config.Core.AppviewHost, 59 CamoUrl: config.Camo.Host, 60 CamoSecret: config.Camo.SharedSecret, 61 Sanitizer: markup.NewSanitizer(), 62 Files: Files, 63 } 64 65 p := &Pages{ 66 mu: sync.RWMutex{}, 67 cache: NewTmplCache[string, *template.Template](), 68 dev: config.Core.Dev, 69 avatar: config.Avatar, 70 rctx: rctx, 71 resolver: res, 72 db: database, 73 templateDir: "appview/pages", 74 logger: logger, 75 } 76 77 if p.dev { 78 p.embedFS = os.DirFS(p.templateDir) 79 } else { 80 p.embedFS = Files 81 } 82 83 return p 84} 85 86// reverse of pathToName 87func (p *Pages) nameToPath(s string) string { 88 return "templates/" + s + ".html" 89} 90 91// FuncMap returns the template function map for use by external template consumers. 92func (p *Pages) FuncMap() template.FuncMap { 93 return p.funcMap() 94} 95 96// FragmentPaths returns all fragment template paths from the embedded FS. 97func (p *Pages) FragmentPaths() ([]string, error) { 98 return p.fragmentPaths() 99} 100 101// EmbedFS returns the embedded filesystem containing templates and static assets. 102func (p *Pages) EmbedFS() fs.FS { 103 return p.embedFS 104} 105 106// ParseWith parses the base layout together with all appview fragments and 107// an additional template from extraFS identified by extraPath (relative to 108// extraFS root). The returned template is ready to ExecuteTemplate with 109// "layouts/base" -- primarily for use with the blog. 110func (p *Pages) ParseWith(extraFS fs.FS, extraPath string) (*template.Template, error) { 111 fragmentPaths, err := p.fragmentPaths() 112 if err != nil { 113 return nil, err 114 } 115 116 funcs := p.funcMap() 117 tpl, err := template.New("layouts/base"). 118 Funcs(funcs). 119 ParseFS(p.embedFS, append(fragmentPaths, p.nameToPath("layouts/base"))...) 120 if err != nil { 121 return nil, err 122 } 123 124 err = fs.WalkDir(extraFS, ".", func(path string, d fs.DirEntry, err error) error { 125 if err != nil { 126 return err 127 } 128 if d.IsDir() || !strings.HasSuffix(path, ".html") { 129 return nil 130 } 131 if path != extraPath && !strings.Contains(path, "fragments/") { 132 return nil 133 } 134 data, err := fs.ReadFile(extraFS, path) 135 if err != nil { 136 return err 137 } 138 if _, err = tpl.New(path).Parse(string(data)); err != nil { 139 return err 140 } 141 return nil 142 }) 143 if err != nil { 144 return nil, err 145 } 146 147 return tpl, nil 148} 149 150func (p *Pages) fragmentPaths() ([]string, error) { 151 var fragmentPaths []string 152 err := fs.WalkDir(p.embedFS, "templates", func(path string, d fs.DirEntry, err error) error { 153 if err != nil { 154 return err 155 } 156 if d.IsDir() { 157 return nil 158 } 159 if !strings.HasSuffix(path, ".html") { 160 return nil 161 } 162 if !strings.Contains(path, "fragments/") { 163 return nil 164 } 165 fragmentPaths = append(fragmentPaths, path) 166 return nil 167 }) 168 if err != nil { 169 return nil, err 170 } 171 172 return fragmentPaths, nil 173} 174 175// parse without memoization 176func (p *Pages) rawParse(stack ...string) (*template.Template, error) { 177 paths, err := p.fragmentPaths() 178 if err != nil { 179 return nil, err 180 } 181 for _, s := range stack { 182 paths = append(paths, p.nameToPath(s)) 183 } 184 185 funcs := p.funcMap() 186 top := stack[len(stack)-1] 187 parsed, err := template.New(top). 188 Funcs(funcs). 189 ParseFS(p.embedFS, paths...) 190 if err != nil { 191 return nil, err 192 } 193 194 return parsed, nil 195} 196 197func (p *Pages) parse(stack ...string) (*template.Template, error) { 198 key := strings.Join(stack, "|") 199 200 // never cache in dev mode 201 if cached, exists := p.cache.Get(key); !p.dev && exists { 202 return cached, nil 203 } 204 205 result, err := p.rawParse(stack...) 206 if err != nil { 207 return nil, err 208 } 209 210 p.cache.Set(key, result) 211 return result, nil 212} 213 214func (p *Pages) parseBase(top string) (*template.Template, error) { 215 stack := []string{ 216 "layouts/base", 217 top, 218 } 219 return p.parse(stack...) 220} 221 222func (p *Pages) parseRepoBase(top string) (*template.Template, error) { 223 stack := []string{ 224 "layouts/base", 225 "layouts/repobase", 226 top, 227 } 228 return p.parse(stack...) 229} 230 231func (p *Pages) parseProfileBase(top string) (*template.Template, error) { 232 stack := []string{ 233 "layouts/base", 234 "layouts/profilebase", 235 top, 236 } 237 return p.parse(stack...) 238} 239 240func (p *Pages) parseLoginBase(top string) (*template.Template, error) { 241 stack := []string{ 242 "layouts/base", 243 "layouts/loginbase", 244 top, 245 } 246 return p.parse(stack...) 247} 248 249func (p *Pages) executePlain(name string, w io.Writer, params any) error { 250 tpl, err := p.parse(name) 251 if err != nil { 252 return err 253 } 254 255 return tpl.Execute(w, params) 256} 257 258func (p *Pages) executeLogin(name string, w io.Writer, params any) error { 259 tpl, err := p.parseLoginBase(name) 260 if err != nil { 261 return err 262 } 263 264 return tpl.ExecuteTemplate(w, "layouts/base", params) 265} 266 267func (p *Pages) execute(name string, w io.Writer, params any) error { 268 tpl, err := p.parseBase(name) 269 if err != nil { 270 return err 271 } 272 273 return tpl.ExecuteTemplate(w, "layouts/base", params) 274} 275 276func (p *Pages) executeRepo(name string, w io.Writer, params any) error { 277 tpl, err := p.parseRepoBase(name) 278 if err != nil { 279 return err 280 } 281 282 return tpl.ExecuteTemplate(w, "layouts/base", params) 283} 284 285func (p *Pages) executeProfile(name string, w io.Writer, params any) error { 286 tpl, err := p.parseProfileBase(name) 287 if err != nil { 288 return err 289 } 290 291 return tpl.ExecuteTemplate(w, "layouts/base", params) 292} 293 294type DollyParams struct { 295 Classes string 296 FillColor string 297} 298 299func (p *Pages) Dolly(w io.Writer, params DollyParams) error { 300 return p.executePlain("fragments/dolly/logo", w, params) 301} 302 303func (p *Pages) Favicon(w io.Writer) error { 304 return p.Dolly(w, DollyParams{ 305 Classes: "text-black dark:text-white", 306 }) 307} 308 309type LoginParams struct { 310 ReturnUrl string 311 ErrorCode string 312 AddAccount bool 313 LoggedInUser *oauth.MultiAccountUser 314} 315 316func (p *Pages) Login(w io.Writer, params LoginParams) error { 317 return p.executeLogin("user/login", w, params) 318} 319 320type SignupParams struct { 321 CloudflareSiteKey string 322 EmailId string 323} 324 325func (p *Pages) Signup(w io.Writer, params SignupParams) error { 326 return p.executeLogin("user/signup", w, params) 327} 328 329func (p *Pages) CompleteSignup(w io.Writer) error { 330 return p.executeLogin("user/completeSignup", w, nil) 331} 332 333type TermsOfServiceParams struct { 334 LoggedInUser *oauth.MultiAccountUser 335 Content template.HTML 336} 337 338func (p *Pages) TermsOfService(w io.Writer, params TermsOfServiceParams) error { 339 filename := "terms.md" 340 filePath := filepath.Join("legal", filename) 341 342 file, err := p.embedFS.Open(filePath) 343 if err != nil { 344 return fmt.Errorf("failed to read %s: %w", filename, err) 345 } 346 defer file.Close() 347 348 markdownBytes, err := io.ReadAll(file) 349 if err != nil { 350 return fmt.Errorf("failed to read %s: %w", filename, err) 351 } 352 353 p.rctx.RendererType = markup.RendererTypeDefault 354 htmlString := p.rctx.RenderMarkdown(string(markdownBytes)) 355 sanitized := p.rctx.SanitizeDefault(htmlString) 356 params.Content = template.HTML(sanitized) 357 358 return p.execute("legal/terms", w, params) 359} 360 361type PrivacyPolicyParams struct { 362 LoggedInUser *oauth.MultiAccountUser 363 Content template.HTML 364} 365 366func (p *Pages) PrivacyPolicy(w io.Writer, params PrivacyPolicyParams) error { 367 filename := "privacy.md" 368 filePath := filepath.Join("legal", filename) 369 370 file, err := p.embedFS.Open(filePath) 371 if err != nil { 372 return fmt.Errorf("failed to read %s: %w", filename, err) 373 } 374 defer file.Close() 375 376 markdownBytes, err := io.ReadAll(file) 377 if err != nil { 378 return fmt.Errorf("failed to read %s: %w", filename, err) 379 } 380 381 p.rctx.RendererType = markup.RendererTypeDefault 382 htmlString := p.rctx.RenderMarkdown(string(markdownBytes)) 383 sanitized := p.rctx.SanitizeDefault(htmlString) 384 params.Content = template.HTML(sanitized) 385 386 return p.execute("legal/privacy", w, params) 387} 388 389type BrandParams struct { 390 LoggedInUser *oauth.MultiAccountUser 391} 392 393func (p *Pages) Brand(w io.Writer, params BrandParams) error { 394 return p.execute("brand/brand", w, params) 395} 396 397type TimelineParams struct { 398 LoggedInUser *oauth.MultiAccountUser 399 Timeline []models.TimelineEvent 400 Repos []models.Repo 401 GfiLabel *models.LabelDefinition 402 BlueskyPosts []models.BskyPost 403} 404 405func (p *Pages) Timeline(w io.Writer, params TimelineParams) error { 406 return p.execute("timeline/timeline", w, params) 407} 408 409type GoodFirstIssuesParams struct { 410 LoggedInUser *oauth.MultiAccountUser 411 Issues []models.Issue 412 RepoGroups []*models.RepoGroup 413 LabelDefs map[string]*models.LabelDefinition 414 GfiLabel *models.LabelDefinition 415 Page pagination.Page 416} 417 418func (p *Pages) GoodFirstIssues(w io.Writer, params GoodFirstIssuesParams) error { 419 return p.execute("goodfirstissues/index", w, params) 420} 421 422type UserProfileSettingsParams struct { 423 LoggedInUser *oauth.MultiAccountUser 424 Tab string 425 PunchcardPreference models.PunchcardPreference 426 IsTnglSh bool 427 IsDeactivated bool 428 PdsDomain string 429 HandleOpen bool 430} 431 432func (p *Pages) UserProfileSettings(w io.Writer, params UserProfileSettingsParams) error { 433 params.Tab = "profile" 434 return p.execute("user/settings/profile", w, params) 435} 436 437type NotificationsParams struct { 438 LoggedInUser *oauth.MultiAccountUser 439 Notifications []*models.NotificationWithEntity 440 UnreadCount int 441 Page pagination.Page 442 Total int64 443} 444 445func (p *Pages) Notifications(w io.Writer, params NotificationsParams) error { 446 return p.execute("notifications/list", w, params) 447} 448 449type NotificationItemParams struct { 450 Notification *models.Notification 451} 452 453func (p *Pages) NotificationItem(w io.Writer, params NotificationItemParams) error { 454 return p.executePlain("notifications/fragments/item", w, params) 455} 456 457type NotificationCountParams struct { 458 Count int64 459} 460 461func (p *Pages) NotificationCount(w io.Writer, params NotificationCountParams) error { 462 return p.executePlain("notifications/fragments/count", w, params) 463} 464 465type UserKeysSettingsParams struct { 466 LoggedInUser *oauth.MultiAccountUser 467 PubKeys []models.PublicKey 468 Tab string 469} 470 471func (p *Pages) UserKeysSettings(w io.Writer, params UserKeysSettingsParams) error { 472 params.Tab = "keys" 473 return p.execute("user/settings/keys", w, params) 474} 475 476type UserEmailsSettingsParams struct { 477 LoggedInUser *oauth.MultiAccountUser 478 Emails []models.Email 479 Tab string 480} 481 482func (p *Pages) UserEmailsSettings(w io.Writer, params UserEmailsSettingsParams) error { 483 params.Tab = "emails" 484 return p.execute("user/settings/emails", w, params) 485} 486 487type UserNotificationSettingsParams struct { 488 LoggedInUser *oauth.MultiAccountUser 489 Preferences *models.NotificationPreferences 490 Tab string 491} 492 493func (p *Pages) UserNotificationSettings(w io.Writer, params UserNotificationSettingsParams) error { 494 params.Tab = "notifications" 495 return p.execute("user/settings/notifications", w, params) 496} 497 498type UserSiteSettingsParams struct { 499 LoggedInUser *oauth.MultiAccountUser 500 Claim *models.DomainClaim 501 SitesDomain string 502 IsTnglHandle bool 503 Tab string 504} 505 506func (p *Pages) UserSiteSettings(w io.Writer, params UserSiteSettingsParams) error { 507 params.Tab = "sites" 508 return p.execute("user/settings/sites", w, params) 509} 510 511type UpgradeBannerParams struct { 512 Registrations []models.Registration 513 Spindles []models.Spindle 514} 515 516func (p *Pages) UpgradeBanner(w io.Writer, params UpgradeBannerParams) error { 517 return p.executePlain("banner", w, params) 518} 519 520type KnotsParams struct { 521 LoggedInUser *oauth.MultiAccountUser 522 Registrations []models.Registration 523 Tab string 524} 525 526func (p *Pages) Knots(w io.Writer, params KnotsParams) error { 527 params.Tab = "knots" 528 return p.execute("knots/index", w, params) 529} 530 531type KnotParams struct { 532 LoggedInUser *oauth.MultiAccountUser 533 Registration *models.Registration 534 Members []string 535 Repos map[string][]models.Repo 536 IsOwner bool 537 Tab string 538} 539 540func (p *Pages) Knot(w io.Writer, params KnotParams) error { 541 return p.execute("knots/dashboard", w, params) 542} 543 544type KnotListingParams struct { 545 *models.Registration 546} 547 548func (p *Pages) KnotListing(w io.Writer, params KnotListingParams) error { 549 return p.executePlain("knots/fragments/knotListing", w, params) 550} 551 552type SpindlesParams struct { 553 LoggedInUser *oauth.MultiAccountUser 554 Spindles []models.Spindle 555 Tab string 556} 557 558func (p *Pages) Spindles(w io.Writer, params SpindlesParams) error { 559 params.Tab = "spindles" 560 return p.execute("spindles/index", w, params) 561} 562 563type SpindleListingParams struct { 564 models.Spindle 565 Tab string 566} 567 568func (p *Pages) SpindleListing(w io.Writer, params SpindleListingParams) error { 569 return p.executePlain("spindles/fragments/spindleListing", w, params) 570} 571 572type SpindleDashboardParams struct { 573 LoggedInUser *oauth.MultiAccountUser 574 Spindle models.Spindle 575 Members []string 576 Repos map[string][]models.Repo 577 Tab string 578} 579 580func (p *Pages) SpindleDashboard(w io.Writer, params SpindleDashboardParams) error { 581 return p.execute("spindles/dashboard", w, params) 582} 583 584type NewRepoParams struct { 585 LoggedInUser *oauth.MultiAccountUser 586 Knots []string 587} 588 589func (p *Pages) NewRepo(w io.Writer, params NewRepoParams) error { 590 return p.execute("repo/new", w, params) 591} 592 593type ForkRepoParams struct { 594 LoggedInUser *oauth.MultiAccountUser 595 Knots []string 596 RepoInfo repoinfo.RepoInfo 597} 598 599func (p *Pages) ForkRepo(w io.Writer, params ForkRepoParams) error { 600 return p.execute("repo/fork", w, params) 601} 602 603type ProfileCard struct { 604 UserDid string 605 HasProfile bool 606 FollowStatus models.FollowStatus 607 Punchcard *models.Punchcard 608 Profile *models.Profile 609 Stats ProfileStats 610 Active string 611} 612 613type ProfileStats struct { 614 RepoCount int64 615 StarredCount int64 616 StringCount int64 617 FollowersCount int64 618 FollowingCount int64 619} 620 621func (p *ProfileCard) GetTabs() [][]any { 622 tabs := [][]any{ 623 {"overview", "overview", "square-chart-gantt", nil}, 624 {"repos", "repos", "book-marked", p.Stats.RepoCount}, 625 {"starred", "starred", "star", p.Stats.StarredCount}, 626 {"strings", "strings", "line-squiggle", p.Stats.StringCount}, 627 } 628 629 return tabs 630} 631 632type ProfileOverviewParams struct { 633 LoggedInUser *oauth.MultiAccountUser 634 Repos []models.Repo 635 CollaboratingRepos []models.Repo 636 ProfileTimeline *models.ProfileTimeline 637 Card *ProfileCard 638 Active string 639 ShowPunchcard bool 640} 641 642func (p *Pages) ProfileOverview(w io.Writer, params ProfileOverviewParams) error { 643 params.Active = "overview" 644 return p.executeProfile("user/overview", w, params) 645} 646 647type ProfileReposParams struct { 648 LoggedInUser *oauth.MultiAccountUser 649 Repos []models.Repo 650 Card *ProfileCard 651 Active string 652 Page pagination.Page 653 RepoCount int 654 FilterQuery string 655} 656 657func (p *Pages) ProfileRepos(w io.Writer, params ProfileReposParams) error { 658 params.Active = "repos" 659 return p.executeProfile("user/repos", w, params) 660} 661 662type ProfileStarredParams struct { 663 LoggedInUser *oauth.MultiAccountUser 664 Repos []models.Repo 665 Card *ProfileCard 666 Active string 667} 668 669func (p *Pages) ProfileStarred(w io.Writer, params ProfileStarredParams) error { 670 params.Active = "starred" 671 return p.executeProfile("user/starred", w, params) 672} 673 674type ProfileStringsParams struct { 675 LoggedInUser *oauth.MultiAccountUser 676 Strings []models.String 677 Card *ProfileCard 678 Active string 679} 680 681func (p *Pages) ProfileStrings(w io.Writer, params ProfileStringsParams) error { 682 params.Active = "strings" 683 return p.executeProfile("user/strings", w, params) 684} 685 686type FollowCard struct { 687 UserDid string 688 LoggedInUser *oauth.MultiAccountUser 689 FollowStatus models.FollowStatus 690 FollowersCount int64 691 FollowingCount int64 692 Profile *models.Profile 693} 694 695type ProfileFollowersParams struct { 696 LoggedInUser *oauth.MultiAccountUser 697 Followers []FollowCard 698 Card *ProfileCard 699 Active string 700} 701 702func (p *Pages) ProfileFollowers(w io.Writer, params ProfileFollowersParams) error { 703 params.Active = "overview" 704 return p.executeProfile("user/followers", w, params) 705} 706 707type ProfileFollowingParams struct { 708 LoggedInUser *oauth.MultiAccountUser 709 Following []FollowCard 710 Card *ProfileCard 711 Active string 712} 713 714func (p *Pages) ProfileFollowing(w io.Writer, params ProfileFollowingParams) error { 715 params.Active = "overview" 716 return p.executeProfile("user/following", w, params) 717} 718 719type FollowFragmentParams struct { 720 UserDid string 721 FollowStatus models.FollowStatus 722 FollowersCount int64 723} 724 725func (p *Pages) FollowFragment(w io.Writer, params FollowFragmentParams) error { 726 return p.executePlain("user/fragments/follow-oob", w, params) 727} 728 729type EditBioParams struct { 730 LoggedInUser *oauth.MultiAccountUser 731 Profile *models.Profile 732} 733 734func (p *Pages) EditBioFragment(w io.Writer, params EditBioParams) error { 735 return p.executePlain("user/fragments/editBio", w, params) 736} 737 738type EditPinsParams struct { 739 LoggedInUser *oauth.MultiAccountUser 740 Profile *models.Profile 741 AllRepos []PinnedRepo 742} 743 744type PinnedRepo struct { 745 IsPinned bool 746 models.Repo 747} 748 749func (p *Pages) EditPinsFragment(w io.Writer, params EditPinsParams) error { 750 return p.executePlain("user/fragments/editPins", w, params) 751} 752 753type StarBtnFragmentParams struct { 754 IsStarred bool 755 SubjectAt syntax.ATURI 756 StarCount int 757 HxSwapOob bool 758} 759 760func (p *Pages) StarBtnFragment(w io.Writer, params StarBtnFragmentParams) error { 761 params.HxSwapOob = true 762 return p.executePlain("fragments/starBtn", w, params) 763} 764 765type RepoIndexParams struct { 766 LoggedInUser *oauth.MultiAccountUser 767 RepoInfo repoinfo.RepoInfo 768 Active string 769 TagMap map[string][]string 770 CommitsTrunc []types.Commit 771 TagsTrunc []*types.TagReference 772 BranchesTrunc []types.Branch 773 // ForkInfo *types.ForkInfo 774 HTMLReadme template.HTML 775 Raw bool 776 EmailToDid map[string]string 777 VerifiedCommits commitverify.VerifiedCommits 778 Languages []types.RepoLanguageDetails 779 Pipelines map[string]models.Pipeline 780 NeedsKnotUpgrade bool 781 KnotUnreachable bool 782 types.RepoIndexResponse 783} 784 785func (p *Pages) RepoIndexPage(w io.Writer, params RepoIndexParams) error { 786 params.Active = "overview" 787 if params.IsEmpty { 788 return p.executeRepo("repo/empty", w, params) 789 } 790 791 if params.NeedsKnotUpgrade { 792 return p.executeRepo("repo/needsUpgrade", w, params) 793 } 794 795 if params.KnotUnreachable { 796 return p.executeRepo("repo/knotUnreachable", w, params) 797 } 798 799 p.rctx.RepoInfo = params.RepoInfo 800 p.rctx.RepoInfo.Ref = params.Ref 801 p.rctx.RendererType = markup.RendererTypeRepoMarkdown 802 803 if params.ReadmeFileName != "" { 804 ext := filepath.Ext(params.ReadmeFileName) 805 switch ext { 806 case ".md", ".markdown", ".mdown", ".mkdn", ".mkd": 807 params.Raw = false 808 htmlString := p.rctx.RenderMarkdown(params.Readme) 809 sanitized := p.rctx.SanitizeDefault(htmlString) 810 params.HTMLReadme = template.HTML(sanitized) 811 default: 812 params.Raw = true 813 } 814 } 815 816 return p.executeRepo("repo/index", w, params) 817} 818 819type RepoLogParams struct { 820 LoggedInUser *oauth.MultiAccountUser 821 RepoInfo repoinfo.RepoInfo 822 TagMap map[string][]string 823 Active string 824 EmailToDid map[string]string 825 VerifiedCommits commitverify.VerifiedCommits 826 Pipelines map[string]models.Pipeline 827 828 types.RepoLogResponse 829} 830 831func (p *Pages) RepoLog(w io.Writer, params RepoLogParams) error { 832 params.Active = "overview" 833 return p.executeRepo("repo/log", w, params) 834} 835 836type RepoCommitParams struct { 837 LoggedInUser *oauth.MultiAccountUser 838 RepoInfo repoinfo.RepoInfo 839 Active string 840 EmailToDid map[string]string 841 Pipeline *models.Pipeline 842 DiffOpts types.DiffOpts 843 844 // singular because it's always going to be just one 845 VerifiedCommit commitverify.VerifiedCommits 846 847 types.RepoCommitResponse 848} 849 850func (p *Pages) RepoCommit(w io.Writer, params RepoCommitParams) error { 851 params.Active = "overview" 852 return p.executeRepo("repo/commit", w, params) 853} 854 855type RepoTreeParams struct { 856 LoggedInUser *oauth.MultiAccountUser 857 RepoInfo repoinfo.RepoInfo 858 Active string 859 BreadCrumbs [][]string 860 Path string 861 Raw bool 862 HTMLReadme template.HTML 863 EmailToDid map[string]string 864 LastCommitInfo *types.LastCommitInfo 865 types.RepoTreeResponse 866} 867 868type RepoTreeStats struct { 869 NumFolders uint64 870 NumFiles uint64 871} 872 873func (r RepoTreeParams) TreeStats() RepoTreeStats { 874 numFolders, numFiles := 0, 0 875 for _, f := range r.Files { 876 if !f.IsFile() { 877 numFolders += 1 878 } else if f.IsFile() { 879 numFiles += 1 880 } 881 } 882 883 return RepoTreeStats{ 884 NumFolders: uint64(numFolders), 885 NumFiles: uint64(numFiles), 886 } 887} 888 889func (p *Pages) RepoTree(w io.Writer, params RepoTreeParams) error { 890 params.Active = "overview" 891 892 p.rctx.RepoInfo = params.RepoInfo 893 p.rctx.RepoInfo.Ref = params.Ref 894 p.rctx.RendererType = markup.RendererTypeRepoMarkdown 895 896 if params.ReadmeFileName != "" { 897 ext := filepath.Ext(params.ReadmeFileName) 898 switch ext { 899 case ".md", ".markdown", ".mdown", ".mkdn", ".mkd": 900 params.Raw = false 901 htmlString := p.rctx.RenderMarkdown(params.Readme) 902 sanitized := p.rctx.SanitizeDefault(htmlString) 903 params.HTMLReadme = template.HTML(sanitized) 904 default: 905 params.Raw = true 906 } 907 } 908 909 return p.executeRepo("repo/tree", w, params) 910} 911 912type RepoBranchesParams struct { 913 LoggedInUser *oauth.MultiAccountUser 914 RepoInfo repoinfo.RepoInfo 915 Active string 916 types.RepoBranchesResponse 917} 918 919func (p *Pages) RepoBranches(w io.Writer, params RepoBranchesParams) error { 920 params.Active = "overview" 921 return p.executeRepo("repo/branches", w, params) 922} 923 924type RepoTagsParams struct { 925 LoggedInUser *oauth.MultiAccountUser 926 RepoInfo repoinfo.RepoInfo 927 Active string 928 types.RepoTagsResponse 929 ArtifactMap map[plumbing.Hash][]models.Artifact 930 DanglingArtifacts []models.Artifact 931} 932 933func (p *Pages) RepoTags(w io.Writer, params RepoTagsParams) error { 934 params.Active = "overview" 935 return p.executeRepo("repo/tags", w, params) 936} 937 938type RepoTagParams struct { 939 LoggedInUser *oauth.MultiAccountUser 940 RepoInfo repoinfo.RepoInfo 941 Active string 942 types.RepoTagResponse 943 ArtifactMap map[plumbing.Hash][]models.Artifact 944 DanglingArtifacts []models.Artifact 945} 946 947func (p *Pages) RepoTag(w io.Writer, params RepoTagParams) error { 948 params.Active = "overview" 949 return p.executeRepo("repo/tag", w, params) 950} 951 952type RepoArtifactParams struct { 953 LoggedInUser *oauth.MultiAccountUser 954 RepoInfo repoinfo.RepoInfo 955 Artifact models.Artifact 956} 957 958func (p *Pages) RepoArtifactFragment(w io.Writer, params RepoArtifactParams) error { 959 return p.executePlain("repo/fragments/artifact", w, params) 960} 961 962type RepoBlobParams struct { 963 LoggedInUser *oauth.MultiAccountUser 964 RepoInfo repoinfo.RepoInfo 965 Active string 966 BreadCrumbs [][]string 967 BlobView models.BlobView 968 EmailToDid map[string]string 969 LastCommitInfo *types.LastCommitInfo 970 *tangled.RepoBlob_Output 971} 972 973func (p *Pages) RepoBlob(w io.Writer, params RepoBlobParams) error { 974 switch params.BlobView.ContentType { 975 case models.BlobContentTypeMarkup: 976 p.rctx.RepoInfo = params.RepoInfo 977 } 978 979 params.Active = "overview" 980 return p.executeRepo("repo/blob", w, params) 981} 982 983type Collaborator struct { 984 Did string 985 Role string 986} 987 988type RepoSettingsParams struct { 989 LoggedInUser *oauth.MultiAccountUser 990 RepoInfo repoinfo.RepoInfo 991 Collaborators []Collaborator 992 Active string 993 Branches []types.Branch 994 Spindles []string 995 CurrentSpindle string 996 Secrets []*tangled.RepoListSecrets_Secret 997 998 // TODO: use repoinfo.roles 999 IsCollaboratorInviteAllowed bool 1000} 1001 1002func (p *Pages) RepoSettings(w io.Writer, params RepoSettingsParams) error { 1003 params.Active = "settings" 1004 return p.executeRepo("repo/settings", w, params) 1005} 1006 1007type RepoGeneralSettingsParams struct { 1008 LoggedInUser *oauth.MultiAccountUser 1009 RepoInfo repoinfo.RepoInfo 1010 Labels []models.LabelDefinition 1011 DefaultLabels []models.LabelDefinition 1012 SubscribedLabels map[string]struct{} 1013 ShouldSubscribeAll bool 1014 Active string 1015 Tab string 1016 Branches []types.Branch 1017} 1018 1019func (p *Pages) RepoGeneralSettings(w io.Writer, params RepoGeneralSettingsParams) error { 1020 params.Active = "settings" 1021 params.Tab = "general" 1022 return p.executeRepo("repo/settings/general", w, params) 1023} 1024 1025type RepoAccessSettingsParams struct { 1026 LoggedInUser *oauth.MultiAccountUser 1027 RepoInfo repoinfo.RepoInfo 1028 Active string 1029 Tab string 1030 Collaborators []Collaborator 1031} 1032 1033func (p *Pages) RepoAccessSettings(w io.Writer, params RepoAccessSettingsParams) error { 1034 params.Active = "settings" 1035 params.Tab = "access" 1036 return p.executeRepo("repo/settings/access", w, params) 1037} 1038 1039type RepoPipelineSettingsParams struct { 1040 LoggedInUser *oauth.MultiAccountUser 1041 RepoInfo repoinfo.RepoInfo 1042 Active string 1043 Tab string 1044 Spindles []string 1045 CurrentSpindle string 1046 Secrets []map[string]any 1047} 1048 1049func (p *Pages) RepoPipelineSettings(w io.Writer, params RepoPipelineSettingsParams) error { 1050 params.Active = "settings" 1051 params.Tab = "pipelines" 1052 return p.executeRepo("repo/settings/pipelines", w, params) 1053} 1054 1055type RepoWebhooksSettingsParams struct { 1056 LoggedInUser *oauth.MultiAccountUser 1057 RepoInfo repoinfo.RepoInfo 1058 Active string 1059 Tab string 1060 Webhooks []models.Webhook 1061 WebhookDeliveries map[int64][]models.WebhookDelivery 1062} 1063 1064func (p *Pages) RepoWebhooksSettings(w io.Writer, params RepoWebhooksSettingsParams) error { 1065 params.Active = "settings" 1066 params.Tab = "hooks" 1067 return p.executeRepo("repo/settings/hooks", w, params) 1068} 1069 1070type WebhookDeliveriesListParams struct { 1071 LoggedInUser *oauth.MultiAccountUser 1072 RepoInfo repoinfo.RepoInfo 1073 Webhook *models.Webhook 1074 Deliveries []models.WebhookDelivery 1075} 1076 1077func (p *Pages) WebhookDeliveriesList(w io.Writer, params WebhookDeliveriesListParams) error { 1078 tpl, err := p.parse("repo/settings/fragments/webhookDeliveries") 1079 if err != nil { 1080 return err 1081 } 1082 return tpl.ExecuteTemplate(w, "repo/settings/fragments/webhookDeliveries", params) 1083} 1084 1085type RepoSiteSettingsParams struct { 1086 LoggedInUser *oauth.MultiAccountUser 1087 RepoInfo repoinfo.RepoInfo 1088 Active string 1089 Tab string 1090 Branches []types.Branch 1091 SiteConfig *models.RepoSite 1092 OwnerClaim *models.DomainClaim 1093 Deploys []models.SiteDeploy 1094 IndexSiteTakenBy string // repo_at of another repo that already holds is_index, or "" 1095} 1096 1097func (p *Pages) RepoSiteSettings(w io.Writer, params RepoSiteSettingsParams) error { 1098 params.Active = "settings" 1099 params.Tab = "sites" 1100 return p.executeRepo("repo/settings/sites", w, params) 1101} 1102 1103type RepoIssuesParams struct { 1104 LoggedInUser *oauth.MultiAccountUser 1105 RepoInfo repoinfo.RepoInfo 1106 Active string 1107 Issues []models.Issue 1108 IssueCount int 1109 LabelDefs map[string]*models.LabelDefinition 1110 Page pagination.Page 1111 FilterState string 1112 FilterQuery string 1113} 1114 1115func (p *Pages) RepoIssues(w io.Writer, params RepoIssuesParams) error { 1116 params.Active = "issues" 1117 return p.executeRepo("repo/issues/issues", w, params) 1118} 1119 1120type RepoSingleIssueParams struct { 1121 LoggedInUser *oauth.MultiAccountUser 1122 RepoInfo repoinfo.RepoInfo 1123 Active string 1124 Issue *models.Issue 1125 CommentList []models.CommentListItem 1126 Backlinks []models.RichReferenceLink 1127 LabelDefs map[string]*models.LabelDefinition 1128 1129 Reactions map[models.ReactionKind]models.ReactionDisplayData 1130 UserReacted map[models.ReactionKind]bool 1131} 1132 1133func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error { 1134 params.Active = "issues" 1135 return p.executeRepo("repo/issues/issue", w, params) 1136} 1137 1138type EditIssueParams struct { 1139 LoggedInUser *oauth.MultiAccountUser 1140 RepoInfo repoinfo.RepoInfo 1141 Issue *models.Issue 1142 Action string 1143} 1144 1145func (p *Pages) EditIssueFragment(w io.Writer, params EditIssueParams) error { 1146 params.Action = "edit" 1147 return p.executePlain("repo/issues/fragments/putIssue", w, params) 1148} 1149 1150type ThreadReactionFragmentParams struct { 1151 ThreadAt syntax.ATURI 1152 Kind models.ReactionKind 1153 Count int 1154 Users []string 1155 IsReacted bool 1156} 1157 1158func (p *Pages) ThreadReactionFragment(w io.Writer, params ThreadReactionFragmentParams) error { 1159 return p.executePlain("repo/fragments/reaction", w, params) 1160} 1161 1162type RepoNewIssueParams struct { 1163 LoggedInUser *oauth.MultiAccountUser 1164 RepoInfo repoinfo.RepoInfo 1165 Issue *models.Issue // existing issue if any -- passed when editing 1166 Active string 1167 Action string 1168} 1169 1170func (p *Pages) RepoNewIssue(w io.Writer, params RepoNewIssueParams) error { 1171 params.Active = "issues" 1172 params.Action = "create" 1173 return p.executeRepo("repo/issues/new", w, params) 1174} 1175 1176type EditIssueCommentParams struct { 1177 LoggedInUser *oauth.MultiAccountUser 1178 RepoInfo repoinfo.RepoInfo 1179 Issue *models.Issue 1180 Comment *models.IssueComment 1181} 1182 1183func (p *Pages) EditIssueCommentFragment(w io.Writer, params EditIssueCommentParams) error { 1184 return p.executePlain("repo/issues/fragments/editIssueComment", w, params) 1185} 1186 1187type ReplyIssueCommentPlaceholderParams struct { 1188 LoggedInUser *oauth.MultiAccountUser 1189 RepoInfo repoinfo.RepoInfo 1190 Issue *models.Issue 1191 Comment *models.IssueComment 1192} 1193 1194func (p *Pages) ReplyIssueCommentPlaceholderFragment(w io.Writer, params ReplyIssueCommentPlaceholderParams) error { 1195 return p.executePlain("repo/issues/fragments/replyIssueCommentPlaceholder", w, params) 1196} 1197 1198type ReplyIssueCommentParams struct { 1199 LoggedInUser *oauth.MultiAccountUser 1200 RepoInfo repoinfo.RepoInfo 1201 Issue *models.Issue 1202 Comment *models.IssueComment 1203} 1204 1205func (p *Pages) ReplyIssueCommentFragment(w io.Writer, params ReplyIssueCommentParams) error { 1206 return p.executePlain("repo/issues/fragments/replyComment", w, params) 1207} 1208 1209type IssueCommentBodyParams struct { 1210 LoggedInUser *oauth.MultiAccountUser 1211 RepoInfo repoinfo.RepoInfo 1212 Issue *models.Issue 1213 Comment *models.IssueComment 1214} 1215 1216func (p *Pages) IssueCommentBodyFragment(w io.Writer, params IssueCommentBodyParams) error { 1217 return p.executePlain("repo/issues/fragments/issueCommentBody", w, params) 1218} 1219 1220type RepoNewPullParams struct { 1221 LoggedInUser *oauth.MultiAccountUser 1222 RepoInfo repoinfo.RepoInfo 1223 Branches []types.Branch 1224 Strategy string 1225 SourceBranch string 1226 TargetBranch string 1227 Title string 1228 Body string 1229 Active string 1230} 1231 1232func (p *Pages) RepoNewPull(w io.Writer, params RepoNewPullParams) error { 1233 params.Active = "pulls" 1234 return p.executeRepo("repo/pulls/new", w, params) 1235} 1236 1237type RepoPullsParams struct { 1238 LoggedInUser *oauth.MultiAccountUser 1239 RepoInfo repoinfo.RepoInfo 1240 Pulls []*models.Pull 1241 Active string 1242 FilterState string 1243 FilterQuery string 1244 Stacks map[string]models.Stack 1245 Pipelines map[string]models.Pipeline 1246 LabelDefs map[string]*models.LabelDefinition 1247 Page pagination.Page 1248 PullCount int 1249} 1250 1251func (p *Pages) RepoPulls(w io.Writer, params RepoPullsParams) error { 1252 params.Active = "pulls" 1253 return p.executeRepo("repo/pulls/pulls", w, params) 1254} 1255 1256type ResubmitResult uint64 1257 1258const ( 1259 ShouldResubmit ResubmitResult = iota 1260 ShouldNotResubmit 1261 Unknown 1262) 1263 1264func (r ResubmitResult) Yes() bool { 1265 return r == ShouldResubmit 1266} 1267func (r ResubmitResult) No() bool { 1268 return r == ShouldNotResubmit 1269} 1270func (r ResubmitResult) Unknown() bool { 1271 return r == Unknown 1272} 1273 1274type RepoSinglePullParams struct { 1275 LoggedInUser *oauth.MultiAccountUser 1276 RepoInfo repoinfo.RepoInfo 1277 Active string 1278 Pull *models.Pull 1279 Stack models.Stack 1280 AbandonedPulls []*models.Pull 1281 Backlinks []models.RichReferenceLink 1282 BranchDeleteStatus *models.BranchDeleteStatus 1283 MergeCheck types.MergeCheckResponse 1284 ResubmitCheck ResubmitResult 1285 Pipelines map[string]models.Pipeline 1286 Diff types.DiffRenderer 1287 DiffOpts types.DiffOpts 1288 ActiveRound int 1289 IsInterdiff bool 1290 1291 Reactions map[models.ReactionKind]models.ReactionDisplayData 1292 UserReacted map[models.ReactionKind]bool 1293 1294 LabelDefs map[string]*models.LabelDefinition 1295} 1296 1297func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error { 1298 params.Active = "pulls" 1299 return p.executeRepo("repo/pulls/pull", w, params) 1300} 1301 1302type RepoPullPatchParams struct { 1303 LoggedInUser *oauth.MultiAccountUser 1304 RepoInfo repoinfo.RepoInfo 1305 Pull *models.Pull 1306 Stack models.Stack 1307 Diff *types.NiceDiff 1308 Round int 1309 Submission *models.PullSubmission 1310 DiffOpts types.DiffOpts 1311} 1312 1313// this name is a mouthful 1314func (p *Pages) RepoPullPatchPage(w io.Writer, params RepoPullPatchParams) error { 1315 return p.execute("repo/pulls/patch", w, params) 1316} 1317 1318type RepoPullInterdiffParams struct { 1319 LoggedInUser *oauth.MultiAccountUser 1320 RepoInfo repoinfo.RepoInfo 1321 Pull *models.Pull 1322 Round int 1323 Interdiff *patchutil.InterdiffResult 1324 DiffOpts types.DiffOpts 1325} 1326 1327// this name is a mouthful 1328func (p *Pages) RepoPullInterdiffPage(w io.Writer, params RepoPullInterdiffParams) error { 1329 return p.execute("repo/pulls/interdiff", w, params) 1330} 1331 1332type PullPatchUploadParams struct { 1333 RepoInfo repoinfo.RepoInfo 1334} 1335 1336func (p *Pages) PullPatchUploadFragment(w io.Writer, params PullPatchUploadParams) error { 1337 return p.executePlain("repo/pulls/fragments/pullPatchUpload", w, params) 1338} 1339 1340type PullCompareBranchesParams struct { 1341 RepoInfo repoinfo.RepoInfo 1342 Branches []types.Branch 1343 SourceBranch string 1344} 1345 1346func (p *Pages) PullCompareBranchesFragment(w io.Writer, params PullCompareBranchesParams) error { 1347 return p.executePlain("repo/pulls/fragments/pullCompareBranches", w, params) 1348} 1349 1350type PullCompareForkParams struct { 1351 RepoInfo repoinfo.RepoInfo 1352 Forks []models.Repo 1353 Selected string 1354} 1355 1356func (p *Pages) PullCompareForkFragment(w io.Writer, params PullCompareForkParams) error { 1357 return p.executePlain("repo/pulls/fragments/pullCompareForks", w, params) 1358} 1359 1360type PullCompareForkBranchesParams struct { 1361 RepoInfo repoinfo.RepoInfo 1362 SourceBranches []types.Branch 1363 TargetBranches []types.Branch 1364} 1365 1366func (p *Pages) PullCompareForkBranchesFragment(w io.Writer, params PullCompareForkBranchesParams) error { 1367 return p.executePlain("repo/pulls/fragments/pullCompareForksBranches", w, params) 1368} 1369 1370type PullResubmitParams struct { 1371 LoggedInUser *oauth.MultiAccountUser 1372 RepoInfo repoinfo.RepoInfo 1373 Pull *models.Pull 1374 SubmissionId int 1375} 1376 1377func (p *Pages) PullResubmitFragment(w io.Writer, params PullResubmitParams) error { 1378 return p.executePlain("repo/pulls/fragments/pullResubmit", w, params) 1379} 1380 1381type PullActionsParams struct { 1382 LoggedInUser *oauth.MultiAccountUser 1383 RepoInfo repoinfo.RepoInfo 1384 Pull *models.Pull 1385 RoundNumber int 1386 MergeCheck types.MergeCheckResponse 1387 ResubmitCheck ResubmitResult 1388 BranchDeleteStatus *models.BranchDeleteStatus 1389 Stack models.Stack 1390} 1391 1392func (p *Pages) PullActionsFragment(w io.Writer, params PullActionsParams) error { 1393 return p.executePlain("repo/pulls/fragments/pullActions", w, params) 1394} 1395 1396type PullNewCommentParams struct { 1397 LoggedInUser *oauth.MultiAccountUser 1398 RepoInfo repoinfo.RepoInfo 1399 Pull *models.Pull 1400 RoundNumber int 1401} 1402 1403func (p *Pages) PullNewCommentFragment(w io.Writer, params PullNewCommentParams) error { 1404 return p.executePlain("repo/pulls/fragments/pullNewComment", w, params) 1405} 1406 1407type RepoCompareParams struct { 1408 LoggedInUser *oauth.MultiAccountUser 1409 RepoInfo repoinfo.RepoInfo 1410 Forks []models.Repo 1411 Branches []types.Branch 1412 Tags []*types.TagReference 1413 Base string 1414 Head string 1415 Diff *types.NiceDiff 1416 DiffOpts types.DiffOpts 1417 1418 Active string 1419} 1420 1421func (p *Pages) RepoCompare(w io.Writer, params RepoCompareParams) error { 1422 params.Active = "overview" 1423 return p.executeRepo("repo/compare/compare", w, params) 1424} 1425 1426type RepoCompareNewParams struct { 1427 LoggedInUser *oauth.MultiAccountUser 1428 RepoInfo repoinfo.RepoInfo 1429 Forks []models.Repo 1430 Branches []types.Branch 1431 Tags []*types.TagReference 1432 Base string 1433 Head string 1434 1435 Active string 1436} 1437 1438func (p *Pages) RepoCompareNew(w io.Writer, params RepoCompareNewParams) error { 1439 params.Active = "overview" 1440 return p.executeRepo("repo/compare/new", w, params) 1441} 1442 1443type RepoCompareAllowPullParams struct { 1444 LoggedInUser *oauth.MultiAccountUser 1445 RepoInfo repoinfo.RepoInfo 1446 Base string 1447 Head string 1448} 1449 1450func (p *Pages) RepoCompareAllowPullFragment(w io.Writer, params RepoCompareAllowPullParams) error { 1451 return p.executePlain("repo/fragments/compareAllowPull", w, params) 1452} 1453 1454type RepoCompareDiffFragmentParams struct { 1455 Diff types.NiceDiff 1456 DiffOpts types.DiffOpts 1457} 1458 1459func (p *Pages) RepoCompareDiffFragment(w io.Writer, params RepoCompareDiffFragmentParams) error { 1460 return p.executePlain("repo/fragments/diff", w, []any{&params.Diff, &params.DiffOpts}) 1461} 1462 1463type LabelPanelParams struct { 1464 LoggedInUser *oauth.MultiAccountUser 1465 RepoInfo repoinfo.RepoInfo 1466 Defs map[string]*models.LabelDefinition 1467 Subject string 1468 State models.LabelState 1469} 1470 1471func (p *Pages) LabelPanel(w io.Writer, params LabelPanelParams) error { 1472 return p.executePlain("repo/fragments/labelPanel", w, params) 1473} 1474 1475type EditLabelPanelParams struct { 1476 LoggedInUser *oauth.MultiAccountUser 1477 RepoInfo repoinfo.RepoInfo 1478 Defs map[string]*models.LabelDefinition 1479 Subject string 1480 State models.LabelState 1481} 1482 1483func (p *Pages) EditLabelPanel(w io.Writer, params EditLabelPanelParams) error { 1484 return p.executePlain("repo/fragments/editLabelPanel", w, params) 1485} 1486 1487type PipelinesParams struct { 1488 LoggedInUser *oauth.MultiAccountUser 1489 RepoInfo repoinfo.RepoInfo 1490 Pipelines []models.Pipeline 1491 Active string 1492 FilterKind string 1493 Total int64 1494} 1495 1496func (p *Pages) Pipelines(w io.Writer, params PipelinesParams) error { 1497 params.Active = "pipelines" 1498 return p.executeRepo("repo/pipelines/pipelines", w, params) 1499} 1500 1501type LogBlockParams struct { 1502 Id int 1503 Name string 1504 Command string 1505 Collapsed bool 1506 StartTime time.Time 1507} 1508 1509func (p *Pages) LogBlock(w io.Writer, params LogBlockParams) error { 1510 return p.executePlain("repo/pipelines/fragments/logBlock", w, params) 1511} 1512 1513type LogBlockEndParams struct { 1514 Id int 1515 StartTime time.Time 1516 EndTime time.Time 1517} 1518 1519func (p *Pages) LogBlockEnd(w io.Writer, params LogBlockEndParams) error { 1520 return p.executePlain("repo/pipelines/fragments/logBlockEnd", w, params) 1521} 1522 1523type LogLineParams struct { 1524 Id int 1525 Content string 1526} 1527 1528func (p *Pages) LogLine(w io.Writer, params LogLineParams) error { 1529 return p.executePlain("repo/pipelines/fragments/logLine", w, params) 1530} 1531 1532type WorkflowParams struct { 1533 LoggedInUser *oauth.MultiAccountUser 1534 RepoInfo repoinfo.RepoInfo 1535 Pipeline models.Pipeline 1536 Workflow string 1537 LogUrl string 1538 Active string 1539} 1540 1541func (p *Pages) Workflow(w io.Writer, params WorkflowParams) error { 1542 params.Active = "pipelines" 1543 return p.executeRepo("repo/pipelines/workflow", w, params) 1544} 1545 1546type PutStringParams struct { 1547 LoggedInUser *oauth.MultiAccountUser 1548 Action string 1549 1550 // this is supplied in the case of editing an existing string 1551 String models.String 1552} 1553 1554func (p *Pages) PutString(w io.Writer, params PutStringParams) error { 1555 return p.execute("strings/put", w, params) 1556} 1557 1558type StringsDashboardParams struct { 1559 LoggedInUser *oauth.MultiAccountUser 1560 Card ProfileCard 1561 Strings []models.String 1562} 1563 1564func (p *Pages) StringsDashboard(w io.Writer, params StringsDashboardParams) error { 1565 return p.execute("strings/dashboard", w, params) 1566} 1567 1568type StringTimelineParams struct { 1569 LoggedInUser *oauth.MultiAccountUser 1570 Strings []models.String 1571} 1572 1573func (p *Pages) StringsTimeline(w io.Writer, params StringTimelineParams) error { 1574 return p.execute("strings/timeline", w, params) 1575} 1576 1577type SingleStringParams struct { 1578 LoggedInUser *oauth.MultiAccountUser 1579 ShowRendered bool 1580 RenderToggle bool 1581 RenderedContents template.HTML 1582 String *models.String 1583 Stats models.StringStats 1584 IsStarred bool 1585 StarCount int 1586 Owner identity.Identity 1587} 1588 1589func (p *Pages) SingleString(w io.Writer, params SingleStringParams) error { 1590 return p.execute("strings/string", w, params) 1591} 1592 1593func (p *Pages) Home(w io.Writer, params TimelineParams) error { 1594 return p.execute("timeline/home", w, params) 1595} 1596 1597func (p *Pages) Static() http.Handler { 1598 if p.dev { 1599 return http.StripPrefix("/static/", http.FileServer(http.Dir("appview/pages/static"))) 1600 } 1601 1602 sub, err := fs.Sub(p.embedFS, "static") 1603 if err != nil { 1604 p.logger.Error("no static dir found? that's crazy", "err", err) 1605 panic(err) 1606 } 1607 // Custom handler to apply Cache-Control headers for font files 1608 return Cache(http.StripPrefix("/static/", http.FileServer(http.FS(sub)))) 1609} 1610 1611func Cache(h http.Handler) http.Handler { 1612 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1613 path := strings.Split(r.URL.Path, "?")[0] 1614 1615 if strings.HasSuffix(path, ".css") { 1616 // on day for css files 1617 w.Header().Set("Cache-Control", "public, max-age=86400") 1618 } else { 1619 w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") 1620 } 1621 h.ServeHTTP(w, r) 1622 }) 1623} 1624 1625func (p *Pages) CssContentHash() string { 1626 cssFile, err := p.embedFS.Open("static/tw.css") 1627 if err != nil { 1628 slog.Debug("Error opening CSS file", "err", err) 1629 return "" 1630 } 1631 defer cssFile.Close() 1632 1633 hasher := sha256.New() 1634 if _, err := io.Copy(hasher, cssFile); err != nil { 1635 slog.Debug("Error hashing CSS file", "err", err) 1636 return "" 1637 } 1638 1639 return hex.EncodeToString(hasher.Sum(nil))[:8] // Use first 8 chars of hash 1640} 1641 1642func (p *Pages) DangerPasswordTokenStep(w io.Writer) error { 1643 return p.executePlain("user/settings/fragments/dangerPasswordToken", w, nil) 1644} 1645 1646func (p *Pages) DangerPasswordSuccess(w io.Writer) error { 1647 return p.executePlain("user/settings/fragments/dangerPasswordSuccess", w, nil) 1648} 1649 1650func (p *Pages) DangerDeleteTokenStep(w io.Writer) error { 1651 return p.executePlain("user/settings/fragments/dangerDeleteToken", w, nil) 1652} 1653 1654func (p *Pages) Error500(w io.Writer) error { 1655 return p.execute("errors/500", w, nil) 1656} 1657 1658func (p *Pages) Error404(w io.Writer) error { 1659 return p.execute("errors/404", w, nil) 1660} 1661 1662func (p *Pages) ErrorKnot404(w io.Writer) error { 1663 return p.execute("errors/knot404", w, nil) 1664} 1665 1666func (p *Pages) Error503(w io.Writer) error { 1667 return p.execute("errors/503", w, nil) 1668}