appview: remove all occurrences of DidHandleMap #457

merged
opened by oppi.li targeting master from push-wpkykovtqxnx
Changed files
+74 -283
appview
+7 -7
appview/db/reaction.go
···
const (
Like ReactionKind = "👍"
-
Unlike = "👎"
-
Laugh = "😆"
-
Celebration = "🎉"
-
Confused = "🫤"
-
Heart = "❤️"
-
Rocket = "🚀"
-
Eyes = "👀"
)
func (rk ReactionKind) String() string {
···
const (
Like ReactionKind = "👍"
+
Unlike ReactionKind = "👎"
+
Laugh ReactionKind = "😆"
+
Celebration ReactionKind = "🎉"
+
Confused ReactionKind = "🫤"
+
Heart ReactionKind = "❤️"
+
Rocket ReactionKind = "🚀"
+
Eyes ReactionKind = "👀"
)
func (rk ReactionKind) String() string {
-54
appview/issues/issues.go
···
log.Println("failed to resolve issue owner", err)
}
-
identsToResolve := make([]string, len(comments))
-
for i, comment := range comments {
-
identsToResolve[i] = comment.OwnerDid
-
}
-
resolvedIds := rp.idResolver.ResolveIdents(r.Context(), identsToResolve)
-
didHandleMap := make(map[string]string)
-
for _, identity := range resolvedIds {
-
if !identity.Handle.IsInvalidHandle() {
-
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
-
} else {
-
didHandleMap[identity.DID.String()] = identity.DID.String()
-
}
-
}
-
rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{
LoggedInUser: user,
RepoInfo: f.RepoInfo(user),
···
Comments: comments,
IssueOwnerHandle: issueOwnerIdent.Handle.String(),
-
DidHandleMap: didHandleMap,
OrderedReactionKinds: db.OrderedReactionKinds,
Reactions: reactionCountMap,
···
return
}
-
identity, err := rp.idResolver.ResolveIdent(r.Context(), comment.OwnerDid)
-
if err != nil {
-
log.Println("failed to resolve did")
-
return
-
}
-
-
didHandleMap := make(map[string]string)
-
if !identity.Handle.IsInvalidHandle() {
-
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
-
} else {
-
didHandleMap[identity.DID.String()] = identity.DID.String()
-
}
-
rp.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
LoggedInUser: user,
RepoInfo: f.RepoInfo(user),
-
DidHandleMap: didHandleMap,
Issue: issue,
Comment: comment,
})
···
}
// optimistic update for htmx
-
didHandleMap := map[string]string{
-
user.Did: user.Handle,
-
}
comment.Body = newBody
comment.Edited = &edited
···
rp.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
LoggedInUser: user,
RepoInfo: f.RepoInfo(user),
-
DidHandleMap: didHandleMap,
Issue: issue,
Comment: comment,
})
···
}
// optimistic update for htmx
-
didHandleMap := map[string]string{
-
user.Did: user.Handle,
-
}
comment.Body = ""
comment.Deleted = &deleted
···
rp.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
LoggedInUser: user,
RepoInfo: f.RepoInfo(user),
-
DidHandleMap: didHandleMap,
Issue: issue,
Comment: comment,
})
-
return
}
func (rp *Issues) RepoIssues(w http.ResponseWriter, r *http.Request) {
···
return
}
-
identsToResolve := make([]string, len(issues))
-
for i, issue := range issues {
-
identsToResolve[i] = issue.OwnerDid
-
}
-
resolvedIds := rp.idResolver.ResolveIdents(r.Context(), identsToResolve)
-
didHandleMap := make(map[string]string)
-
for _, identity := range resolvedIds {
-
if !identity.Handle.IsInvalidHandle() {
-
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
-
} else {
-
didHandleMap[identity.DID.String()] = identity.DID.String()
-
}
-
}
-
rp.pages.RepoIssues(w, pages.RepoIssuesParams{
LoggedInUser: rp.oauth.GetUser(r),
RepoInfo: f.RepoInfo(user),
Issues: issues,
-
DidHandleMap: didHandleMap,
FilteringByOpen: isOpen,
Page: page,
})
-
return
}
func (rp *Issues) NewIssue(w http.ResponseWriter, r *http.Request) {
···
log.Println("failed to resolve issue owner", err)
}
rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{
LoggedInUser: user,
RepoInfo: f.RepoInfo(user),
···
Comments: comments,
IssueOwnerHandle: issueOwnerIdent.Handle.String(),
OrderedReactionKinds: db.OrderedReactionKinds,
Reactions: reactionCountMap,
···
return
}
rp.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
LoggedInUser: user,
RepoInfo: f.RepoInfo(user),
Issue: issue,
Comment: comment,
})
···
}
// optimistic update for htmx
comment.Body = newBody
comment.Edited = &edited
···
rp.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
LoggedInUser: user,
RepoInfo: f.RepoInfo(user),
Issue: issue,
Comment: comment,
})
···
}
// optimistic update for htmx
comment.Body = ""
comment.Deleted = &deleted
···
rp.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
LoggedInUser: user,
RepoInfo: f.RepoInfo(user),
Issue: issue,
Comment: comment,
})
}
func (rp *Issues) RepoIssues(w http.ResponseWriter, r *http.Request) {
···
return
}
rp.pages.RepoIssues(w, pages.RepoIssuesParams{
LoggedInUser: rp.oauth.GetUser(r),
RepoInfo: f.RepoInfo(user),
Issues: issues,
FilteringByOpen: isOpen,
Page: page,
})
}
func (rp *Issues) NewIssue(w http.ResponseWriter, r *http.Request) {
-16
appview/knots/knots.go
···
repoByMember[r.Did] = append(repoByMember[r.Did], r)
}
-
var didsToResolve []string
-
for _, m := range members {
-
didsToResolve = append(didsToResolve, m)
-
}
-
didsToResolve = append(didsToResolve, reg.ByDid)
-
resolvedIds := k.IdResolver.ResolveIdents(r.Context(), didsToResolve)
-
didHandleMap := make(map[string]string)
-
for _, identity := range resolvedIds {
-
if !identity.Handle.IsInvalidHandle() {
-
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
-
} else {
-
didHandleMap[identity.DID.String()] = identity.DID.String()
-
}
-
}
-
k.Pages.Knot(w, pages.KnotParams{
LoggedInUser: user,
-
DidHandleMap: didHandleMap,
Registration: reg,
Members: members,
Repos: repoByMember,
···
repoByMember[r.Did] = append(repoByMember[r.Did], r)
}
k.Pages.Knot(w, pages.KnotParams{
LoggedInUser: user,
Registration: reg,
Members: members,
Repos: repoByMember,
+16 -2
appview/pages/funcmap.go
···
package pages
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
···
"split": func(s string) []string {
return strings.Split(s, "\n")
},
"truncateAt30": func(s string) string {
if len(s) <= 30 {
return s
···
"negf64": func(a float64) float64 {
return -a
},
-
"cond": func(cond interface{}, a, b string) string {
if cond == nil {
return b
}
···
return html.UnescapeString(s)
},
"nl2br": func(text string) template.HTML {
-
return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "<br>", -1))
},
"unwrapText": func(text string) string {
paragraphs := strings.Split(text, "\n\n")
···
package pages
import (
+
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
···
"split": func(s string) []string {
return strings.Split(s, "\n")
},
+
"resolve": func(s string) string {
+
identity, err := p.resolver.ResolveIdent(context.Background(), s)
+
+
if err != nil {
+
return s
+
}
+
+
if identity.Handle.IsInvalidHandle() {
+
return "handle invalid"
+
}
+
+
return "@" + identity.Handle.String()
+
},
"truncateAt30": func(s string) string {
if len(s) <= 30 {
return s
···
"negf64": func(a float64) float64 {
return -a
},
+
"cond": func(cond any, a, b string) string {
if cond == nil {
return b
}
···
return html.UnescapeString(s)
},
"nl2br": func(text string) template.HTML {
+
return template.HTML(strings.ReplaceAll(template.HTMLEscapeString(text), "\n", "<br>"))
},
"unwrapText": func(text string) string {
paragraphs := strings.Split(text, "\n\n")
+4 -16
appview/pages/pages.go
···
"tangled.sh/tangled.sh/core/appview/pages/markup"
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
"tangled.sh/tangled.sh/core/appview/pagination"
"tangled.sh/tangled.sh/core/patchutil"
"tangled.sh/tangled.sh/core/types"
···
t map[string]*template.Template
avatar config.AvatarConfig
dev bool
embedFS embed.FS
templateDir string // Path to templates on disk for dev mode
rctx *markup.RenderContext
}
-
func NewPages(config *config.Config) *Pages {
// initialized with safe defaults, can be overriden per use
rctx := &markup.RenderContext{
IsDev: config.Core.Dev,
···
avatar: config.Avatar,
embedFS: Files,
rctx: rctx,
templateDir: "appview/pages",
}
···
type TimelineParams struct {
LoggedInUser *oauth.User
Timeline []db.TimelineEvent
-
DidHandleMap map[string]string
}
func (p *Pages) Timeline(w io.Writer, params TimelineParams) error {
···
type KnotParams struct {
LoggedInUser *oauth.User
-
DidHandleMap map[string]string
Registration *db.Registration
Members []string
Repos map[string][]db.Repo
···
Spindle db.Spindle
Members []string
Repos map[string][]db.Repo
-
DidHandleMap map[string]string
}
func (p *Pages) SpindleDashboard(w io.Writer, params SpindleDashboardParams) error {
···
ProfileTimeline *db.ProfileTimeline
Card ProfileCard
Punchcard db.Punchcard
-
-
DidHandleMap map[string]string
}
type ProfileCard struct {
···
LoggedInUser *oauth.User
Repos []db.Repo
Card ProfileCard
-
-
DidHandleMap map[string]string
}
func (p *Pages) ReposPage(w io.Writer, params ReposPageParams) error {
···
LoggedInUser *oauth.User
Profile *db.Profile
AllRepos []PinnedRepo
-
DidHandleMap map[string]string
}
type PinnedRepo struct {
···
RepoInfo repoinfo.RepoInfo
Active string
Issues []db.Issue
-
DidHandleMap map[string]string
Page pagination.Page
FilteringByOpen bool
}
···
Issue db.Issue
Comments []db.Comment
IssueOwnerHandle string
-
DidHandleMap map[string]string
OrderedReactionKinds []db.ReactionKind
Reactions map[db.ReactionKind]int
···
type SingleIssueCommentParams struct {
LoggedInUser *oauth.User
-
DidHandleMap map[string]string
RepoInfo repoinfo.RepoInfo
Issue *db.Issue
Comment *db.Comment
···
RepoInfo repoinfo.RepoInfo
Pulls []*db.Pull
Active string
-
DidHandleMap map[string]string
FilteringBy db.PullState
Stacks map[string]db.Stack
Pipelines map[string]db.Pipeline
···
LoggedInUser *oauth.User
RepoInfo repoinfo.RepoInfo
Active string
-
DidHandleMap map[string]string
Pull *db.Pull
Stack db.Stack
AbandonedPulls []*db.Pull
···
type RepoPullPatchParams struct {
LoggedInUser *oauth.User
-
DidHandleMap map[string]string
RepoInfo repoinfo.RepoInfo
Pull *db.Pull
Stack db.Stack
···
type RepoPullInterdiffParams struct {
LoggedInUser *oauth.User
-
DidHandleMap map[string]string
RepoInfo repoinfo.RepoInfo
Pull *db.Pull
Round int
···
"tangled.sh/tangled.sh/core/appview/pages/markup"
"tangled.sh/tangled.sh/core/appview/pages/repoinfo"
"tangled.sh/tangled.sh/core/appview/pagination"
+
"tangled.sh/tangled.sh/core/idresolver"
"tangled.sh/tangled.sh/core/patchutil"
"tangled.sh/tangled.sh/core/types"
···
t map[string]*template.Template
avatar config.AvatarConfig
+
resolver *idresolver.Resolver
dev bool
embedFS embed.FS
templateDir string // Path to templates on disk for dev mode
rctx *markup.RenderContext
}
+
func NewPages(config *config.Config, res *idresolver.Resolver) *Pages {
// initialized with safe defaults, can be overriden per use
rctx := &markup.RenderContext{
IsDev: config.Core.Dev,
···
avatar: config.Avatar,
embedFS: Files,
rctx: rctx,
+
resolver: res,
templateDir: "appview/pages",
}
···
type TimelineParams struct {
LoggedInUser *oauth.User
Timeline []db.TimelineEvent
}
func (p *Pages) Timeline(w io.Writer, params TimelineParams) error {
···
type KnotParams struct {
LoggedInUser *oauth.User
Registration *db.Registration
Members []string
Repos map[string][]db.Repo
···
Spindle db.Spindle
Members []string
Repos map[string][]db.Repo
}
func (p *Pages) SpindleDashboard(w io.Writer, params SpindleDashboardParams) error {
···
ProfileTimeline *db.ProfileTimeline
Card ProfileCard
Punchcard db.Punchcard
}
type ProfileCard struct {
···
LoggedInUser *oauth.User
Repos []db.Repo
Card ProfileCard
}
func (p *Pages) ReposPage(w io.Writer, params ReposPageParams) error {
···
LoggedInUser *oauth.User
Profile *db.Profile
AllRepos []PinnedRepo
}
type PinnedRepo struct {
···
RepoInfo repoinfo.RepoInfo
Active string
Issues []db.Issue
Page pagination.Page
FilteringByOpen bool
}
···
Issue db.Issue
Comments []db.Comment
IssueOwnerHandle string
OrderedReactionKinds []db.ReactionKind
Reactions map[db.ReactionKind]int
···
type SingleIssueCommentParams struct {
LoggedInUser *oauth.User
RepoInfo repoinfo.RepoInfo
Issue *db.Issue
Comment *db.Comment
···
RepoInfo repoinfo.RepoInfo
Pulls []*db.Pull
Active string
FilteringBy db.PullState
Stacks map[string]db.Stack
Pipelines map[string]db.Pipeline
···
LoggedInUser *oauth.User
RepoInfo repoinfo.RepoInfo
Active string
Pull *db.Pull
Stack db.Stack
AbandonedPulls []*db.Pull
···
type RepoPullPatchParams struct {
LoggedInUser *oauth.User
RepoInfo repoinfo.RepoInfo
Pull *db.Pull
Stack db.Stack
···
type RepoPullInterdiffParams struct {
LoggedInUser *oauth.User
RepoInfo repoinfo.RepoInfo
Pull *db.Pull
Round int
+3 -4
appview/pages/templates/knots/dashboard.html
···
<div>
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
-
{{ i "user" "size-4" }}
-
{{ $user := index $.DidHandleMap . }}
-
<a href="/{{ $user }}">{{ $user }} <span class="ml-2 font-mono text-gray-500">{{.}}</span></a>
</div>
</div>
<div class="ml-2 pl-2 pt-2 border-l border-gray-200 dark:border-gray-700">
···
{{ range $repos }}
<div class="flex gap-2 items-center">
{{ i "book-marked" "size-4" }}
-
<a href="/{{ .Did }}/{{ .Name }}">
{{ .Name }}
</a>
</div>
···
<div>
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
+
{{ template "user/fragments/picHandleLink" . }}
+
<span class="ml-2 font-mono text-gray-500">{{.}}</span>
</div>
</div>
<div class="ml-2 pl-2 pt-2 border-l border-gray-200 dark:border-gray-700">
···
{{ range $repos }}
<div class="flex gap-2 items-center">
{{ i "book-marked" "size-4" }}
+
<a href="/{{ resolve .Did }}/{{ .Name }}">
{{ .Name }}
</a>
</div>
+1 -2
appview/pages/templates/repo/issues/fragments/issueComment.html
···
{{ with .Comment }}
<div id="comment-container-{{.CommentId}}">
<div class="flex items-center gap-2 mb-2 text-gray-500 dark:text-gray-400 text-sm flex-wrap">
-
{{ $owner := index $.DidHandleMap .OwnerDid }}
-
{{ template "user/fragments/picHandleLink" $owner }}
<!-- show user "hats" -->
{{ $isIssueAuthor := eq .OwnerDid $.Issue.OwnerDid }}
···
{{ with .Comment }}
<div id="comment-container-{{.CommentId}}">
<div class="flex items-center gap-2 mb-2 text-gray-500 dark:text-gray-400 text-sm flex-wrap">
+
{{ template "user/fragments/picHandleLink" .OwnerDid }}
<!-- show user "hats" -->
{{ $isIssueAuthor := eq .OwnerDid $.Issue.OwnerDid }}
+1 -1
appview/pages/templates/repo/issues/issue.html
···
{{ if gt $index 0 }}
<div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>
{{ end }}
-
{{ template "repo/issues/fragments/issueComment" (dict "RepoInfo" $.RepoInfo "LoggedInUser" $.LoggedInUser "DidHandleMap" $.DidHandleMap "Issue" $.Issue "Comment" .)}}
</div>
{{ end }}
</section>
···
{{ if gt $index 0 }}
<div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>
{{ end }}
+
{{ template "repo/issues/fragments/issueComment" (dict "RepoInfo" $.RepoInfo "LoggedInUser" $.LoggedInUser "Issue" $.Issue "Comment" .)}}
</div>
{{ end }}
</section>
+1 -2
appview/pages/templates/repo/issues/issues.html
···
</span>
<span class="ml-1">
-
{{ $owner := index $.DidHandleMap .OwnerDid }}
-
{{ template "user/fragments/picHandleLink" $owner }}
</span>
<span class="before:content-['·']">
···
</span>
<span class="ml-1">
+
{{ template "user/fragments/picHandleLink" .OwnerDid }}
</span>
<span class="before:content-['·']">
+2 -2
appview/pages/templates/repo/pipelines/fragments/pipelineSymbol.html
···
</div>
{{ else if $allFail }}
<div class="flex gap-1 items-center">
-
{{ i "x" "size-4 text-red-600" }}
<span>0/{{ $total }}</span>
</div>
{{ else if $allTimeout }}
<div class="flex gap-1 items-center">
-
{{ i "clock-alert" "size-4 text-orange-400" }}
<span>0/{{ $total }}</span>
</div>
{{ else }}
···
</div>
{{ else if $allFail }}
<div class="flex gap-1 items-center">
+
{{ i "x" "size-4 text-red-500" }}
<span>0/{{ $total }}</span>
</div>
{{ else if $allTimeout }}
<div class="flex gap-1 items-center">
+
{{ i "clock-alert" "size-4 text-orange-500" }}
<span>0/{{ $total }}</span>
</div>
{{ else }}
+2 -2
appview/pages/templates/repo/pulls/fragments/pullHeader.html
···
{{ $icon = "git-merge" }}
{{ end }}
<section class="mt-2">
<div class="flex items-center gap-2">
<div
···
</div>
<span class="text-gray-500 dark:text-gray-400 text-sm flex flex-wrap items-center gap-1">
opened by
-
{{ $owner := index $.DidHandleMap .Pull.OwnerDid }}
-
{{ template "user/fragments/picHandleLink" $owner }}
<span class="select-none before:content-['\00B7']"></span>
{{ template "repo/fragments/time" .Pull.Created }}
···
{{ $icon = "git-merge" }}
{{ end }}
+
{{ $owner := resolve .Pull.OwnerDid }}
<section class="mt-2">
<div class="flex items-center gap-2">
<div
···
</div>
<span class="text-gray-500 dark:text-gray-400 text-sm flex flex-wrap items-center gap-1">
opened by
+
{{ template "user/fragments/picHandleLink" .Pull.OwnerDid }}
<span class="select-none before:content-['\00B7']"></span>
{{ template "repo/fragments/time" .Pull.Created }}
+3 -4
appview/pages/templates/repo/pulls/pull.html
···
<!-- round summary -->
<div class="rounded drop-shadow-sm bg-white dark:bg-gray-800 p-2 text-gray-500 dark:text-gray-400">
<span class="gap-1 flex items-center">
-
{{ $owner := index $.DidHandleMap $.Pull.OwnerDid }}
{{ $re := "re" }}
{{ if eq .RoundNumber 0 }}
{{ $re = "" }}
{{ end }}
<span class="hidden md:inline">{{$re}}submitted</span>
-
by {{ template "user/fragments/picHandleLink" $owner }}
<span class="select-none before:content-['\00B7']"></span>
<a class="text-gray-500 dark:text-gray-400 hover:text-gray-500" href="#round-#{{ .RoundNumber }}">{{ template "repo/fragments/shortTime" .Created }}</a>
<span class="select-none before:content-['·']"></span>
···
<div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>
{{ end }}
<div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1">
-
{{ $owner := index $.DidHandleMap $c.OwnerDid }}
-
{{ template "user/fragments/picHandleLink" $owner }}
<span class="before:content-['·']"></span>
<a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.ID}}">{{ template "repo/fragments/time" $c.Created }}</a>
</div>
···
<!-- round summary -->
<div class="rounded drop-shadow-sm bg-white dark:bg-gray-800 p-2 text-gray-500 dark:text-gray-400">
<span class="gap-1 flex items-center">
+
{{ $owner := resolve $.Pull.OwnerDid }}
{{ $re := "re" }}
{{ if eq .RoundNumber 0 }}
{{ $re = "" }}
{{ end }}
<span class="hidden md:inline">{{$re}}submitted</span>
+
by {{ template "user/fragments/picHandleLink" $.Pull.OwnerDid }}
<span class="select-none before:content-['\00B7']"></span>
<a class="text-gray-500 dark:text-gray-400 hover:text-gray-500" href="#round-#{{ .RoundNumber }}">{{ template "repo/fragments/shortTime" .Created }}</a>
<span class="select-none before:content-['·']"></span>
···
<div class="absolute left-8 -top-2 w-px h-2 bg-gray-300 dark:bg-gray-600"></div>
{{ end }}
<div class="text-sm text-gray-500 dark:text-gray-400 flex items-center gap-1">
+
{{ template "user/fragments/picHandleLink" $c.OwnerDid }}
<span class="before:content-['·']"></span>
<a class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-300" href="#comment-{{.ID}}">{{ template "repo/fragments/time" $c.Created }}</a>
</div>
+1 -2
appview/pages/templates/repo/pulls/pulls.html
···
</a>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-1">
-
{{ $owner := index $.DidHandleMap .OwnerDid }}
{{ $bgColor := "bg-gray-800 dark:bg-gray-700" }}
{{ $icon := "ban" }}
···
</span>
<span class="ml-1">
-
{{ template "user/fragments/picHandleLink" $owner }}
</span>
<span class="before:content-['·']">
···
</a>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-1">
{{ $bgColor := "bg-gray-800 dark:bg-gray-700" }}
{{ $icon := "ban" }}
···
</span>
<span class="ml-1">
+
{{ template "user/fragments/picHandleLink" .OwnerDid }}
</span>
<span class="before:content-['·']">
+2 -4
appview/pages/templates/spindles/dashboard.html
···
<div>
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
-
{{ i "user" "size-4" }}
-
{{ $user := index $.DidHandleMap . }}
-
<a href="/{{ $user }}">{{ $user }}</a>
</div>
{{ if ne $.LoggedInUser.Did . }}
{{ block "removeMemberButton" (list $ . ) }} {{ end }}
···
hx-post="/spindles/{{ $root.Spindle.Instance }}/remove"
hx-swap="none"
hx-vals='{"member": "{{$member}}" }'
-
hx-confirm="Are you sure you want to remove {{ index $root.DidHandleMap $member }} from this instance?"
>
{{ i "user-minus" "w-4 h-4" }}
remove
···
<div>
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
+
{{ template "user/fragments/picHandleLink" . }}
</div>
{{ if ne $.LoggedInUser.Did . }}
{{ block "removeMemberButton" (list $ . ) }} {{ end }}
···
hx-post="/spindles/{{ $root.Spindle.Instance }}/remove"
hx-swap="none"
hx-vals='{"member": "{{$member}}" }'
+
hx-confirm="Are you sure you want to remove {{ resolve $member }} from this instance?"
>
{{ i "user-minus" "w-4 h-4" }}
remove
+9 -8
appview/pages/templates/timeline.html
···
{{ $root := index . 0 }}
{{ $repo := index . 1 }}
{{ $source := index . 2 }}
-
{{ $userHandle := index $root.DidHandleMap $repo.Did }}
<div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm">
-
{{ template "user/fragments/picHandleLink" $userHandle }}
{{ with $source }}
forked
-
<a href="/{{ index $root.DidHandleMap .Did }}/{{ .Name }}"class="no-underline hover:underline">
-
{{ index $root.DidHandleMap .Did }}/{{ .Name }}
</a>
to
<a href="/{{ $userHandle }}/{{ $repo.Name }}" class="no-underline hover:underline">{{ $repo.Name }}</a>
···
{{ $root := index . 0 }}
{{ $star := index . 1 }}
{{ with $star }}
-
{{ $starrerHandle := index $root.DidHandleMap .StarredByDid }}
-
{{ $repoOwnerHandle := index $root.DidHandleMap .Repo.Did }}
<div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm">
{{ template "user/fragments/picHandleLink" $starrerHandle }}
starred
···
{{ $profile := index . 2 }}
{{ $stat := index . 3 }}
-
{{ $userHandle := index $root.DidHandleMap $follow.UserDid }}
-
{{ $subjectHandle := index $root.DidHandleMap $follow.SubjectDid }}
<div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm">
{{ template "user/fragments/picHandleLink" $userHandle }}
followed
···
{{ $root := index . 0 }}
{{ $repo := index . 1 }}
{{ $source := index . 2 }}
+
{{ $userHandle := resolve $repo.Did }}
<div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm">
+
{{ template "user/fragments/picHandleLink" $repo.Did }}
{{ with $source }}
+
{{ $sourceDid := resolve .Did }}
forked
+
<a href="/{{ $sourceDid }}/{{ .Name }}"class="no-underline hover:underline">
+
{{ $sourceDid }}/{{ .Name }}
</a>
to
<a href="/{{ $userHandle }}/{{ $repo.Name }}" class="no-underline hover:underline">{{ $repo.Name }}</a>
···
{{ $root := index . 0 }}
{{ $star := index . 1 }}
{{ with $star }}
+
{{ $starrerHandle := resolve .StarredByDid }}
+
{{ $repoOwnerHandle := resolve .Repo.Did }}
<div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm">
{{ template "user/fragments/picHandleLink" $starrerHandle }}
starred
···
{{ $profile := index . 2 }}
{{ $stat := index . 3 }}
+
{{ $userHandle := resolve $follow.UserDid }}
+
{{ $subjectHandle := resolve $follow.SubjectDid }}
<div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm">
{{ template "user/fragments/picHandleLink" $userHandle }}
followed
+1 -1
appview/pages/templates/user/fragments/editPins.html
···
<input type="checkbox" id="repo-{{$idx}}" name="pinnedRepo{{$idx}}" value="{{.RepoAt}}" {{if .IsPinned}}checked{{end}}>
<label for="repo-{{$idx}}" class="my-0 py-0 normal-case font-normal w-full">
<div class="flex justify-between items-center w-full">
-
<span class="flex-shrink-0 overflow-hidden text-ellipsis ">{{ index $.DidHandleMap .Did }}/{{.Name}}</span>
<div class="flex gap-1 items-center">
{{ i "star" "size-4 fill-current" }}
<span>{{ .RepoStats.StarCount }}</span>
···
<input type="checkbox" id="repo-{{$idx}}" name="pinnedRepo{{$idx}}" value="{{.RepoAt}}" {{if .IsPinned}}checked{{end}}>
<label for="repo-{{$idx}}" class="my-0 py-0 normal-case font-normal w-full">
<div class="flex justify-between items-center w-full">
+
<span class="flex-shrink-0 overflow-hidden text-ellipsis ">{{ resolve .Did }}/{{.Name}}</span>
<div class="flex gap-1 items-center">
{{ i "star" "size-4 fill-current" }}
<span>{{ .RepoStats.StarCount }}</span>
+3 -2
appview/pages/templates/user/fragments/picHandleLink.html
···
{{ define "user/fragments/picHandleLink" }}
-
<a href="/{{ . }}" class="flex items-center">
-
{{ template "user/fragments/picHandle" . }}
</a>
{{ end }}
···
{{ define "user/fragments/picHandleLink" }}
+
{{ $resolved := resolve . }}
+
<a href="/{{ $resolved }}" class="flex items-center">
+
{{ template "user/fragments/picHandle" $resolved }}
</a>
{{ end }}
+3 -2
appview/pages/templates/user/fragments/repoCard.html
···
{{ with $repo }}
<div class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800">
<div class="font-medium dark:text-white flex gap-2 items-center">
{{- if $fullName -}}
-
<a href="/{{ index $root.DidHandleMap .Did }}/{{ .Name }}">{{ index $root.DidHandleMap .Did }}/{{ .Name }}</a>
{{- else -}}
-
<a href="/{{ index $root.DidHandleMap .Did }}/{{ .Name }}">{{ .Name }}</a>
{{- end -}}
</div>
{{ with .Description }}
···
{{ with $repo }}
<div class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800">
<div class="font-medium dark:text-white flex gap-2 items-center">
+
{{ $repoOwner := resolve .Did }}
{{- if $fullName -}}
+
<a href="/{{ $repoOwner }}/{{ .Name }}">{{ $repoOwner }}/{{ .Name }}</a>
{{- else -}}
+
<a href="/{{ $repoOwner }}/{{ .Name }}">{{ .Name }}</a>
{{- end -}}
</div>
{{ with .Description }}
+13 -20
appview/pages/templates/user/profile.html
···
</div>
{{ else }}
<div class="flex flex-col gap-1">
-
{{ block "repoEvents" (list .RepoEvents $.DidHandleMap) }} {{ end }}
-
{{ block "issueEvents" (list .IssueEvents $.DidHandleMap) }} {{ end }}
-
{{ block "pullEvents" (list .PullEvents $.DidHandleMap) }} {{ end }}
</div>
{{ end }}
</div>
···
{{ end }}
{{ define "repoEvents" }}
-
{{ $items := index . 0 }}
-
{{ $handleMap := index . 1 }}
-
-
{{ if gt (len $items) 0 }}
<details>
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
<div class="flex flex-wrap items-center gap-2">
{{ i "book-plus" "w-4 h-4" }}
-
created {{ len $items }} {{if eq (len $items) 1 }}repository{{else}}repositories{{end}}
</div>
</summary>
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
-
{{ range $items }}
<div class="flex flex-wrap items-center gap-2">
<span class="text-gray-500 dark:text-gray-400">
{{ if .Source }}
···
{{ i "book-plus" "w-4 h-4" }}
{{ end }}
</span>
-
<a href="/{{ index $handleMap .Repo.Did }}/{{ .Repo.Name }}" class="no-underline hover:underline">
{{- .Repo.Name -}}
</a>
</div>
···
{{ end }}
{{ define "issueEvents" }}
-
{{ $i := index . 0 }}
-
{{ $items := $i.Items }}
-
{{ $stats := $i.Stats }}
-
{{ $handleMap := index . 1 }}
{{ if gt (len $items) 0 }}
<details>
···
</summary>
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
{{ range $items }}
-
{{ $repoOwner := index $handleMap .Metadata.Repo.Did }}
{{ $repoName := .Metadata.Repo.Name }}
{{ $repoUrl := printf "%s/%s" $repoOwner $repoName }}
···
{{ end }}
{{ define "pullEvents" }}
-
{{ $i := index . 0 }}
-
{{ $items := $i.Items }}
-
{{ $stats := $i.Stats }}
-
{{ $handleMap := index . 1 }}
{{ if gt (len $items) 0 }}
<details>
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
···
</summary>
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
{{ range $items }}
-
{{ $repoOwner := index $handleMap .Repo.Did }}
{{ $repoName := .Repo.Name }}
{{ $repoUrl := printf "%s/%s" $repoOwner $repoName }}
···
</div>
{{ else }}
<div class="flex flex-col gap-1">
+
{{ block "repoEvents" .RepoEvents }} {{ end }}
+
{{ block "issueEvents" .IssueEvents }} {{ end }}
+
{{ block "pullEvents" .PullEvents }} {{ end }}
</div>
{{ end }}
</div>
···
{{ end }}
{{ define "repoEvents" }}
+
{{ if gt (len .) 0 }}
<details>
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
<div class="flex flex-wrap items-center gap-2">
{{ i "book-plus" "w-4 h-4" }}
+
created {{ len . }} {{if eq (len .) 1 }}repository{{else}}repositories{{end}}
</div>
</summary>
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
+
{{ range . }}
<div class="flex flex-wrap items-center gap-2">
<span class="text-gray-500 dark:text-gray-400">
{{ if .Source }}
···
{{ i "book-plus" "w-4 h-4" }}
{{ end }}
</span>
+
<a href="/{{ resolve .Repo.Did }}/{{ .Repo.Name }}" class="no-underline hover:underline">
{{- .Repo.Name -}}
</a>
</div>
···
{{ end }}
{{ define "issueEvents" }}
+
{{ $items := .Items }}
+
{{ $stats := .Stats }}
{{ if gt (len $items) 0 }}
<details>
···
</summary>
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
{{ range $items }}
+
{{ $repoOwner := resolve .Metadata.Repo.Did }}
{{ $repoName := .Metadata.Repo.Name }}
{{ $repoUrl := printf "%s/%s" $repoOwner $repoName }}
···
{{ end }}
{{ define "pullEvents" }}
+
{{ $items := .Items }}
+
{{ $stats := .Stats }}
{{ if gt (len $items) 0 }}
<details>
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
···
</summary>
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
{{ range $items }}
+
{{ $repoOwner := resolve .Repo.Did }}
{{ $repoName := .Repo.Name }}
{{ $repoUrl := printf "%s/%s" $repoOwner $repoName }}
-61
appview/pulls/pulls.go
···
}
}
-
resolvedIds := s.idResolver.ResolveIdents(r.Context(), identsToResolve)
-
didHandleMap := make(map[string]string)
-
for _, identity := range resolvedIds {
-
if !identity.Handle.IsInvalidHandle() {
-
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
-
} else {
-
didHandleMap[identity.DID.String()] = identity.DID.String()
-
}
-
}
-
mergeCheckResponse := s.mergeCheck(f, pull, stack)
resubmitResult := pages.Unknown
if user != nil && user.Did == pull.OwnerDid {
···
s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{
LoggedInUser: user,
RepoInfo: repoInfo,
-
DidHandleMap: didHandleMap,
Pull: pull,
Stack: stack,
AbandonedPulls: abandonedPulls,
···
return
}
-
identsToResolve := []string{pull.OwnerDid}
-
resolvedIds := s.idResolver.ResolveIdents(r.Context(), identsToResolve)
-
didHandleMap := make(map[string]string)
-
for _, identity := range resolvedIds {
-
if !identity.Handle.IsInvalidHandle() {
-
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
-
} else {
-
didHandleMap[identity.DID.String()] = identity.DID.String()
-
}
-
}
-
patch := pull.Submissions[roundIdInt].Patch
diff := patchutil.AsNiceDiff(patch, pull.TargetBranch)
s.pages.RepoPullPatchPage(w, pages.RepoPullPatchParams{
LoggedInUser: user,
-
DidHandleMap: didHandleMap,
RepoInfo: f.RepoInfo(user),
Pull: pull,
Stack: stack,
···
return
}
-
identsToResolve := []string{pull.OwnerDid}
-
resolvedIds := s.idResolver.ResolveIdents(r.Context(), identsToResolve)
-
didHandleMap := make(map[string]string)
-
for _, identity := range resolvedIds {
-
if !identity.Handle.IsInvalidHandle() {
-
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
-
} else {
-
didHandleMap[identity.DID.String()] = identity.DID.String()
-
}
-
}
-
currentPatch, err := patchutil.AsDiff(pull.Submissions[roundIdInt].Patch)
if err != nil {
log.Println("failed to interdiff; current patch malformed")
···
RepoInfo: f.RepoInfo(user),
Pull: pull,
Round: roundIdInt,
-
DidHandleMap: didHandleMap,
Interdiff: interdiff,
DiffOpts: diffOpts,
})
···
return
}
-
identsToResolve := []string{pull.OwnerDid}
-
resolvedIds := s.idResolver.ResolveIdents(r.Context(), identsToResolve)
-
didHandleMap := make(map[string]string)
-
for _, identity := range resolvedIds {
-
if !identity.Handle.IsInvalidHandle() {
-
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
-
} else {
-
didHandleMap[identity.DID.String()] = identity.DID.String()
-
}
-
}
-
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Write([]byte(pull.Submissions[roundIdInt].Patch))
}
···
m[p.Sha] = p
}
-
identsToResolve := make([]string, len(pulls))
-
for i, pull := range pulls {
-
identsToResolve[i] = pull.OwnerDid
-
}
-
resolvedIds := s.idResolver.ResolveIdents(r.Context(), identsToResolve)
-
didHandleMap := make(map[string]string)
-
for _, identity := range resolvedIds {
-
if !identity.Handle.IsInvalidHandle() {
-
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
-
} else {
-
didHandleMap[identity.DID.String()] = identity.DID.String()
-
}
-
}
-
s.pages.RepoPulls(w, pages.RepoPullsParams{
LoggedInUser: s.oauth.GetUser(r),
RepoInfo: f.RepoInfo(user),
Pulls: pulls,
-
DidHandleMap: didHandleMap,
FilteringBy: state,
Stacks: stacks,
Pipelines: m,
···
}
}
mergeCheckResponse := s.mergeCheck(f, pull, stack)
resubmitResult := pages.Unknown
if user != nil && user.Did == pull.OwnerDid {
···
s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{
LoggedInUser: user,
RepoInfo: repoInfo,
Pull: pull,
Stack: stack,
AbandonedPulls: abandonedPulls,
···
return
}
patch := pull.Submissions[roundIdInt].Patch
diff := patchutil.AsNiceDiff(patch, pull.TargetBranch)
s.pages.RepoPullPatchPage(w, pages.RepoPullPatchParams{
LoggedInUser: user,
RepoInfo: f.RepoInfo(user),
Pull: pull,
Stack: stack,
···
return
}
currentPatch, err := patchutil.AsDiff(pull.Submissions[roundIdInt].Patch)
if err != nil {
log.Println("failed to interdiff; current patch malformed")
···
RepoInfo: f.RepoInfo(user),
Pull: pull,
Round: roundIdInt,
Interdiff: interdiff,
DiffOpts: diffOpts,
})
···
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Write([]byte(pull.Submissions[roundIdInt].Patch))
}
···
m[p.Sha] = p
}
s.pages.RepoPulls(w, pages.RepoPullsParams{
LoggedInUser: s.oauth.GetUser(r),
RepoInfo: f.RepoInfo(user),
Pulls: pulls,
FilteringBy: state,
Stacks: stacks,
Pipelines: m,
-13
appview/spindles/spindles.go
···
return
}
-
identsToResolve := make([]string, len(members))
-
copy(identsToResolve, members)
-
resolvedIds := s.IdResolver.ResolveIdents(r.Context(), identsToResolve)
-
didHandleMap := make(map[string]string)
-
for _, identity := range resolvedIds {
-
if !identity.Handle.IsInvalidHandle() {
-
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
-
} else {
-
didHandleMap[identity.DID.String()] = identity.DID.String()
-
}
-
}
-
// organize repos by did
repoMap := make(map[string][]db.Repo)
for _, r := range repos {
···
Spindle: spindle,
Members: members,
Repos: repoMap,
-
DidHandleMap: didHandleMap,
})
}
···
return
}
// organize repos by did
repoMap := make(map[string][]db.Repo)
for _, r := range repos {
···
Spindle: spindle,
Members: members,
Repos: repoMap,
})
}
-27
appview/state/profile.go
···
}
}
-
resolvedIds := s.idResolver.ResolveIdents(r.Context(), didsToResolve)
-
didHandleMap := make(map[string]string)
-
for _, identity := range resolvedIds {
-
if !identity.Handle.IsInvalidHandle() {
-
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
-
} else {
-
didHandleMap[identity.DID.String()] = identity.DID.String()
-
}
-
}
-
followers, following, err := db.GetFollowerFollowing(s.db, ident.DID.String())
if err != nil {
log.Printf("getting follow stats repos for %s: %s", ident.DID.String(), err)
···
LoggedInUser: loggedInUser,
Repos: pinnedRepos,
CollaboratingRepos: pinnedCollaboratingRepos,
-
DidHandleMap: didHandleMap,
Card: pages.ProfileCard{
UserDid: ident.DID.String(),
UserHandle: ident.Handle.String(),
···
s.pages.ReposPage(w, pages.ReposPageParams{
LoggedInUser: loggedInUser,
Repos: repos,
-
DidHandleMap: map[string]string{ident.DID.String(): ident.Handle.String()},
Card: pages.ProfileCard{
UserDid: ident.DID.String(),
UserHandle: ident.Handle.String(),
···
})
}
-
var didsToResolve []string
-
for _, r := range allRepos {
-
didsToResolve = append(didsToResolve, r.Did)
-
}
-
resolvedIds := s.idResolver.ResolveIdents(r.Context(), didsToResolve)
-
didHandleMap := make(map[string]string)
-
for _, identity := range resolvedIds {
-
if !identity.Handle.IsInvalidHandle() {
-
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
-
} else {
-
didHandleMap[identity.DID.String()] = identity.DID.String()
-
}
-
}
-
s.pages.EditPinsFragment(w, pages.EditPinsParams{
LoggedInUser: user,
Profile: profile,
AllRepos: allRepos,
-
DidHandleMap: didHandleMap,
})
}
···
}
}
followers, following, err := db.GetFollowerFollowing(s.db, ident.DID.String())
if err != nil {
log.Printf("getting follow stats repos for %s: %s", ident.DID.String(), err)
···
LoggedInUser: loggedInUser,
Repos: pinnedRepos,
CollaboratingRepos: pinnedCollaboratingRepos,
Card: pages.ProfileCard{
UserDid: ident.DID.String(),
UserHandle: ident.Handle.String(),
···
s.pages.ReposPage(w, pages.ReposPageParams{
LoggedInUser: loggedInUser,
Repos: repos,
Card: pages.ProfileCard{
UserDid: ident.DID.String(),
UserHandle: ident.Handle.String(),
···
})
}
s.pages.EditPinsFragment(w, pages.EditPinsParams{
LoggedInUser: user,
Profile: profile,
AllRepos: allRepos,
})
}
+2 -31
appview/state/state.go
···
return nil, fmt.Errorf("failed to create enforcer: %w", err)
}
-
pgs := pages.NewPages(config)
-
res, err := idresolver.RedisResolver(config.Redis.ToURL())
if err != nil {
log.Printf("failed to create redis resolver: %v", err)
res = idresolver.DefaultResolver()
}
cache := cache.New(config.Redis.Addr)
sess := session.New(cache)
···
s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.")
}
-
var didsToResolve []string
-
for _, ev := range timeline {
-
if ev.Repo != nil {
-
didsToResolve = append(didsToResolve, ev.Repo.Did)
-
if ev.Source != nil {
-
didsToResolve = append(didsToResolve, ev.Source.Did)
-
}
-
}
-
if ev.Follow != nil {
-
didsToResolve = append(didsToResolve, ev.Follow.UserDid, ev.Follow.SubjectDid)
-
}
-
if ev.Star != nil {
-
didsToResolve = append(didsToResolve, ev.Star.StarredByDid, ev.Star.Repo.Did)
-
}
-
}
-
-
resolvedIds := s.idResolver.ResolveIdents(r.Context(), didsToResolve)
-
didHandleMap := make(map[string]string)
-
for _, identity := range resolvedIds {
-
if !identity.Handle.IsInvalidHandle() {
-
didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
-
} else {
-
didHandleMap[identity.DID.String()] = identity.DID.String()
-
}
-
}
-
s.pages.Timeline(w, pages.TimelineParams{
LoggedInUser: user,
Timeline: timeline,
-
DidHandleMap: didHandleMap,
})
-
-
return
}
func (s *State) Keys(w http.ResponseWriter, r *http.Request) {
···
return nil, fmt.Errorf("failed to create enforcer: %w", err)
}
res, err := idresolver.RedisResolver(config.Redis.ToURL())
if err != nil {
log.Printf("failed to create redis resolver: %v", err)
res = idresolver.DefaultResolver()
}
+
pgs := pages.NewPages(config, res)
+
cache := cache.New(config.Redis.Addr)
sess := session.New(cache)
···
s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.")
}
s.pages.Timeline(w, pages.TimelineParams{
LoggedInUser: user,
Timeline: timeline,
})
}
func (s *State) Keys(w http.ResponseWriter, r *http.Request) {