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