Monorepo for Tangled
0
fork

Configure Feed

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

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