Monorepo for Tangled
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028package pages
import ( "context" "crypto/sha256" "embed" "encoding/hex" "fmt" "html/template" "io" "io/fs" "log/slog" "net/http" "os" "path/filepath" "strings" "sync" "time"
"tangled.org/core/api/tangled" "tangled.org/core/appview/cache" "tangled.org/core/appview/commitverify" "tangled.org/core/appview/config" "tangled.org/core/appview/db" "tangled.org/core/appview/models" "tangled.org/core/appview/oauth" "tangled.org/core/appview/pages/markup" "tangled.org/core/appview/pages/markup/sanitizer" "tangled.org/core/appview/pages/repoinfo" "tangled.org/core/appview/pagination" "tangled.org/core/idresolver" "tangled.org/core/types"
"github.com/bluesky-social/indigo/atproto/identity" "github.com/bluesky-social/indigo/atproto/syntax" "github.com/go-git/go-git/v5/plumbing" "github.com/sourcegraph/zoekt")
//go:embed templates/* static legalvar Files embed.FS
type baseParamsCtxKey struct{}
type BaseParams struct { LoggedInUser *oauth.MultiAccountUser FocusParams FocusParams}
type FocusParams struct { Focusing bool FocusLink string FocusNotificationID int64 CurrentPath string // r.URL.Path, for off-focus detection in templates FocusCount int // total unread focus-eligible items remaining}
func (p *Pages) Resolver() *idresolver.Resolver { return p.resolver}
func BaseParamsIntoContext(ctx context.Context, bp BaseParams) context.Context { return context.WithValue(ctx, baseParamsCtxKey{}, bp)}
func BaseParamsFromContext(ctx context.Context) BaseParams { bp, _ := ctx.Value(baseParamsCtxKey{}).(BaseParams) return bp}
type Pages struct { mu sync.RWMutex cache *TmplCache[string, *template.Template]
avatar config.AvatarConfig pdsCfg config.PdsConfig resolver *idresolver.Resolver db *db.DB rdb *cache.Cache dev bool embedFS fs.FS templateDir string // Path to templates on disk for dev mode rctx *markup.RenderContext logger *slog.Logger}
func NewPages(config *config.Config, res *idresolver.Resolver, database *db.DB, rdb *cache.Cache, logger *slog.Logger) *Pages { // initialized with safe defaults, can be overridden per use rctx := &markup.RenderContext{ IsDev: config.Core.Dev, Hostname: config.Core.AppviewHost, CamoUrl: config.Camo.Host, CamoSecret: config.Camo.SharedSecret, Files: Files, }
p := &Pages{ mu: sync.RWMutex{}, cache: NewTmplCache[string, *template.Template](), dev: config.Core.Dev, avatar: config.Avatar, pdsCfg: config.Pds, rctx: rctx, resolver: res, db: database, rdb: rdb, templateDir: "appview/pages", logger: logger, }
if p.dev { p.embedFS = os.DirFS(p.templateDir) } else { p.embedFS = Files }
return p}
// reverse of pathToNamefunc (p *Pages) nameToPath(s string) string { return "templates/" + s + ".html"}
// FuncMap returns the template function map for use by external template consumers.func (p *Pages) FuncMap() template.FuncMap { return p.funcMap()}
// FragmentPaths returns all fragment template paths from the embedded FS.func (p *Pages) FragmentPaths() ([]string, error) { return p.fragmentPaths()}
// EmbedFS returns the embedded filesystem containing templates and static assets.func (p *Pages) EmbedFS() fs.FS { return p.embedFS}
func (p *Pages) fragmentPaths() ([]string, error) { var fragmentPaths []string err := fs.WalkDir(p.embedFS, "templates", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { return nil } if !strings.HasSuffix(path, ".html") { return nil } if !strings.Contains(path, "fragments/") { return nil } fragmentPaths = append(fragmentPaths, path) return nil }) if err != nil { return nil, err }
return fragmentPaths, nil}
// parse without memoizationfunc (p *Pages) rawParse(stack ...string) (*template.Template, error) { paths, err := p.fragmentPaths() if err != nil { return nil, err } for _, s := range stack { paths = append(paths, p.nameToPath(s)) }
funcs := p.funcMap() top := stack[len(stack)-1] parsed, err := template.New(top). Funcs(funcs). ParseFS(p.embedFS, paths...) if err != nil { return nil, err }
return parsed, nil}
func (p *Pages) parse(stack ...string) (*template.Template, error) { key := strings.Join(stack, "|")
// never cache in dev mode if cached, exists := p.cache.Get(key); !p.dev && exists { return cached, nil }
result, err := p.rawParse(stack...) if err != nil { return nil, err }
p.cache.Set(key, result) return result, nil}
func (p *Pages) parseBase(top string) (*template.Template, error) { stack := []string{ "layouts/base", top, } return p.parse(stack...)}
func (p *Pages) parseRepoBase(top string) (*template.Template, error) { stack := []string{ "layouts/base", "layouts/repobase", top, } return p.parse(stack...)}
func (p *Pages) parseProfileBase(top string) (*template.Template, error) { stack := []string{ "layouts/base", "layouts/profilebase", top, } return p.parse(stack...)}
func (p *Pages) parseLoginBase(top string) (*template.Template, error) { stack := []string{ "layouts/base", "layouts/loginbase", top, } return p.parse(stack...)}
func (p *Pages) executePlain(name string, w io.Writer, params any) error { tpl, err := p.parse(name) if err != nil { return err }
err = tpl.Execute(w, params) if err != nil { p.logger.Error("failed to execute template", "template", name, "err", err) } return err}
func (p *Pages) executeLogin(name string, w io.Writer, params any) error { tpl, err := p.parseLoginBase(name) if err != nil { return err }
err = tpl.ExecuteTemplate(w, "layouts/base", params) if err != nil { p.logger.Error("failed to execute login template", "template", name, "err", err) } return err}
func (p *Pages) execute(name string, w io.Writer, params any) error { tpl, err := p.parseBase(name) if err != nil { return err }
err = tpl.ExecuteTemplate(w, "layouts/base", params) if err != nil { p.logger.Error("failed to execute template", "template", name, "err", err) } return err}
func (p *Pages) executeRepo(name string, w io.Writer, params any) error { tpl, err := p.parseRepoBase(name) if err != nil { return err }
err = tpl.ExecuteTemplate(w, "layouts/base", params) if err != nil { p.logger.Error("failed to execute repo template", "template", name, "err", err) } return err}
func (p *Pages) executeProfile(name string, w io.Writer, params any) error { tpl, err := p.parseProfileBase(name) if err != nil { return err }
err = tpl.ExecuteTemplate(w, "layouts/base", params) if err != nil { p.logger.Error("failed to execute profile template", "template", name, "err", err) } return err}
type DollyParams struct { Classes string FillColor string // Favicon embeds a prefers-color-scheme style block so the SVG // adapts to dark mode when used as a standalone favicon document. Favicon bool}
func (p *Pages) Dolly(w io.Writer, params DollyParams) error { return p.executePlain("fragments/dolly/logo", w, params)}
func (p *Pages) Favicon(w io.Writer) error { return p.Dolly(w, DollyParams{ Favicon: true, })}
type LoginParams struct { ReturnUrl string ErrorCode string AddAccount bool Accounts []oauth.AccountInfo}
func (p *Pages) Login(w io.Writer, params LoginParams) error { return p.executeLogin("user/login", w, params)}
type SignupParams struct { CloudflareSiteKey string EmailId string}
func (p *Pages) Signup(w io.Writer, params SignupParams) error { return p.executeLogin("user/signup", w, params)}
func (p *Pages) CompleteSignup(w io.Writer) error { return p.executeLogin("user/completeSignup", w, nil)}
type TermsOfServiceParams struct { BaseParams Content template.HTML}
func (p *Pages) TermsOfService(w io.Writer, params TermsOfServiceParams) error { filename := "terms.md" filePath := filepath.Join("legal", filename)
file, err := p.embedFS.Open(filePath) if err != nil { return fmt.Errorf("failed to read %s: %w", filename, err) } defer file.Close()
markdownBytes, err := io.ReadAll(file) if err != nil { return fmt.Errorf("failed to read %s: %w", filename, err) }
rctx := p.rctx.Clone() rctx.RendererType = markup.RendererTypeDefault htmlString := rctx.RenderMarkdown(string(markdownBytes)) sanitized := sanitizer.SanitizeDefault(htmlString) params.Content = template.HTML(sanitized)
return p.execute("legal/terms", w, params)}
type PrivacyPolicyParams struct { BaseParams Content template.HTML}
func (p *Pages) PrivacyPolicy(w io.Writer, params PrivacyPolicyParams) error { filename := "privacy.md" filePath := filepath.Join("legal", filename)
file, err := p.embedFS.Open(filePath) if err != nil { return fmt.Errorf("failed to read %s: %w", filename, err) } defer file.Close()
markdownBytes, err := io.ReadAll(file) if err != nil { return fmt.Errorf("failed to read %s: %w", filename, err) }
rctx := p.rctx.Clone() rctx.RendererType = markup.RendererTypeDefault htmlString := rctx.RenderMarkdown(string(markdownBytes)) sanitized := sanitizer.SanitizeDefault(htmlString) params.Content = template.HTML(sanitized)
return p.execute("legal/privacy", w, params)}
type BrandParams struct { BaseParams}
func (p *Pages) Brand(w io.Writer, params BrandParams) error { return p.execute("brand/brand", w, params)}
type RecentItem struct { Link *models.RecentLink Repo *models.Repo Issue *models.Issue Pull *models.Pull}
type BlogPost struct { Slug string Title string Subtitle string Date time.Time}
type TimelineParams struct { BaseParams Timeline []models.TimelineGroup Repos []models.Repo GfiLabel *models.LabelDefinition BlueskyPosts []models.BskyPost VouchSuggestions []models.VouchSuggestion Notifications []*models.NotificationWithEntity Recents []RecentItem FollowingOnly bool RecentBlogPosts []BlogPost // ShowNewsletter controls whether the newsletter widget/CTA is rendered. // For logged-in users it reflects their newsletter_preferences row; for // anonymous visitors it is always true (dismissal falls back to // localStorage on the client). ShowNewsletter bool CanFocus bool}
func (p *Pages) Timeline(w io.Writer, params TimelineParams) error { return p.execute("timeline/timeline", w, params)}
type GoodFirstIssuesParams struct { BaseParams Issues []models.Issue RepoGroups []*models.RepoGroup LabelDefs map[string]*models.LabelDefinition GfiLabel *models.LabelDefinition Page pagination.Page}
func (p *Pages) GoodFirstIssues(w io.Writer, params GoodFirstIssuesParams) error { return p.execute("goodfirstissues/index", w, params)}
type UserProfileSettingsParams struct { BaseParams Tab string PunchcardPreference models.PunchcardPreference IsTnglSh bool IsDeactivated bool HandleOpen bool}
func (p *Pages) UserProfileSettings(w io.Writer, params UserProfileSettingsParams) error { params.Tab = "profile" return p.execute("user/settings/profile", w, params)}
type GroupedNotifications struct { Today []*models.NotificationWithEntity ThisWeek []*models.NotificationWithEntity Older []*models.NotificationWithEntity}
func GroupNotificationsByDate(notifs []*models.NotificationWithEntity) GroupedNotifications { now := time.Now() todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) weekStart := todayStart.AddDate(0, 0, -6)
var g GroupedNotifications for _, n := range notifs { switch { case !n.Created.Before(todayStart): g.Today = append(g.Today, n) case !n.Created.Before(weekStart): g.ThisWeek = append(g.ThisWeek, n) default: g.Older = append(g.Older, n) } } return g}
type NotificationsParams struct { BaseParams WorkGroups GroupedNotifications SocialGroups GroupedNotifications MobileGroups GroupedNotifications WorkUnreadCount int64 SocialUnreadCount int64 Page pagination.Page Total int ReadFilter string // "inbox" or "unread" CategoryFilter string // "all", "work", "social" CanFocus bool}
func (p *Pages) Notifications(w io.Writer, params NotificationsParams) error { return p.execute("notifications/list", w, params)}
func (p *Pages) NotificationItem(w io.Writer, notif *models.NotificationWithEntity) error { return p.executePlain("notifications/fragments/item", w, notif)}
type NotificationCountParams struct { Count int64}
func (p *Pages) NotificationCount(w io.Writer, params NotificationCountParams) error { return p.executePlain("notifications/fragments/count", w, params)}
type NotificationPreviewParams struct { BaseParams Notifications []*models.NotificationWithEntity ReadFilter string CategoryFilter string CanFocus bool}
func (p *Pages) NotificationPreview(w io.Writer, params NotificationPreviewParams) error { return p.executePlain("notifications/fragments/preview", w, params)}
type UserKeysSettingsParams struct { BaseParams PubKeys []models.PublicKey Tab string}
func (p *Pages) UserKeysSettings(w io.Writer, params UserKeysSettingsParams) error { params.Tab = "keys" return p.execute("user/settings/keys", w, params)}
type UserEmailsSettingsParams struct { BaseParams Emails []models.Email Tab string}
func (p *Pages) UserEmailsSettings(w io.Writer, params UserEmailsSettingsParams) error { params.Tab = "emails" return p.execute("user/settings/emails", w, params)}
type UserNotificationSettingsParams struct { BaseParams Preferences *models.NotificationPreferences Tab string}
func (p *Pages) UserNotificationSettings(w io.Writer, params UserNotificationSettingsParams) error { params.Tab = "notifications" return p.execute("user/settings/notifications", w, params)}
type UserSiteSettingsParams struct { BaseParams Claim *models.DomainClaim SitesDomain string IsTnglHandle bool Tab string}
func (p *Pages) UserSiteSettings(w io.Writer, params UserSiteSettingsParams) error { params.Tab = "sites" return p.execute("user/settings/sites", w, params)}
type UpgradeBannerParams struct { Registrations []models.Registration Spindles []models.Spindle}
func (p *Pages) UpgradeBanner(w io.Writer, params UpgradeBannerParams) error { return p.executePlain("banner", w, params)}
type NewsletterResponseParams struct { // Id identifies the calling form instance; the response span's id will // be "newsletter-msg-<Id>" so it round-trips with the form's hx-target. Id string // Error, when non-empty, switches the template to the error variant. Error string}
func (p *Pages) NewsletterResponse(w io.Writer, params NewsletterResponseParams) error { return p.executePlain("timeline/fragments/newsletterResponse", w, params)}
type KnotsParams struct { BaseParams Knots []KnotListingParams Tab string}
func (p *Pages) Knots(w io.Writer, params KnotsParams) error { params.Tab = "knots" return p.execute("knots/index", w, params)}
type KnotParams struct { BaseParams Registration *models.Registration Members []string Repos map[string][]models.Repo IsOwner bool RepoCount int Tab string}
func (p *Pages) Knot(w io.Writer, params KnotParams) error { return p.execute("knots/dashboard", w, params)}
type KnotListingParams struct { *models.Registration RepoCount int}
func (p *Pages) KnotListing(w io.Writer, params KnotListingParams) error { return p.executePlain("knots/fragments/knotListing", w, params)}
type SpindlesParams struct { BaseParams Spindles []models.Spindle Tab string}
func (p *Pages) Spindles(w io.Writer, params SpindlesParams) error { params.Tab = "spindles" return p.execute("spindles/index", w, params)}
type SpindleListingParams struct { models.Spindle Tab string}
func (p *Pages) SpindleListing(w io.Writer, params SpindleListingParams) error { return p.executePlain("spindles/fragments/spindleListing", w, params)}
type SpindleDashboardParams struct { BaseParams Spindle models.Spindle Members []string Repos map[string][]models.Repo Tab string}
func (p *Pages) SpindleDashboard(w io.Writer, params SpindleDashboardParams) error { return p.execute("spindles/dashboard", w, params)}
type NewRepoParams struct { BaseParams Knots []string}
func (p *Pages) NewRepo(w io.Writer, params NewRepoParams) error { return p.execute("repo/new", w, params)}
type ForkRepoParams struct { BaseParams Knots []string RepoInfo repoinfo.RepoInfo}
func (p *Pages) ForkRepo(w io.Writer, params ForkRepoParams) error { return p.execute("repo/fork", w, params)}
type ProfileCard struct { UserDid string HasProfile bool FollowStatus models.FollowStatus VouchRelationship *models.VouchRelationship Punchcard *models.Punchcard Profile *models.Profile Stats ProfileStats Active string ProfileScript string}
type ProfileStats struct { RepoCount int64 StarredCount int64 StringCount int64 FollowersCount int64 FollowingCount int64}
func (p *ProfileCard) GetTabs() [][]any { tabs := [][]any{ {"overview", "overview", "square-chart-gantt", nil}, {"repos", "repos", "book-marked", p.Stats.RepoCount}, {"starred", "starred", "star", p.Stats.StarredCount}, {"strings", "strings", "line-squiggle", p.Stats.StringCount}, {"vouches", "vouches", "shield", nil}, }
return tabs}
type ProfileOverviewParams struct { BaseParams Repos []models.Repo CollaboratingRepos []models.Repo ProfileTimeline *models.ProfileTimeline Card *ProfileCard Active string ShowPunchcard bool}
func (p *Pages) ProfileOverview(w io.Writer, params ProfileOverviewParams) error { params.Active = "overview" return p.executeProfile("user/overview", w, params)}
type ProfileReposParams struct { BaseParams Repos []models.Repo StarStatuses map[string]bool Card *ProfileCard Active string Page pagination.Page RepoCount int FilterQuery string}
func (p *Pages) ProfileRepos(w io.Writer, params ProfileReposParams) error { params.Active = "repos" return p.executeProfile("user/repos", w, params)}
type ProfileStarredParams struct { BaseParams Repos []models.Repo Card *ProfileCard Page pagination.Page Total int Active string}
func (p *Pages) ProfileStarred(w io.Writer, params ProfileStarredParams) error { params.Active = "starred" return p.executeProfile("user/starred", w, params)}
type ProfileStringsParams struct { BaseParams Strings []models.String Card *ProfileCard Active string}
func (p *Pages) ProfileStrings(w io.Writer, params ProfileStringsParams) error { params.Active = "strings" return p.executeProfile("user/strings", w, params)}
type ProfileVouchesParams struct { BaseParams Vouches []models.Vouch Suggestions []models.VouchSuggestion Card *ProfileCard Page pagination.Page VouchCount int Active string EvidencePulls map[syntax.ATURI]*models.Pull EvidenceIssues map[syntax.ATURI]*models.Issue}
func (p *Pages) ProfileVouches(w io.Writer, params ProfileVouchesParams) error { params.Active = "vouches" return p.executeProfile("user/vouches", w, params)}
type FollowCard struct { UserDid string BaseParams FollowStatus models.FollowStatus FollowersCount int64 FollowingCount int64 Profile *models.Profile}
type ProfileFollowersParams struct { BaseParams Followers []FollowCard Card *ProfileCard Active string}
func (p *Pages) ProfileFollowers(w io.Writer, params ProfileFollowersParams) error { params.Active = "overview" return p.executeProfile("user/followers", w, params)}
type ProfileFollowingParams struct { BaseParams Following []FollowCard Card *ProfileCard Active string}
func (p *Pages) ProfileFollowing(w io.Writer, params ProfileFollowingParams) error { params.Active = "overview" return p.executeProfile("user/following", w, params)}
type FollowFragmentParams struct { UserDid string FollowStatus models.FollowStatus FollowersCount int64}
func (p *Pages) FollowFragment(w io.Writer, params FollowFragmentParams) error { return p.executePlain("user/fragments/follow-oob", w, params)}
type ProfilePopoverParams struct { BaseParams UserDid string Profile *models.Profile FollowStatus models.FollowStatus VouchRelationship *models.VouchRelationship Stats ProfilePopoverStats}
type ProfilePopoverStats struct { FollowersCount int64 FollowingCount int64}
func (p *Pages) ProfilePopoverFragment(w io.Writer, params ProfilePopoverParams) error { return p.executePlain("user/fragments/profilePopover", w, params)}
type EditBioParams struct { BaseParams Profile *models.Profile AlsoKnownAs []string}
func (p *Pages) EditBioFragment(w io.Writer, params EditBioParams) error { return p.executePlain("user/fragments/editBio", w, params)}
type EditPinsParams struct { BaseParams Profile *models.Profile AllRepos []PinnedRepo}
type PinnedRepo struct { IsPinned bool models.Repo}
func (p *Pages) EditPinsFragment(w io.Writer, params EditPinsParams) error { return p.executePlain("user/fragments/editPins", w, params)}
type StarBtnFragmentParams struct { IsStarred bool SubjectAt syntax.ATURI StarCount int RepoName string HxSwapOob bool}
func (p *Pages) StarBtnFragment(w io.Writer, params StarBtnFragmentParams) error { params.HxSwapOob = true return p.executePlain("fragments/starBtn", w, params)}
type RepoIndexParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string TagMap map[string][]string CommitsTrunc []types.Commit TagsTrunc []*types.TagReference BranchesTrunc []types.Branch // ForkInfo *types.ForkInfo HTMLReadme template.HTML Raw bool EmailToDid map[string]string VerifiedCommits commitverify.VerifiedCommits Languages []types.RepoLanguageDetails Pipelines map[string]types.Pipeline NeedsKnotUpgrade bool KnotUnreachable bool types.RepoIndexResponse}
func (p *Pages) RepoIndexPage(w io.Writer, params RepoIndexParams) error { params.Active = "overview" if params.IsEmpty { return p.executeRepo("repo/empty", w, params) }
if params.NeedsKnotUpgrade { return p.executeRepo("repo/needsUpgrade", w, params) }
if params.KnotUnreachable { return p.executeRepo("repo/knotUnreachable", w, params) }
rctx := p.rctx.Clone() rctx.RepoInfo = params.RepoInfo rctx.RepoInfo.Ref = params.Ref rctx.RendererType = markup.RendererTypeRepoMarkdown
if params.ReadmeFileName != "" { switch markup.GetFormat(params.ReadmeFileName) { case markup.FormatMarkdown: params.Raw = false htmlString := rctx.RenderMarkdown(params.Readme) sanitized := sanitizer.SanitizeDefault(htmlString) params.HTMLReadme = template.HTML(sanitized) default: params.Raw = true } }
return p.executeRepo("repo/index", w, params)}
type RepoSearchParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string FilterQuery string}
func (p *Pages) RepoSearchPage(w io.Writer, params RepoSearchParams) error { params.Active = "overview" return p.executeRepo("repo/search", w, params)}
type RepoSearchResultsFragmentParams struct { Query string Results []SearchResult ErrorMsg string}
func (p *Pages) RepoSearchResultsFragment(w io.Writer, params RepoSearchResultsFragmentParams) error { return p.executePlain("repo/fragments/searchResults", w, params)}
type RepoLogParams struct { BaseParams RepoInfo repoinfo.RepoInfo TagMap map[string][]string Active string EmailToDid map[string]string VerifiedCommits commitverify.VerifiedCommits Pipelines map[string]types.Pipeline
types.RepoLogResponse}
func (p *Pages) RepoLog(w io.Writer, params RepoLogParams) error { params.Active = "overview" return p.executeRepo("repo/log", w, params)}
type RepoCommitParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string EmailToDid map[string]string Pipeline *types.Pipeline DiffOpts types.DiffOpts
// singular because it's always going to be just one VerifiedCommit commitverify.VerifiedCommits
types.RepoCommitResponse}
func (p *Pages) RepoCommit(w io.Writer, params RepoCommitParams) error { params.Active = "overview" return p.executeRepo("repo/commit", w, params)}
type RepoTreeParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string BreadCrumbs [][]string Path string Raw bool HTMLReadme template.HTML EmailToDid map[string]string LastCommitInfo *types.LastCommitInfo Ref string Parent string DotDot string Files []types.NiceTree ReadmeFileName string Readme string}
type RepoTreeStats struct { NumFolders uint64 NumFiles uint64}
func (r RepoTreeParams) TreeStats() RepoTreeStats { numFolders, numFiles := 0, 0 for _, f := range r.Files { if !f.IsFile() { numFolders += 1 } else if f.IsFile() { numFiles += 1 } }
return RepoTreeStats{ NumFolders: uint64(numFolders), NumFiles: uint64(numFiles), }}
func (p *Pages) RepoTree(w io.Writer, params RepoTreeParams) error { params.Active = "overview"
rctx := p.rctx.Clone() rctx.RepoInfo = params.RepoInfo rctx.RepoInfo.Ref = params.Ref rctx.RendererType = markup.RendererTypeRepoMarkdown
if params.ReadmeFileName != "" { switch markup.GetFormat(params.ReadmeFileName) { case markup.FormatMarkdown: params.Raw = false htmlString := rctx.RenderMarkdown(params.Readme) sanitized := sanitizer.SanitizeDefault(htmlString) params.HTMLReadme = template.HTML(sanitized) default: params.Raw = true } }
return p.executeRepo("repo/tree", w, params)}
type RepoBranchesParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string types.RepoBranchesResponse}
func (p *Pages) RepoBranches(w io.Writer, params RepoBranchesParams) error { params.Active = "overview" return p.executeRepo("repo/branches", w, params)}
type RepoTagsParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string types.RepoTagsResponse ArtifactMap map[plumbing.Hash][]models.Artifact DanglingArtifacts []models.Artifact}
func (p *Pages) RepoTags(w io.Writer, params RepoTagsParams) error { params.Active = "overview" return p.executeRepo("repo/tags", w, params)}
type RepoTagParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string types.RepoTagResponse ArtifactMap map[plumbing.Hash][]models.Artifact DanglingArtifacts []models.Artifact}
func (p *Pages) RepoTag(w io.Writer, params RepoTagParams) error { params.Active = "overview" return p.executeRepo("repo/tag", w, params)}
type RepoArtifactParams struct { BaseParams RepoInfo repoinfo.RepoInfo Artifact models.Artifact}
func (p *Pages) RepoArtifactFragment(w io.Writer, params RepoArtifactParams) error { return p.executePlain("repo/fragments/artifact", w, params)}
type RepoBlobParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string // always "overview" BreadCrumbs [][]string BlobView models.BlobView // TODO: expose this struct ShowRendered bool EmailToDid map[string]string LastCommitInfo *types.LastCommitInfo Ref string Path string Language string}
func (p *Pages) RepoBlob(w io.Writer, params RepoBlobParams) error { params.Active = "overview" return p.executeRepo("repo/blob", w, params)}
type Collaborator struct { Did string Role string}
type RepoSettingsParams struct { BaseParams RepoInfo repoinfo.RepoInfo Collaborators []Collaborator Active string Branches []types.Branch Spindles []string CurrentSpindle string Secrets []*tangled.RepoListSecrets_Secret
// TODO: use repoinfo.roles IsCollaboratorInviteAllowed bool}
func (p *Pages) RepoSettings(w io.Writer, params RepoSettingsParams) error { params.Active = "settings" return p.executeRepo("repo/settings", w, params)}
type RepoGeneralSettingsParams struct { BaseParams RepoInfo repoinfo.RepoInfo Labels []models.LabelDefinition DefaultLabels []models.LabelDefinition SubscribedLabels map[string]struct{} ShouldSubscribeAll bool Active string Tab string Branches []types.Branch}
func (p *Pages) RepoGeneralSettings(w io.Writer, params RepoGeneralSettingsParams) error { params.Active = "settings" params.Tab = "general" return p.executeRepo("repo/settings/general", w, params)}
type RepoAccessSettingsParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string Tab string Collaborators []Collaborator CanRemoveCollaborator bool}
func (p *Pages) RepoAccessSettings(w io.Writer, params RepoAccessSettingsParams) error { params.Active = "settings" params.Tab = "access" return p.executeRepo("repo/settings/access", w, params)}
type RepoPipelineSettingsParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string Tab string Spindles []string CurrentSpindle string Secrets []map[string]any}
func (p *Pages) RepoPipelineSettings(w io.Writer, params RepoPipelineSettingsParams) error { params.Active = "settings" params.Tab = "pipelines" return p.executeRepo("repo/settings/pipelines", w, params)}
type RepoWebhooksSettingsParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string Tab string Webhooks []models.Webhook WebhookDeliveries map[int64][]models.WebhookDelivery}
func (p *Pages) RepoWebhooksSettings(w io.Writer, params RepoWebhooksSettingsParams) error { params.Active = "settings" params.Tab = "hooks" return p.executeRepo("repo/settings/hooks", w, params)}
type WebhookDeliveriesListParams struct { BaseParams RepoInfo repoinfo.RepoInfo Webhook *models.Webhook Deliveries []models.WebhookDelivery}
func (p *Pages) WebhookDeliveriesList(w io.Writer, params WebhookDeliveriesListParams) error { tpl, err := p.parse("repo/settings/fragments/webhookDeliveries") if err != nil { return err } return tpl.ExecuteTemplate(w, "repo/settings/fragments/webhookDeliveries", params)}
type RepoSiteSettingsParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string Tab string Branches []types.Branch SiteConfig *models.RepoSite OwnerClaim *models.DomainClaim Deploys []models.SiteDeploy IndexSiteTakenBy string // repo_at of another repo that already holds is_index, or ""}
func (p *Pages) RepoSiteSettings(w io.Writer, params RepoSiteSettingsParams) error { params.Active = "settings" params.Tab = "sites" return p.executeRepo("repo/settings/sites", w, params)}
type RepoIssuesParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string Issues []models.Issue IssueCount int LabelDefs map[string]*models.LabelDefinition Page pagination.Page FilterState string FilterQuery string BaseFilterQuery string VouchRelationships map[syntax.DID]*models.VouchRelationship}
func (p *Pages) RepoIssues(w io.Writer, params RepoIssuesParams) error { params.Active = "issues" return p.executeRepo("repo/issues/issues", w, params)}
type RepoSingleIssueParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string Issue *models.Issue CommentList []models.CommentListItem Backlinks []models.RichReferenceLink LabelDefs map[string]*models.LabelDefinition
Reactions map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData UserReacted map[syntax.ATURI]map[models.ReactionKind]bool VouchRelationships map[syntax.DID]*models.VouchRelationship}
func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error { params.Active = "issues" return p.executeRepo("repo/issues/issue", w, params)}
type EditIssueParams struct { BaseParams RepoInfo repoinfo.RepoInfo Issue *models.Issue Action string}
func (p *Pages) EditIssueFragment(w io.Writer, params EditIssueParams) error { params.Action = "edit" return p.executePlain("repo/issues/fragments/putIssue", w, params)}
type ThreadReactionFragmentParams struct { Kind models.ReactionKind Count int Users []string IsReacted bool CommentRkey string SubjectUri string}
func (p *Pages) ThreadReactionFragment(w io.Writer, params ThreadReactionFragmentParams) error { return p.executePlain("repo/fragments/reaction", w, params)}
type RepoNewIssueParams struct { BaseParams RepoInfo repoinfo.RepoInfo Issue *models.Issue // existing issue if any -- passed when editing Active string Action string}
func (p *Pages) RepoNewIssue(w io.Writer, params RepoNewIssueParams) error { params.Active = "issues" params.Action = "create" return p.executeRepo("repo/issues/new", w, params)}
type StackedDiff struct { Diff *types.NiceDiff Opts types.DiffOpts}
type RepoNewPullParams struct { BaseParams RepoInfo repoinfo.RepoInfo Branches []types.Branch SourceBranches []types.Branch ForkBranches []types.Branch Forks []models.Repo Source Source SourceBranch string TargetBranch string Fork string Patch string Title string Body string TitleDirty bool BodyDirty bool IsStacked bool Comparison *types.RepoFormatPatchResponse Diff *types.NiceDiff DiffOpts types.DiffOpts StackedDiffs []StackedDiff MergeCheck *types.MergeCheckResponse StackTitles map[string]string StackBodies map[string]string PrefillError string Active string LabelDefs map[string]*models.LabelDefinition LabelState models.LabelState StackLabelStates map[string]models.LabelState}
func (p *Pages) RepoNewPull(w io.Writer, params RepoNewPullParams) error { params.Active = "pulls" return p.executeRepo("repo/pulls/new", w, params)}
func (p *Pages) PullComposeHostFragment(w io.Writer, params RepoNewPullParams) error { return p.executePlain("repo/pulls/fragments/pullComposeHost", w, params)}
func (p *Pages) MarkdownPreviewFragment(w io.Writer, body string) error { return p.executePlain("fragments/markdownPreview", w, body)}
type RepoPullsParams struct { BaseParams RepoInfo repoinfo.RepoInfo Pulls []*models.Pull Active string FilterState string FilterQuery string BaseFilterQuery string Stacks []models.Stack Pipelines map[string]types.Pipeline LabelDefs map[string]*models.LabelDefinition Page pagination.Page PullCount int VouchRelationships map[syntax.DID]*models.VouchRelationship}
func (p *Pages) RepoPulls(w io.Writer, params RepoPullsParams) error { params.Active = "pulls" return p.executeRepo("repo/pulls/pulls", w, params)}
type ResubmitResult uint64
const ( ShouldResubmit ResubmitResult = iota ShouldNotResubmit Unknown)
func (r ResubmitResult) Yes() bool { return r == ShouldResubmit}func (r ResubmitResult) No() bool { return r == ShouldNotResubmit}func (r ResubmitResult) Unknown() bool { return r == Unknown}
type RepoSinglePullParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string Pull *models.Pull Stack models.Stack Backlinks []models.RichReferenceLink BranchDeleteStatus *models.BranchDeleteStatus MergeCheck types.MergeCheckResponse ResubmitCheck ResubmitResult Pipelines map[string]types.Pipeline Diff types.DiffRenderer DiffOpts types.DiffOpts ActiveRound int IsInterdiff bool
// WorkflowsChanged and ChangedWorkflowFiles describe whether the latest // round's patch touches .tangled/workflows/, for warning maintainers // before they manually trigger CI on a fork-based pull request. WorkflowsChanged bool ChangedWorkflowFiles []string
Reactions map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData UserReacted map[syntax.ATURI]map[models.ReactionKind]bool
LabelDefs map[string]*models.LabelDefinition VouchRelationships map[syntax.DID]*models.VouchRelationship VouchSkips map[syntax.DID]bool}
func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error { params.Active = "pulls" return p.executeRepo("repo/pulls/pull", w, params)}
type PullResubmitParams struct { BaseParams RepoInfo repoinfo.RepoInfo Pull *models.Pull SubmissionId int}
func (p *Pages) PullResubmitFragment(w io.Writer, params PullResubmitParams) error { return p.executePlain("repo/pulls/fragments/pullResubmit", w, params)}
type PullActionsParams struct { BaseParams RepoInfo repoinfo.RepoInfo Pull *models.Pull RoundNumber int MergeCheck types.MergeCheckResponse ResubmitCheck ResubmitResult BranchDeleteStatus *models.BranchDeleteStatus Stack models.Stack
// renders buttons in a pre-check state and attaches the hx-trigger="load" // that fetches the real, checked fragment Loading bool}
func (p *Pages) PullActionsFragment(w io.Writer, params PullActionsParams) error { return p.executePlain("repo/pulls/fragments/pullActions", w, params)}
type PullNewCommentParams struct { BaseParams RepoInfo repoinfo.RepoInfo Pull *models.Pull RoundNumber int}
func (p *Pages) PullNewCommentFragment(w io.Writer, params PullNewCommentParams) error { return p.executePlain("repo/pulls/fragments/pullNewComment", w, params)}
type RepoCompareParams struct { BaseParams RepoInfo repoinfo.RepoInfo Forks []models.Repo Branches []types.Branch Tags []*types.TagReference Base string Head string Diff *types.NiceDiff DiffOpts types.DiffOpts
Active string}
func (p *Pages) RepoCompare(w io.Writer, params RepoCompareParams) error { params.Active = "overview" return p.executeRepo("repo/compare/compare", w, params)}
type RepoCompareNewParams struct { BaseParams RepoInfo repoinfo.RepoInfo Forks []models.Repo Branches []types.Branch Tags []*types.TagReference Base string Head string
Active string}
func (p *Pages) RepoCompareNew(w io.Writer, params RepoCompareNewParams) error { params.Active = "overview" return p.executeRepo("repo/compare/new", w, params)}
type RepoCompareAllowPullParams struct { BaseParams RepoInfo repoinfo.RepoInfo Base string Head string}
func (p *Pages) RepoCompareAllowPullFragment(w io.Writer, params RepoCompareAllowPullParams) error { return p.executePlain("repo/fragments/compareAllowPull", w, params)}
type RepoCompareDiffFragmentParams struct { Diff types.NiceDiff DiffOpts types.DiffOpts}
func (p *Pages) RepoCompareDiffFragment(w io.Writer, params RepoCompareDiffFragmentParams) error { return p.executePlain("repo/fragments/diff", w, []any{¶ms.Diff, ¶ms.DiffOpts})}
type LabelPanelParams struct { BaseParams RepoInfo repoinfo.RepoInfo Defs map[string]*models.LabelDefinition Subject string State models.LabelState}
func (p *Pages) LabelPanel(w io.Writer, params LabelPanelParams) error { return p.executePlain("repo/fragments/labelPanel", w, params)}
type EditLabelPanelParams struct { BaseParams RepoInfo repoinfo.RepoInfo Defs map[string]*models.LabelDefinition Subject string State models.LabelState Prefix string}
func (p *Pages) EditLabelPanel(w io.Writer, params EditLabelPanelParams) error { return p.executePlain("repo/fragments/editLabelPanel", w, params)}
type RepoStarsParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string Starrers []models.Star Page pagination.Page TotalCount int}
func (p *Pages) RepoStars(w io.Writer, params RepoStarsParams) error { params.Active = "overview" return p.executeRepo("repo/stars", w, params)}
type RepoForksParams struct { BaseParams RepoInfo repoinfo.RepoInfo Active string Forks []models.Repo Page pagination.Page TotalCount int}
func (p *Pages) RepoForks(w io.Writer, params RepoForksParams) error { params.Active = "overview" return p.executeRepo("repo/forks", w, params)}
type PipelinesParams struct { BaseParams RepoInfo repoinfo.RepoInfo Pipelines []types.Pipeline Active string FilterKind string Total int64}
func (p *Pages) Pipelines(w io.Writer, params PipelinesParams) error { params.Active = "pipelines" return p.executeRepo("repo/pipelines/pipelines", w, params)}
type LogBlockParams struct { Id int Name string Command string Collapsed bool StartTime time.Time}
func (p *Pages) LogBlock(w io.Writer, params LogBlockParams) error { return p.executePlain("repo/pipelines/fragments/logBlock", w, params)}
type LogBlockEndParams struct { Id int StartTime time.Time EndTime time.Time}
func (p *Pages) LogBlockEnd(w io.Writer, params LogBlockEndParams) error { return p.executePlain("repo/pipelines/fragments/logBlockEnd", w, params)}
type LogLineParams struct { Id int Content template.HTML}
func (p *Pages) LogLine(w io.Writer, params LogLineParams) error { return p.executePlain("repo/pipelines/fragments/logLine", w, params)}
type WorkflowSymbolOOBParams struct { Name string Statuses models.WorkflowStatus}
func (p *Pages) WorkflowSymbolOOB(w io.Writer, params WorkflowSymbolOOBParams) error { return p.executePlain("repo/pipelines/fragments/workflowSymbolOOB", w, params)}
type WorkflowParams struct { BaseParams RepoInfo repoinfo.RepoInfo Pipeline types.Pipeline Workflow string LogUrl string Active string}
func (p *Pages) Workflow(w io.Writer, params WorkflowParams) error { params.Active = "pipelines" return p.executeRepo("repo/pipelines/workflow", w, params)}
type PutStringParams struct { BaseParams Action string
// this is supplied in the case of editing an existing string String models.String}
func (p *Pages) PutString(w io.Writer, params PutStringParams) error { return p.execute("strings/put", w, params)}
type StringsDashboardParams struct { BaseParams Card ProfileCard Strings []models.String}
func (p *Pages) StringsDashboard(w io.Writer, params StringsDashboardParams) error { return p.execute("strings/dashboard", w, params)}
type StringTimelineParams struct { BaseParams Strings []models.String}
func (p *Pages) StringsTimeline(w io.Writer, params StringTimelineParams) error { return p.execute("strings/timeline", w, params)}
type SingleStringParams struct { BaseParams ShowRendered bool RenderToggle bool RenderedContents template.HTML String *models.String Stats models.StringStats IsStarred bool StarCount int Owner identity.Identity CommentList []models.CommentListItem
Reactions map[syntax.ATURI]map[models.ReactionKind]models.ReactionDisplayData UserReacted map[syntax.ATURI]map[models.ReactionKind]bool VouchRelationships map[syntax.DID]*models.VouchRelationship}
func (p *Pages) SingleString(w io.Writer, params SingleStringParams) error { return p.execute("strings/string", w, params)}
type SearchReposParams struct { BaseParams FilterType string // "repo" | "code" Repos []SearchResult Page pagination.Page ResultCount int FilterQuery string SortParam string TimeTaken time.Duration DocCount int64 ErrorMsg string}
func (p *Pages) SearchRepos(w io.Writer, params SearchReposParams) error { params.FilterType = "repo" return p.execute("search/search", w, params)}
type SearchQuickParams struct { Repos []models.Repo Query string Total int}
func (p *Pages) SearchQuick(w io.Writer, params SearchQuickParams) error { return p.executePlain("search/fragments/quick", w, params)}
func (p *Pages) SearchQuickMobile(w io.Writer, params SearchQuickParams) error { tpl, err := p.parse("search/fragments/quick") if err != nil { return err } return tpl.ExecuteTemplate(w, "search/fragments/quickMobile", params)}
type SearchResult struct { RepoDID syntax.DID Repo *models.Repo FilePath string Branches []string Commit string Language string
File *CodeSearchResult_File // filename match Chunks CodeSearchResult_Chunks // content matches}
// CodeSearchResult_Chunk is a content match with its lines pre-rendered.type CodeSearchResult_Chunk struct { Lines []ChunkLine // precomputed from Content/ContentStartLine/Ranges MatchCount int // number of match ranges in this chunk}
type CodeSearchResult_Chunks []CodeSearchResult_Chunk
func (cs CodeSearchResult_Chunks) MatchCount() int { count := 0 for _, c := range cs { count += c.MatchCount } return count}
type CodeSearchResult_File struct { NameSpans []ChunkSpan // precomputed from FilePath/Ranges}
type ChunkSpan struct { Text string Match bool}
type ChunkLine struct { Num int Spans []ChunkSpan Highlight bool}
// ChunkLines renders a chunk's Content into per-line ChunkLines, splitting each// line into matched/unmatched spans using ranges. startLine is the 1-based line// number of the first line.func ChunkLines(content string, startLine int, ranges []zoekt.Range) []ChunkLine { if startLine < 1 { startLine = 1 } // trim a single trailing newline so we don't emit a spurious empty line content = strings.TrimSuffix(content, "\n") lines := strings.Split(content, "\n") out := make([]ChunkLine, len(lines)) for i, text := range lines { num := startLine + i runes := []rune(text)
// collect matched rune intervals [c0,c1) for this line var intervals [][2]int for _, rg := range ranges { if num < int(rg.Start.LineNumber) || num > int(rg.End.LineNumber) { continue } c0, c1 := 0, len(runes) if num == int(rg.Start.LineNumber) { c0 = int(rg.Start.Column) - 1 } if num == int(rg.End.LineNumber) { c1 = int(rg.End.Column) - 1 } c0 = max(0, min(c0, len(runes))) c1 = max(0, min(c1, len(runes))) if c0 < c1 { intervals = append(intervals, [2]int{c0, c1}) } } intervals = mergeIntervals(intervals)
out[i] = ChunkLine{ Num: num, Spans: spanRunes(runes, intervals), Highlight: len(intervals) > 0, } } return out}
// FileNameSpans splits a filename into matched/unmatched spans using ranges.// Filename ranges live on line 1; columns are clamped to rune bounds.func FileNameSpans(name string, ranges []zoekt.Range) []ChunkSpan { runes := []rune(name) var intervals [][2]int for _, rg := range ranges { if rg.Start.LineNumber > 1 || rg.End.LineNumber < 1 { continue } c0 := max(0, min(int(rg.Start.Column)-1, len(runes))) c1 := max(0, min(int(rg.End.Column)-1, len(runes))) if c0 < c1 { intervals = append(intervals, [2]int{c0, c1}) } } return spanRunes(runes, mergeIntervals(intervals))}
type CodeSearchParams struct { BaseParams FilterType string // "code" FilterQuery string Results []SearchResult Page pagination.Page HasMore bool ErrorMsg string
MatchCount int FileCount int TimeTaken time.Duration}
func (p *Pages) CodeSearch(w io.Writer, params CodeSearchParams) error { params.FilterType = "code" return p.execute("search/search", w, params)}
func (p *Pages) Home(w io.Writer, params TimelineParams) error { return p.execute("timeline/home", w, params)}
type CommentBodyFragmentParams struct { Comment models.Comment Reactions map[models.ReactionKind]models.ReactionDisplayData UserReacted map[models.ReactionKind]bool}
func (p *Pages) CommentBodyFragment(w io.Writer, params CommentBodyFragmentParams) error { return p.executePlain("fragments/comment/commentBody", w, params)}
type PullCommentFragmentParams struct { LoggedInUser *oauth.MultiAccountUser Comment models.Comment Reactions map[models.ReactionKind]models.ReactionDisplayData UserReacted map[models.ReactionKind]bool HxSwapOob bool}
func (p *Pages) PullCommentFragment(w io.Writer, params PullCommentFragmentParams) error { return p.executePlain("fragments/comment/pullComment", w, params)}
type CommentHeaderFragmentParams struct { Comment models.Comment Reactions map[models.ReactionKind]models.ReactionDisplayData UserReacted map[models.ReactionKind]bool HxSwapOob bool}
func (p *Pages) CommentHeaderFragment(w io.Writer, params CommentHeaderFragmentParams) error { return p.executePlain("fragments/comment/commentHeader", w, params)}
type EditCommentFragmentParams struct { Comment models.Comment}
func (p *Pages) EditCommentFragment(w io.Writer, params EditCommentFragmentParams) error { return p.executePlain("fragments/comment/edit", w, params)}
type ReplyCommentFragmentParams struct { BaseParams}
func (p *Pages) ReplyCommentFragment(w io.Writer, params ReplyCommentFragmentParams) error { return p.executePlain("fragments/comment/reply", w, params)}
type ReplyPlaceholderFragmentParams struct { BaseParams}
func (p *Pages) ReplyPlaceholderFragment(w io.Writer, params ReplyPlaceholderFragmentParams) error { return p.executePlain("fragments/comment/replyPlaceholder", w, params)}
func (p *Pages) Static() http.Handler { if p.dev { return http.StripPrefix("/static/", http.FileServer(http.Dir("appview/pages/static"))) }
sub, err := fs.Sub(p.embedFS, "static") if err != nil { p.logger.Error("no static dir found? that's crazy", "err", err) panic(err) } // Custom handler to apply Cache-Control headers for font files return Cache(http.StripPrefix("/static/", http.FileServer(http.FS(sub))))}
func Cache(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { path := strings.Split(r.URL.Path, "?")[0]
if strings.HasSuffix(path, ".css") { // on day for css files w.Header().Set("Cache-Control", "public, max-age=86400") } else { w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") } h.ServeHTTP(w, r) })}
func (p *Pages) CssContentHash() string { cssFile, err := p.embedFS.Open("static/tw.css") if err != nil { slog.Debug("Error opening CSS file", "err", err) return "" } defer cssFile.Close()
hasher := sha256.New() if _, err := io.Copy(hasher, cssFile); err != nil { slog.Debug("Error hashing CSS file", "err", err) return "" }
return hex.EncodeToString(hasher.Sum(nil))[:8] // Use first 8 chars of hash}
func (p *Pages) DangerPasswordTokenStep(w io.Writer) error { return p.executePlain("user/settings/fragments/dangerPasswordToken", w, nil)}
func (p *Pages) DangerPasswordSuccess(w io.Writer) error { return p.executePlain("user/settings/fragments/dangerPasswordSuccess", w, nil)}
func (p *Pages) DangerDeleteTokenStep(w io.Writer) error { return p.executePlain("user/settings/fragments/dangerDeleteToken", w, nil)}
func (p *Pages) Error500(w io.Writer) error { return p.execute("errors/500", w, nil)}
func (p *Pages) Error404(w io.Writer) error { return p.execute("errors/404", w, nil)}
func (p *Pages) ErrorKnot404(w io.Writer) error { return p.execute("errors/knot404", w, nil)}
func (p *Pages) Error503(w io.Writer) error { return p.execute("errors/503", w, nil)}