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