This repository has no description
0

Configure Feed

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

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