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