Monorepo for Tangled tangled.org
6

Configure Feed

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

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