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