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