This repository has no description
0

Configure Feed

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

core / appview / pages / pages.go
44 kB 1701 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.TimelineEvent 407 Repos []models.Repo 408 GfiLabel *models.LabelDefinition 409 BlueskyPosts []models.BskyPost 410} 411 412func (p *Pages) Timeline(w io.Writer, params TimelineParams) error { 413 return p.execute("timeline/timeline", w, params) 414} 415 416type GoodFirstIssuesParams struct { 417 LoggedInUser *oauth.MultiAccountUser 418 Issues []models.Issue 419 RepoGroups []*models.RepoGroup 420 LabelDefs map[string]*models.LabelDefinition 421 GfiLabel *models.LabelDefinition 422 Page pagination.Page 423} 424 425func (p *Pages) GoodFirstIssues(w io.Writer, params GoodFirstIssuesParams) error { 426 return p.execute("goodfirstissues/index", w, params) 427} 428 429type UserProfileSettingsParams struct { 430 LoggedInUser *oauth.MultiAccountUser 431 Tab string 432 PunchcardPreference models.PunchcardPreference 433 IsTnglSh bool 434 IsDeactivated bool 435 HandleOpen bool 436} 437 438func (p *Pages) UserProfileSettings(w io.Writer, params UserProfileSettingsParams) error { 439 params.Tab = "profile" 440 return p.execute("user/settings/profile", w, params) 441} 442 443type NotificationsParams struct { 444 LoggedInUser *oauth.MultiAccountUser 445 Notifications []*models.NotificationWithEntity 446 UnreadCount int 447 Page pagination.Page 448 Total int64 449} 450 451func (p *Pages) Notifications(w io.Writer, params NotificationsParams) error { 452 return p.execute("notifications/list", w, params) 453} 454 455type NotificationItemParams struct { 456 Notification *models.Notification 457} 458 459func (p *Pages) NotificationItem(w io.Writer, params NotificationItemParams) error { 460 return p.executePlain("notifications/fragments/item", w, params) 461} 462 463type NotificationCountParams struct { 464 Count int64 465} 466 467func (p *Pages) NotificationCount(w io.Writer, params NotificationCountParams) error { 468 return p.executePlain("notifications/fragments/count", w, params) 469} 470 471type UserKeysSettingsParams struct { 472 LoggedInUser *oauth.MultiAccountUser 473 PubKeys []models.PublicKey 474 Tab string 475} 476 477func (p *Pages) UserKeysSettings(w io.Writer, params UserKeysSettingsParams) error { 478 params.Tab = "keys" 479 return p.execute("user/settings/keys", w, params) 480} 481 482type UserEmailsSettingsParams struct { 483 LoggedInUser *oauth.MultiAccountUser 484 Emails []models.Email 485 Tab string 486} 487 488func (p *Pages) UserEmailsSettings(w io.Writer, params UserEmailsSettingsParams) error { 489 params.Tab = "emails" 490 return p.execute("user/settings/emails", w, params) 491} 492 493type UserNotificationSettingsParams struct { 494 LoggedInUser *oauth.MultiAccountUser 495 Preferences *models.NotificationPreferences 496 Tab string 497} 498 499func (p *Pages) UserNotificationSettings(w io.Writer, params UserNotificationSettingsParams) error { 500 params.Tab = "notifications" 501 return p.execute("user/settings/notifications", w, params) 502} 503 504type UserSiteSettingsParams struct { 505 LoggedInUser *oauth.MultiAccountUser 506 Claim *models.DomainClaim 507 SitesDomain string 508 IsTnglHandle bool 509 Tab string 510} 511 512func (p *Pages) UserSiteSettings(w io.Writer, params UserSiteSettingsParams) error { 513 params.Tab = "sites" 514 return p.execute("user/settings/sites", w, params) 515} 516 517type UpgradeBannerParams struct { 518 Registrations []models.Registration 519 Spindles []models.Spindle 520} 521 522func (p *Pages) UpgradeBanner(w io.Writer, params UpgradeBannerParams) error { 523 return p.executePlain("banner", w, params) 524} 525 526type KnotsParams struct { 527 LoggedInUser *oauth.MultiAccountUser 528 Registrations []models.Registration 529 Tab string 530} 531 532func (p *Pages) Knots(w io.Writer, params KnotsParams) error { 533 params.Tab = "knots" 534 return p.execute("knots/index", w, params) 535} 536 537type KnotParams struct { 538 LoggedInUser *oauth.MultiAccountUser 539 Registration *models.Registration 540 Members []string 541 Repos map[string][]models.Repo 542 IsOwner bool 543 Tab string 544} 545 546func (p *Pages) Knot(w io.Writer, params KnotParams) error { 547 return p.execute("knots/dashboard", w, params) 548} 549 550type KnotListingParams struct { 551 *models.Registration 552} 553 554func (p *Pages) KnotListing(w io.Writer, params KnotListingParams) error { 555 return p.executePlain("knots/fragments/knotListing", w, params) 556} 557 558type SpindlesParams struct { 559 LoggedInUser *oauth.MultiAccountUser 560 Spindles []models.Spindle 561 Tab string 562} 563 564func (p *Pages) Spindles(w io.Writer, params SpindlesParams) error { 565 params.Tab = "spindles" 566 return p.execute("spindles/index", w, params) 567} 568 569type SpindleListingParams struct { 570 models.Spindle 571 Tab string 572} 573 574func (p *Pages) SpindleListing(w io.Writer, params SpindleListingParams) error { 575 return p.executePlain("spindles/fragments/spindleListing", w, params) 576} 577 578type SpindleDashboardParams struct { 579 LoggedInUser *oauth.MultiAccountUser 580 Spindle models.Spindle 581 Members []string 582 Repos map[string][]models.Repo 583 Tab string 584} 585 586func (p *Pages) SpindleDashboard(w io.Writer, params SpindleDashboardParams) error { 587 return p.execute("spindles/dashboard", w, params) 588} 589 590type NewRepoParams struct { 591 LoggedInUser *oauth.MultiAccountUser 592 Knots []string 593} 594 595func (p *Pages) NewRepo(w io.Writer, params NewRepoParams) error { 596 return p.execute("repo/new", w, params) 597} 598 599type ForkRepoParams struct { 600 LoggedInUser *oauth.MultiAccountUser 601 Knots []string 602 RepoInfo repoinfo.RepoInfo 603} 604 605func (p *Pages) ForkRepo(w io.Writer, params ForkRepoParams) error { 606 return p.execute("repo/fork", w, params) 607} 608 609type ProfileCard struct { 610 UserDid string 611 HasProfile bool 612 FollowStatus models.FollowStatus 613 Punchcard *models.Punchcard 614 Profile *models.Profile 615 Stats ProfileStats 616 Active string 617} 618 619type ProfileStats struct { 620 RepoCount int64 621 StarredCount int64 622 StringCount int64 623 FollowersCount int64 624 FollowingCount int64 625} 626 627func (p *ProfileCard) GetTabs() [][]any { 628 tabs := [][]any{ 629 {"overview", "overview", "square-chart-gantt", nil}, 630 {"repos", "repos", "book-marked", p.Stats.RepoCount}, 631 {"starred", "starred", "star", p.Stats.StarredCount}, 632 {"strings", "strings", "line-squiggle", p.Stats.StringCount}, 633 } 634 635 return tabs 636} 637 638type ProfileOverviewParams struct { 639 LoggedInUser *oauth.MultiAccountUser 640 Repos []models.Repo 641 CollaboratingRepos []models.Repo 642 ProfileTimeline *models.ProfileTimeline 643 Card *ProfileCard 644 Active string 645 ShowPunchcard bool 646} 647 648func (p *Pages) ProfileOverview(w io.Writer, params ProfileOverviewParams) error { 649 params.Active = "overview" 650 return p.executeProfile("user/overview", w, params) 651} 652 653type ProfileReposParams struct { 654 LoggedInUser *oauth.MultiAccountUser 655 Repos []models.Repo 656 Card *ProfileCard 657 Active string 658 Page pagination.Page 659 RepoCount int 660 FilterQuery string 661} 662 663func (p *Pages) ProfileRepos(w io.Writer, params ProfileReposParams) error { 664 params.Active = "repos" 665 return p.executeProfile("user/repos", w, params) 666} 667 668type ProfileStarredParams struct { 669 LoggedInUser *oauth.MultiAccountUser 670 Repos []models.Repo 671 Card *ProfileCard 672 Page pagination.Page 673 Total int 674 Active string 675} 676 677func (p *Pages) ProfileStarred(w io.Writer, params ProfileStarredParams) error { 678 params.Active = "starred" 679 return p.executeProfile("user/starred", w, params) 680} 681 682type ProfileStringsParams struct { 683 LoggedInUser *oauth.MultiAccountUser 684 Strings []models.String 685 Card *ProfileCard 686 Active string 687} 688 689func (p *Pages) ProfileStrings(w io.Writer, params ProfileStringsParams) error { 690 params.Active = "strings" 691 return p.executeProfile("user/strings", w, params) 692} 693 694type FollowCard struct { 695 UserDid string 696 LoggedInUser *oauth.MultiAccountUser 697 FollowStatus models.FollowStatus 698 FollowersCount int64 699 FollowingCount int64 700 Profile *models.Profile 701} 702 703type ProfileFollowersParams struct { 704 LoggedInUser *oauth.MultiAccountUser 705 Followers []FollowCard 706 Card *ProfileCard 707 Active string 708} 709 710func (p *Pages) ProfileFollowers(w io.Writer, params ProfileFollowersParams) error { 711 params.Active = "overview" 712 return p.executeProfile("user/followers", w, params) 713} 714 715type ProfileFollowingParams struct { 716 LoggedInUser *oauth.MultiAccountUser 717 Following []FollowCard 718 Card *ProfileCard 719 Active string 720} 721 722func (p *Pages) ProfileFollowing(w io.Writer, params ProfileFollowingParams) error { 723 params.Active = "overview" 724 return p.executeProfile("user/following", w, params) 725} 726 727type FollowFragmentParams struct { 728 UserDid string 729 FollowStatus models.FollowStatus 730 FollowersCount int64 731} 732 733func (p *Pages) FollowFragment(w io.Writer, params FollowFragmentParams) error { 734 return p.executePlain("user/fragments/follow-oob", w, params) 735} 736 737type EditBioParams struct { 738 LoggedInUser *oauth.MultiAccountUser 739 Profile *models.Profile 740 AlsoKnownAs []string 741} 742 743func (p *Pages) EditBioFragment(w io.Writer, params EditBioParams) error { 744 return p.executePlain("user/fragments/editBio", w, params) 745} 746 747type EditPinsParams struct { 748 LoggedInUser *oauth.MultiAccountUser 749 Profile *models.Profile 750 AllRepos []PinnedRepo 751} 752 753type PinnedRepo struct { 754 IsPinned bool 755 models.Repo 756} 757 758func (p *Pages) EditPinsFragment(w io.Writer, params EditPinsParams) error { 759 return p.executePlain("user/fragments/editPins", w, params) 760} 761 762type StarBtnFragmentParams struct { 763 IsStarred bool 764 SubjectAt syntax.ATURI 765 StarCount int 766 RepoName string 767 HxSwapOob bool 768} 769 770func (p *Pages) StarBtnFragment(w io.Writer, params StarBtnFragmentParams) error { 771 params.HxSwapOob = true 772 return p.executePlain("fragments/starBtn", w, params) 773} 774 775type RepoIndexParams struct { 776 LoggedInUser *oauth.MultiAccountUser 777 RepoInfo repoinfo.RepoInfo 778 Active string 779 TagMap map[string][]string 780 CommitsTrunc []types.Commit 781 TagsTrunc []*types.TagReference 782 BranchesTrunc []types.Branch 783 // ForkInfo *types.ForkInfo 784 HTMLReadme template.HTML 785 Raw bool 786 EmailToDid map[string]string 787 VerifiedCommits commitverify.VerifiedCommits 788 Languages []types.RepoLanguageDetails 789 Pipelines map[string]models.Pipeline 790 NeedsKnotUpgrade bool 791 KnotUnreachable bool 792 types.RepoIndexResponse 793} 794 795func (p *Pages) RepoIndexPage(w io.Writer, params RepoIndexParams) error { 796 params.Active = "overview" 797 if params.IsEmpty { 798 return p.executeRepo("repo/empty", w, params) 799 } 800 801 if params.NeedsKnotUpgrade { 802 return p.executeRepo("repo/needsUpgrade", w, params) 803 } 804 805 if params.KnotUnreachable { 806 return p.executeRepo("repo/knotUnreachable", w, params) 807 } 808 809 rctx := p.rctx.Clone() 810 rctx.RepoInfo = params.RepoInfo 811 rctx.RepoInfo.Ref = params.Ref 812 rctx.RendererType = markup.RendererTypeRepoMarkdown 813 814 if params.ReadmeFileName != "" { 815 ext := filepath.Ext(params.ReadmeFileName) 816 switch ext { 817 case ".md", ".markdown", ".mdown", ".mkdn", ".mkd": 818 params.Raw = false 819 htmlString := rctx.RenderMarkdown(params.Readme) 820 sanitized := rctx.SanitizeDefault(htmlString) 821 params.HTMLReadme = template.HTML(sanitized) 822 default: 823 params.Raw = true 824 } 825 } 826 827 return p.executeRepo("repo/index", w, params) 828} 829 830type RepoLogParams struct { 831 LoggedInUser *oauth.MultiAccountUser 832 RepoInfo repoinfo.RepoInfo 833 TagMap map[string][]string 834 Active string 835 EmailToDid map[string]string 836 VerifiedCommits commitverify.VerifiedCommits 837 Pipelines map[string]models.Pipeline 838 839 types.RepoLogResponse 840} 841 842func (p *Pages) RepoLog(w io.Writer, params RepoLogParams) error { 843 params.Active = "overview" 844 return p.executeRepo("repo/log", w, params) 845} 846 847type RepoCommitParams struct { 848 LoggedInUser *oauth.MultiAccountUser 849 RepoInfo repoinfo.RepoInfo 850 Active string 851 EmailToDid map[string]string 852 Pipeline *models.Pipeline 853 DiffOpts types.DiffOpts 854 855 // singular because it's always going to be just one 856 VerifiedCommit commitverify.VerifiedCommits 857 858 types.RepoCommitResponse 859} 860 861func (p *Pages) RepoCommit(w io.Writer, params RepoCommitParams) error { 862 params.Active = "overview" 863 return p.executeRepo("repo/commit", w, params) 864} 865 866type RepoTreeParams struct { 867 LoggedInUser *oauth.MultiAccountUser 868 RepoInfo repoinfo.RepoInfo 869 Active string 870 BreadCrumbs [][]string 871 Path string 872 Raw bool 873 HTMLReadme template.HTML 874 EmailToDid map[string]string 875 LastCommitInfo *types.LastCommitInfo 876 types.RepoTreeResponse 877} 878 879type RepoTreeStats struct { 880 NumFolders uint64 881 NumFiles uint64 882} 883 884func (r RepoTreeParams) TreeStats() RepoTreeStats { 885 numFolders, numFiles := 0, 0 886 for _, f := range r.Files { 887 if !f.IsFile() { 888 numFolders += 1 889 } else if f.IsFile() { 890 numFiles += 1 891 } 892 } 893 894 return RepoTreeStats{ 895 NumFolders: uint64(numFolders), 896 NumFiles: uint64(numFiles), 897 } 898} 899 900func (p *Pages) RepoTree(w io.Writer, params RepoTreeParams) error { 901 params.Active = "overview" 902 903 rctx := p.rctx.Clone() 904 rctx.RepoInfo = params.RepoInfo 905 rctx.RepoInfo.Ref = params.Ref 906 rctx.RendererType = markup.RendererTypeRepoMarkdown 907 908 if params.ReadmeFileName != "" { 909 ext := filepath.Ext(params.ReadmeFileName) 910 switch ext { 911 case ".md", ".markdown", ".mdown", ".mkdn", ".mkd": 912 params.Raw = false 913 htmlString := rctx.RenderMarkdown(params.Readme) 914 sanitized := rctx.SanitizeDefault(htmlString) 915 params.HTMLReadme = template.HTML(sanitized) 916 default: 917 params.Raw = true 918 } 919 } 920 921 return p.executeRepo("repo/tree", w, params) 922} 923 924type RepoBranchesParams struct { 925 LoggedInUser *oauth.MultiAccountUser 926 RepoInfo repoinfo.RepoInfo 927 Active string 928 types.RepoBranchesResponse 929} 930 931func (p *Pages) RepoBranches(w io.Writer, params RepoBranchesParams) error { 932 params.Active = "overview" 933 return p.executeRepo("repo/branches", w, params) 934} 935 936type RepoTagsParams struct { 937 LoggedInUser *oauth.MultiAccountUser 938 RepoInfo repoinfo.RepoInfo 939 Active string 940 types.RepoTagsResponse 941 ArtifactMap map[plumbing.Hash][]models.Artifact 942 DanglingArtifacts []models.Artifact 943} 944 945func (p *Pages) RepoTags(w io.Writer, params RepoTagsParams) error { 946 params.Active = "overview" 947 return p.executeRepo("repo/tags", w, params) 948} 949 950type RepoTagParams struct { 951 LoggedInUser *oauth.MultiAccountUser 952 RepoInfo repoinfo.RepoInfo 953 Active string 954 types.RepoTagResponse 955 ArtifactMap map[plumbing.Hash][]models.Artifact 956 DanglingArtifacts []models.Artifact 957} 958 959func (p *Pages) RepoTag(w io.Writer, params RepoTagParams) error { 960 params.Active = "overview" 961 return p.executeRepo("repo/tag", w, params) 962} 963 964type RepoArtifactParams struct { 965 LoggedInUser *oauth.MultiAccountUser 966 RepoInfo repoinfo.RepoInfo 967 Artifact models.Artifact 968} 969 970func (p *Pages) RepoArtifactFragment(w io.Writer, params RepoArtifactParams) error { 971 return p.executePlain("repo/fragments/artifact", w, params) 972} 973 974type RepoBlobParams struct { 975 LoggedInUser *oauth.MultiAccountUser 976 RepoInfo repoinfo.RepoInfo 977 Active string 978 BreadCrumbs [][]string 979 BlobView models.BlobView 980 EmailToDid map[string]string 981 LastCommitInfo *types.LastCommitInfo 982 *tangled.RepoBlob_Output 983} 984 985func (p *Pages) RepoBlob(w io.Writer, params RepoBlobParams) error { 986 params.Active = "overview" 987 return p.executeRepo("repo/blob", w, params) 988} 989 990type Collaborator struct { 991 Did string 992 Role string 993} 994 995type RepoSettingsParams struct { 996 LoggedInUser *oauth.MultiAccountUser 997 RepoInfo repoinfo.RepoInfo 998 Collaborators []Collaborator 999 Active string 1000 Branches []types.Branch 1001 Spindles []string 1002 CurrentSpindle string 1003 Secrets []*tangled.RepoListSecrets_Secret 1004 1005 // TODO: use repoinfo.roles 1006 IsCollaboratorInviteAllowed bool 1007} 1008 1009func (p *Pages) RepoSettings(w io.Writer, params RepoSettingsParams) error { 1010 params.Active = "settings" 1011 return p.executeRepo("repo/settings", w, params) 1012} 1013 1014type RepoGeneralSettingsParams struct { 1015 LoggedInUser *oauth.MultiAccountUser 1016 RepoInfo repoinfo.RepoInfo 1017 Labels []models.LabelDefinition 1018 DefaultLabels []models.LabelDefinition 1019 SubscribedLabels map[string]struct{} 1020 ShouldSubscribeAll bool 1021 Active string 1022 Tab string 1023 Branches []types.Branch 1024} 1025 1026func (p *Pages) RepoGeneralSettings(w io.Writer, params RepoGeneralSettingsParams) error { 1027 params.Active = "settings" 1028 params.Tab = "general" 1029 return p.executeRepo("repo/settings/general", w, params) 1030} 1031 1032type RepoAccessSettingsParams struct { 1033 LoggedInUser *oauth.MultiAccountUser 1034 RepoInfo repoinfo.RepoInfo 1035 Active string 1036 Tab string 1037 Collaborators []Collaborator 1038} 1039 1040func (p *Pages) RepoAccessSettings(w io.Writer, params RepoAccessSettingsParams) error { 1041 params.Active = "settings" 1042 params.Tab = "access" 1043 return p.executeRepo("repo/settings/access", w, params) 1044} 1045 1046type RepoPipelineSettingsParams struct { 1047 LoggedInUser *oauth.MultiAccountUser 1048 RepoInfo repoinfo.RepoInfo 1049 Active string 1050 Tab string 1051 Spindles []string 1052 CurrentSpindle string 1053 Secrets []map[string]any 1054} 1055 1056func (p *Pages) RepoPipelineSettings(w io.Writer, params RepoPipelineSettingsParams) error { 1057 params.Active = "settings" 1058 params.Tab = "pipelines" 1059 return p.executeRepo("repo/settings/pipelines", w, params) 1060} 1061 1062type RepoWebhooksSettingsParams struct { 1063 LoggedInUser *oauth.MultiAccountUser 1064 RepoInfo repoinfo.RepoInfo 1065 Active string 1066 Tab string 1067 Webhooks []models.Webhook 1068 WebhookDeliveries map[int64][]models.WebhookDelivery 1069} 1070 1071func (p *Pages) RepoWebhooksSettings(w io.Writer, params RepoWebhooksSettingsParams) error { 1072 params.Active = "settings" 1073 params.Tab = "hooks" 1074 return p.executeRepo("repo/settings/hooks", w, params) 1075} 1076 1077type WebhookDeliveriesListParams struct { 1078 LoggedInUser *oauth.MultiAccountUser 1079 RepoInfo repoinfo.RepoInfo 1080 Webhook *models.Webhook 1081 Deliveries []models.WebhookDelivery 1082} 1083 1084func (p *Pages) WebhookDeliveriesList(w io.Writer, params WebhookDeliveriesListParams) error { 1085 tpl, err := p.parse("repo/settings/fragments/webhookDeliveries") 1086 if err != nil { 1087 return err 1088 } 1089 return tpl.ExecuteTemplate(w, "repo/settings/fragments/webhookDeliveries", params) 1090} 1091 1092type RepoSiteSettingsParams struct { 1093 LoggedInUser *oauth.MultiAccountUser 1094 RepoInfo repoinfo.RepoInfo 1095 Active string 1096 Tab string 1097 Branches []types.Branch 1098 SiteConfig *models.RepoSite 1099 OwnerClaim *models.DomainClaim 1100 Deploys []models.SiteDeploy 1101 IndexSiteTakenBy string // repo_at of another repo that already holds is_index, or "" 1102} 1103 1104func (p *Pages) RepoSiteSettings(w io.Writer, params RepoSiteSettingsParams) error { 1105 params.Active = "settings" 1106 params.Tab = "sites" 1107 return p.executeRepo("repo/settings/sites", w, params) 1108} 1109 1110type RepoIssuesParams struct { 1111 LoggedInUser *oauth.MultiAccountUser 1112 RepoInfo repoinfo.RepoInfo 1113 Active string 1114 Issues []models.Issue 1115 IssueCount int 1116 LabelDefs map[string]*models.LabelDefinition 1117 Page pagination.Page 1118 FilterState string 1119 FilterQuery string 1120} 1121 1122func (p *Pages) RepoIssues(w io.Writer, params RepoIssuesParams) error { 1123 params.Active = "issues" 1124 return p.executeRepo("repo/issues/issues", w, params) 1125} 1126 1127type RepoSingleIssueParams struct { 1128 LoggedInUser *oauth.MultiAccountUser 1129 RepoInfo repoinfo.RepoInfo 1130 Active string 1131 Issue *models.Issue 1132 CommentList []models.CommentListItem 1133 Backlinks []models.RichReferenceLink 1134 LabelDefs map[string]*models.LabelDefinition 1135 1136 Reactions map[models.ReactionKind]models.ReactionDisplayData 1137 UserReacted map[models.ReactionKind]bool 1138} 1139 1140func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error { 1141 params.Active = "issues" 1142 return p.executeRepo("repo/issues/issue", w, params) 1143} 1144 1145type EditIssueParams struct { 1146 LoggedInUser *oauth.MultiAccountUser 1147 RepoInfo repoinfo.RepoInfo 1148 Issue *models.Issue 1149 Action string 1150} 1151 1152func (p *Pages) EditIssueFragment(w io.Writer, params EditIssueParams) error { 1153 params.Action = "edit" 1154 return p.executePlain("repo/issues/fragments/putIssue", w, params) 1155} 1156 1157type ThreadReactionFragmentParams struct { 1158 ThreadAt syntax.ATURI 1159 Kind models.ReactionKind 1160 Count int 1161 Users []string 1162 IsReacted bool 1163} 1164 1165func (p *Pages) ThreadReactionFragment(w io.Writer, params ThreadReactionFragmentParams) error { 1166 return p.executePlain("repo/fragments/reaction", w, params) 1167} 1168 1169type RepoNewIssueParams struct { 1170 LoggedInUser *oauth.MultiAccountUser 1171 RepoInfo repoinfo.RepoInfo 1172 Issue *models.Issue // existing issue if any -- passed when editing 1173 Active string 1174 Action string 1175} 1176 1177func (p *Pages) RepoNewIssue(w io.Writer, params RepoNewIssueParams) error { 1178 params.Active = "issues" 1179 params.Action = "create" 1180 return p.executeRepo("repo/issues/new", w, params) 1181} 1182 1183type EditIssueCommentParams struct { 1184 LoggedInUser *oauth.MultiAccountUser 1185 RepoInfo repoinfo.RepoInfo 1186 Issue *models.Issue 1187 Comment *models.IssueComment 1188} 1189 1190func (p *Pages) EditIssueCommentFragment(w io.Writer, params EditIssueCommentParams) error { 1191 return p.executePlain("repo/issues/fragments/editIssueComment", w, params) 1192} 1193 1194type ReplyIssueCommentPlaceholderParams struct { 1195 LoggedInUser *oauth.MultiAccountUser 1196 RepoInfo repoinfo.RepoInfo 1197 Issue *models.Issue 1198 Comment *models.IssueComment 1199} 1200 1201func (p *Pages) ReplyIssueCommentPlaceholderFragment(w io.Writer, params ReplyIssueCommentPlaceholderParams) error { 1202 return p.executePlain("repo/issues/fragments/replyIssueCommentPlaceholder", w, params) 1203} 1204 1205type ReplyIssueCommentParams struct { 1206 LoggedInUser *oauth.MultiAccountUser 1207 RepoInfo repoinfo.RepoInfo 1208 Issue *models.Issue 1209 Comment *models.IssueComment 1210} 1211 1212func (p *Pages) ReplyIssueCommentFragment(w io.Writer, params ReplyIssueCommentParams) error { 1213 return p.executePlain("repo/issues/fragments/replyComment", w, params) 1214} 1215 1216type IssueCommentBodyParams struct { 1217 LoggedInUser *oauth.MultiAccountUser 1218 RepoInfo repoinfo.RepoInfo 1219 Issue *models.Issue 1220 Comment *models.IssueComment 1221} 1222 1223func (p *Pages) IssueCommentBodyFragment(w io.Writer, params IssueCommentBodyParams) error { 1224 return p.executePlain("repo/issues/fragments/issueCommentBody", w, params) 1225} 1226 1227type RepoNewPullParams struct { 1228 LoggedInUser *oauth.MultiAccountUser 1229 RepoInfo repoinfo.RepoInfo 1230 Branches []types.Branch 1231 Strategy string 1232 SourceBranch string 1233 TargetBranch string 1234 Title string 1235 Body string 1236 Active string 1237} 1238 1239func (p *Pages) RepoNewPull(w io.Writer, params RepoNewPullParams) error { 1240 params.Active = "pulls" 1241 return p.executeRepo("repo/pulls/new", w, params) 1242} 1243 1244type RepoPullsParams struct { 1245 LoggedInUser *oauth.MultiAccountUser 1246 RepoInfo repoinfo.RepoInfo 1247 Pulls []*models.Pull 1248 Active string 1249 FilterState string 1250 FilterQuery string 1251 Stacks []models.Stack 1252 Pipelines map[string]models.Pipeline 1253 LabelDefs map[string]*models.LabelDefinition 1254 Page pagination.Page 1255 PullCount int 1256} 1257 1258func (p *Pages) RepoPulls(w io.Writer, params RepoPullsParams) error { 1259 params.Active = "pulls" 1260 return p.executeRepo("repo/pulls/pulls", w, params) 1261} 1262 1263type ResubmitResult uint64 1264 1265const ( 1266 ShouldResubmit ResubmitResult = iota 1267 ShouldNotResubmit 1268 Unknown 1269) 1270 1271func (r ResubmitResult) Yes() bool { 1272 return r == ShouldResubmit 1273} 1274func (r ResubmitResult) No() bool { 1275 return r == ShouldNotResubmit 1276} 1277func (r ResubmitResult) Unknown() bool { 1278 return r == Unknown 1279} 1280 1281type RepoSinglePullParams struct { 1282 LoggedInUser *oauth.MultiAccountUser 1283 RepoInfo repoinfo.RepoInfo 1284 Active string 1285 Pull *models.Pull 1286 Stack models.Stack 1287 Backlinks []models.RichReferenceLink 1288 BranchDeleteStatus *models.BranchDeleteStatus 1289 MergeCheck types.MergeCheckResponse 1290 ResubmitCheck ResubmitResult 1291 Pipelines map[string]models.Pipeline 1292 Diff types.DiffRenderer 1293 DiffOpts types.DiffOpts 1294 ActiveRound int 1295 IsInterdiff bool 1296 1297 Reactions map[models.ReactionKind]models.ReactionDisplayData 1298 UserReacted map[models.ReactionKind]bool 1299 1300 LabelDefs map[string]*models.LabelDefinition 1301} 1302 1303func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error { 1304 params.Active = "pulls" 1305 return p.executeRepo("repo/pulls/pull", w, params) 1306} 1307 1308type RepoPullPatchParams struct { 1309 LoggedInUser *oauth.MultiAccountUser 1310 RepoInfo repoinfo.RepoInfo 1311 Pull *models.Pull 1312 Stack models.Stack 1313 Diff *types.NiceDiff 1314 Round int 1315 Submission *models.PullSubmission 1316 DiffOpts types.DiffOpts 1317} 1318 1319// this name is a mouthful 1320func (p *Pages) RepoPullPatchPage(w io.Writer, params RepoPullPatchParams) error { 1321 return p.execute("repo/pulls/patch", w, params) 1322} 1323 1324type RepoPullInterdiffParams struct { 1325 LoggedInUser *oauth.MultiAccountUser 1326 RepoInfo repoinfo.RepoInfo 1327 Pull *models.Pull 1328 Round int 1329 Interdiff *patchutil.InterdiffResult 1330 DiffOpts types.DiffOpts 1331} 1332 1333// this name is a mouthful 1334func (p *Pages) RepoPullInterdiffPage(w io.Writer, params RepoPullInterdiffParams) error { 1335 return p.execute("repo/pulls/interdiff", w, params) 1336} 1337 1338type PullPatchUploadParams struct { 1339 RepoInfo repoinfo.RepoInfo 1340} 1341 1342func (p *Pages) PullPatchUploadFragment(w io.Writer, params PullPatchUploadParams) error { 1343 return p.executePlain("repo/pulls/fragments/pullPatchUpload", w, params) 1344} 1345 1346type PullCompareBranchesParams struct { 1347 RepoInfo repoinfo.RepoInfo 1348 Branches []types.Branch 1349 SourceBranch string 1350} 1351 1352func (p *Pages) PullCompareBranchesFragment(w io.Writer, params PullCompareBranchesParams) error { 1353 return p.executePlain("repo/pulls/fragments/pullCompareBranches", w, params) 1354} 1355 1356type PullCompareForkParams struct { 1357 RepoInfo repoinfo.RepoInfo 1358 Forks []models.Repo 1359 Selected string 1360} 1361 1362func (p *Pages) PullCompareForkFragment(w io.Writer, params PullCompareForkParams) error { 1363 return p.executePlain("repo/pulls/fragments/pullCompareForks", w, params) 1364} 1365 1366type PullCompareForkBranchesParams struct { 1367 RepoInfo repoinfo.RepoInfo 1368 SourceBranches []types.Branch 1369 TargetBranches []types.Branch 1370} 1371 1372func (p *Pages) PullCompareForkBranchesFragment(w io.Writer, params PullCompareForkBranchesParams) error { 1373 return p.executePlain("repo/pulls/fragments/pullCompareForksBranches", w, params) 1374} 1375 1376type PullResubmitParams struct { 1377 LoggedInUser *oauth.MultiAccountUser 1378 RepoInfo repoinfo.RepoInfo 1379 Pull *models.Pull 1380 SubmissionId int 1381} 1382 1383func (p *Pages) PullResubmitFragment(w io.Writer, params PullResubmitParams) error { 1384 return p.executePlain("repo/pulls/fragments/pullResubmit", w, params) 1385} 1386 1387type PullActionsParams struct { 1388 LoggedInUser *oauth.MultiAccountUser 1389 RepoInfo repoinfo.RepoInfo 1390 Pull *models.Pull 1391 RoundNumber int 1392 MergeCheck types.MergeCheckResponse 1393 ResubmitCheck ResubmitResult 1394 BranchDeleteStatus *models.BranchDeleteStatus 1395 Stack models.Stack 1396} 1397 1398func (p *Pages) PullActionsFragment(w io.Writer, params PullActionsParams) error { 1399 return p.executePlain("repo/pulls/fragments/pullActions", w, params) 1400} 1401 1402type PullNewCommentParams struct { 1403 LoggedInUser *oauth.MultiAccountUser 1404 RepoInfo repoinfo.RepoInfo 1405 Pull *models.Pull 1406 RoundNumber int 1407} 1408 1409func (p *Pages) PullNewCommentFragment(w io.Writer, params PullNewCommentParams) error { 1410 return p.executePlain("repo/pulls/fragments/pullNewComment", w, params) 1411} 1412 1413type RepoCompareParams struct { 1414 LoggedInUser *oauth.MultiAccountUser 1415 RepoInfo repoinfo.RepoInfo 1416 Forks []models.Repo 1417 Branches []types.Branch 1418 Tags []*types.TagReference 1419 Base string 1420 Head string 1421 Diff *types.NiceDiff 1422 DiffOpts types.DiffOpts 1423 1424 Active string 1425} 1426 1427func (p *Pages) RepoCompare(w io.Writer, params RepoCompareParams) error { 1428 params.Active = "overview" 1429 return p.executeRepo("repo/compare/compare", w, params) 1430} 1431 1432type RepoCompareNewParams struct { 1433 LoggedInUser *oauth.MultiAccountUser 1434 RepoInfo repoinfo.RepoInfo 1435 Forks []models.Repo 1436 Branches []types.Branch 1437 Tags []*types.TagReference 1438 Base string 1439 Head string 1440 1441 Active string 1442} 1443 1444func (p *Pages) RepoCompareNew(w io.Writer, params RepoCompareNewParams) error { 1445 params.Active = "overview" 1446 return p.executeRepo("repo/compare/new", w, params) 1447} 1448 1449type RepoCompareAllowPullParams struct { 1450 LoggedInUser *oauth.MultiAccountUser 1451 RepoInfo repoinfo.RepoInfo 1452 Base string 1453 Head string 1454} 1455 1456func (p *Pages) RepoCompareAllowPullFragment(w io.Writer, params RepoCompareAllowPullParams) error { 1457 return p.executePlain("repo/fragments/compareAllowPull", w, params) 1458} 1459 1460type RepoCompareDiffFragmentParams struct { 1461 Diff types.NiceDiff 1462 DiffOpts types.DiffOpts 1463} 1464 1465func (p *Pages) RepoCompareDiffFragment(w io.Writer, params RepoCompareDiffFragmentParams) error { 1466 return p.executePlain("repo/fragments/diff", w, []any{&params.Diff, &params.DiffOpts}) 1467} 1468 1469type LabelPanelParams struct { 1470 LoggedInUser *oauth.MultiAccountUser 1471 RepoInfo repoinfo.RepoInfo 1472 Defs map[string]*models.LabelDefinition 1473 Subject string 1474 State models.LabelState 1475} 1476 1477func (p *Pages) LabelPanel(w io.Writer, params LabelPanelParams) error { 1478 return p.executePlain("repo/fragments/labelPanel", w, params) 1479} 1480 1481type EditLabelPanelParams struct { 1482 LoggedInUser *oauth.MultiAccountUser 1483 RepoInfo repoinfo.RepoInfo 1484 Defs map[string]*models.LabelDefinition 1485 Subject string 1486 State models.LabelState 1487} 1488 1489func (p *Pages) EditLabelPanel(w io.Writer, params EditLabelPanelParams) error { 1490 return p.executePlain("repo/fragments/editLabelPanel", w, params) 1491} 1492 1493type RepoStarsParams struct { 1494 LoggedInUser *oauth.MultiAccountUser 1495 RepoInfo repoinfo.RepoInfo 1496 Active string 1497 Starrers []models.Star 1498} 1499 1500func (p *Pages) RepoStars(w io.Writer, params RepoStarsParams) error { 1501 params.Active = "overview" 1502 return p.executeRepo("repo/stars", w, params) 1503} 1504 1505type PipelinesParams struct { 1506 LoggedInUser *oauth.MultiAccountUser 1507 RepoInfo repoinfo.RepoInfo 1508 Pipelines []models.Pipeline 1509 Active string 1510 FilterKind string 1511 Total int64 1512} 1513 1514func (p *Pages) Pipelines(w io.Writer, params PipelinesParams) error { 1515 params.Active = "pipelines" 1516 return p.executeRepo("repo/pipelines/pipelines", w, params) 1517} 1518 1519type LogBlockParams struct { 1520 Id int 1521 Name string 1522 Command string 1523 Collapsed bool 1524 StartTime time.Time 1525} 1526 1527func (p *Pages) LogBlock(w io.Writer, params LogBlockParams) error { 1528 return p.executePlain("repo/pipelines/fragments/logBlock", w, params) 1529} 1530 1531type LogBlockEndParams struct { 1532 Id int 1533 StartTime time.Time 1534 EndTime time.Time 1535} 1536 1537func (p *Pages) LogBlockEnd(w io.Writer, params LogBlockEndParams) error { 1538 return p.executePlain("repo/pipelines/fragments/logBlockEnd", w, params) 1539} 1540 1541type LogLineParams struct { 1542 Id int 1543 Content string 1544} 1545 1546func (p *Pages) LogLine(w io.Writer, params LogLineParams) error { 1547 return p.executePlain("repo/pipelines/fragments/logLine", w, params) 1548} 1549 1550type WorkflowParams struct { 1551 LoggedInUser *oauth.MultiAccountUser 1552 RepoInfo repoinfo.RepoInfo 1553 Pipeline models.Pipeline 1554 Workflow string 1555 LogUrl string 1556 Active string 1557} 1558 1559func (p *Pages) Workflow(w io.Writer, params WorkflowParams) error { 1560 params.Active = "pipelines" 1561 return p.executeRepo("repo/pipelines/workflow", w, params) 1562} 1563 1564type PutStringParams struct { 1565 LoggedInUser *oauth.MultiAccountUser 1566 Action string 1567 1568 // this is supplied in the case of editing an existing string 1569 String models.String 1570} 1571 1572func (p *Pages) PutString(w io.Writer, params PutStringParams) error { 1573 return p.execute("strings/put", w, params) 1574} 1575 1576type StringsDashboardParams struct { 1577 LoggedInUser *oauth.MultiAccountUser 1578 Card ProfileCard 1579 Strings []models.String 1580} 1581 1582func (p *Pages) StringsDashboard(w io.Writer, params StringsDashboardParams) error { 1583 return p.execute("strings/dashboard", w, params) 1584} 1585 1586type StringTimelineParams struct { 1587 LoggedInUser *oauth.MultiAccountUser 1588 Strings []models.String 1589} 1590 1591func (p *Pages) StringsTimeline(w io.Writer, params StringTimelineParams) error { 1592 return p.execute("strings/timeline", w, params) 1593} 1594 1595type SingleStringParams struct { 1596 LoggedInUser *oauth.MultiAccountUser 1597 ShowRendered bool 1598 RenderToggle bool 1599 RenderedContents template.HTML 1600 String *models.String 1601 Stats models.StringStats 1602 IsStarred bool 1603 StarCount int 1604 Owner identity.Identity 1605} 1606 1607func (p *Pages) SingleString(w io.Writer, params SingleStringParams) error { 1608 return p.execute("strings/string", w, params) 1609} 1610 1611type SearchReposParams struct { 1612 LoggedInUser *oauth.MultiAccountUser 1613 Repos []models.Repo 1614 Page pagination.Page 1615 ResultCount int 1616 FilterQuery string 1617 SortParam string 1618 TimeTaken time.Duration 1619 DocCount int64 1620} 1621 1622func (p *Pages) SearchRepos(w io.Writer, params SearchReposParams) error { 1623 return p.execute("search/search", w, params) 1624} 1625 1626func (p *Pages) Home(w io.Writer, params TimelineParams) error { 1627 return p.execute("timeline/home", w, params) 1628} 1629 1630func (p *Pages) Static() http.Handler { 1631 if p.dev { 1632 return http.StripPrefix("/static/", http.FileServer(http.Dir("appview/pages/static"))) 1633 } 1634 1635 sub, err := fs.Sub(p.embedFS, "static") 1636 if err != nil { 1637 p.logger.Error("no static dir found? that's crazy", "err", err) 1638 panic(err) 1639 } 1640 // Custom handler to apply Cache-Control headers for font files 1641 return Cache(http.StripPrefix("/static/", http.FileServer(http.FS(sub)))) 1642} 1643 1644func Cache(h http.Handler) http.Handler { 1645 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1646 path := strings.Split(r.URL.Path, "?")[0] 1647 1648 if strings.HasSuffix(path, ".css") { 1649 // on day for css files 1650 w.Header().Set("Cache-Control", "public, max-age=86400") 1651 } else { 1652 w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") 1653 } 1654 h.ServeHTTP(w, r) 1655 }) 1656} 1657 1658func (p *Pages) CssContentHash() string { 1659 cssFile, err := p.embedFS.Open("static/tw.css") 1660 if err != nil { 1661 slog.Debug("Error opening CSS file", "err", err) 1662 return "" 1663 } 1664 defer cssFile.Close() 1665 1666 hasher := sha256.New() 1667 if _, err := io.Copy(hasher, cssFile); err != nil { 1668 slog.Debug("Error hashing CSS file", "err", err) 1669 return "" 1670 } 1671 1672 return hex.EncodeToString(hasher.Sum(nil))[:8] // Use first 8 chars of hash 1673} 1674 1675func (p *Pages) DangerPasswordTokenStep(w io.Writer) error { 1676 return p.executePlain("user/settings/fragments/dangerPasswordToken", w, nil) 1677} 1678 1679func (p *Pages) DangerPasswordSuccess(w io.Writer) error { 1680 return p.executePlain("user/settings/fragments/dangerPasswordSuccess", w, nil) 1681} 1682 1683func (p *Pages) DangerDeleteTokenStep(w io.Writer) error { 1684 return p.executePlain("user/settings/fragments/dangerDeleteToken", w, nil) 1685} 1686 1687func (p *Pages) Error500(w io.Writer) error { 1688 return p.execute("errors/500", w, nil) 1689} 1690 1691func (p *Pages) Error404(w io.Writer) error { 1692 return p.execute("errors/404", w, nil) 1693} 1694 1695func (p *Pages) ErrorKnot404(w io.Writer) error { 1696 return p.execute("errors/knot404", w, nil) 1697} 1698 1699func (p *Pages) Error503(w io.Writer) error { 1700 return p.execute("errors/503", w, nil) 1701}