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