appview/pages: add strings tab to profile #544

merged
opened by oppi.li targeting master from push-mvmrzuxwmzvs
Changed files
+104 -70
appview
pages
templates
layouts
fragments
user
state
strings
+1 -1
appview/pages/templates/layouts/fragments/topbar.html
···
>
<a href="/{{ $user }}">profile</a>
<a href="/{{ $user }}?tab=repos">repositories</a>
-
<a href="/strings/{{ $user }}">strings</a>
<a href="/knots">knots</a>
<a href="/spindles">spindles</a>
<a href="/settings">settings</a>
···
>
<a href="/{{ $user }}">profile</a>
<a href="/{{ $user }}?tab=repos">repositories</a>
+
<a href="/{{ $user }}?tab=strings">strings</a>
<a href="/knots">knots</a>
<a href="/spindles">spindles</a>
<a href="/settings">settings</a>
+45
appview/pages/templates/user/strings.html
···
···
+
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · strings {{ end }}
+
+
{{ define "profileContent" }}
+
<div id="all-strings" class="md:col-span-8 order-2 md:order-2">
+
{{ block "allStrings" . }}{{ end }}
+
</div>
+
{{ end }}
+
+
{{ define "allStrings" }}
+
<div id="strings" class="grid grid-cols-1 gap-4 mb-6">
+
{{ range .Strings }}
+
<div class="border border-gray-200 dark:border-gray-700 rounded-sm">
+
{{ template "singleString" (list $ .) }}
+
</div>
+
{{ else }}
+
<p class="px-6 dark:text-white">This user does not have any strings yet.</p>
+
{{ end }}
+
</div>
+
{{ end }}
+
+
{{ define "singleString" }}
+
{{ $root := index . 0 }}
+
{{ $s := index . 1 }}
+
<div class="py-4 px-6 rounded bg-white dark:bg-gray-800">
+
<div class="font-medium dark:text-white flex gap-2 items-center">
+
<a href="/strings/{{ or $root.Card.UserHandle $root.Card.UserDid }}/{{ $s.Rkey }}">{{ $s.Filename }}</a>
+
</div>
+
{{ with $s.Description }}
+
<div class="text-gray-600 dark:text-gray-300 text-sm">
+
{{ . }}
+
</div>
+
{{ end }}
+
+
{{ $stat := $s.Stats }}
+
<div class="text-gray-400 pt-4 text-sm font-mono inline-flex gap-2 mt-auto">
+
<span>{{ $stat.LineCount }} line{{if ne $stat.LineCount 1}}s{{end}}</span>
+
<span class="select-none [&:before]:content-['·']"></span>
+
{{ with $s.Edited }}
+
<span>edited {{ template "repo/fragments/shortTimeAgo" . }}</span>
+
{{ else }}
+
{{ template "repo/fragments/shortTimeAgo" $s.Created }}
+
{{ end }}
+
</div>
+
</div>
+
{{ end }}
+57 -10
appview/state/profile.go
···
func (s *State) Profile(w http.ResponseWriter, r *http.Request) {
tabVal := r.URL.Query().Get("tab")
switch tabVal {
-
case "", "overview":
-
s.profileOverview(w, r)
case "repos":
s.reposPage(w, r)
case "followers":
···
s.followingPage(w, r)
case "starred":
s.starredPage(w, r)
}
}
···
return nil, fmt.Errorf("failed to get profile: %w", err)
}
followStats, err := db.GetFollowerFollowingCount(s.db, did)
if err != nil {
return nil, fmt.Errorf("failed to get follower stats: %w", err)
···
}
return &pages.ProfileCard{
-
UserDid: did,
-
UserHandle: ident.Handle.String(),
-
Profile: profile,
-
FollowStatus: followStatus,
-
FollowersCount: followStats.Followers,
-
FollowingCount: followStats.Following,
-
Punchcard: punchcard,
}, nil
}
···
})
}
type FollowsPageParams struct {
Follows []pages.FollowCard
Card *pages.ProfileCard
···
followStatus := db.IsNotFollowing
if _, exists := loggedInUserFollowing[did]; exists {
followStatus = db.IsFollowing
-
} else if loggedInUser.Did == did {
followStatus = db.IsSelf
}
···
func (s *State) Profile(w http.ResponseWriter, r *http.Request) {
tabVal := r.URL.Query().Get("tab")
switch tabVal {
case "repos":
s.reposPage(w, r)
case "followers":
···
s.followingPage(w, r)
case "starred":
s.starredPage(w, r)
+
case "strings":
+
s.stringsPage(w, r)
+
default:
+
s.profileOverview(w, r)
}
}
···
return nil, fmt.Errorf("failed to get profile: %w", err)
}
+
repoCount, err := db.CountRepos(s.db, db.FilterEq("did", did))
+
if err != nil {
+
return nil, fmt.Errorf("failed to get repo count: %w", err)
+
}
+
+
stringCount, err := db.CountStrings(s.db, db.FilterEq("did", did))
+
if err != nil {
+
return nil, fmt.Errorf("failed to get string count: %w", err)
+
}
+
+
starredCount, err := db.CountStars(s.db, db.FilterEq("starred_by_did", did))
+
if err != nil {
+
return nil, fmt.Errorf("failed to get starred repo count: %w", err)
+
}
+
followStats, err := db.GetFollowerFollowingCount(s.db, did)
if err != nil {
return nil, fmt.Errorf("failed to get follower stats: %w", err)
···
}
return &pages.ProfileCard{
+
UserDid: did,
+
UserHandle: ident.Handle.String(),
+
Profile: profile,
+
FollowStatus: followStatus,
+
Stats: pages.ProfileStats{
+
RepoCount: repoCount,
+
StringCount: stringCount,
+
StarredCount: starredCount,
+
FollowersCount: followStats.Followers,
+
FollowingCount: followStats.Following,
+
},
+
Punchcard: punchcard,
}, nil
}
···
})
}
+
func (s *State) stringsPage(w http.ResponseWriter, r *http.Request) {
+
l := s.logger.With("handler", "stringsPage")
+
+
profile, err := s.profile(r)
+
if err != nil {
+
l.Error("failed to build profile card", "err", err)
+
s.pages.Error500(w)
+
return
+
}
+
l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle)
+
+
strings, err := db.GetStrings(s.db, 0, db.FilterEq("did", profile.UserDid))
+
if err != nil {
+
l.Error("failed to get strings", "err", err)
+
s.pages.Error500(w)
+
return
+
}
+
+
err = s.pages.ProfileStrings(w, pages.ProfileStringsParams{
+
LoggedInUser: s.oauth.GetUser(r),
+
Strings: strings,
+
Card: profile,
+
})
+
}
+
type FollowsPageParams struct {
Follows []pages.FollowCard
Card *pages.ProfileCard
···
followStatus := db.IsNotFollowing
if _, exists := loggedInUserFollowing[did]; exists {
followStatus = db.IsFollowing
+
} else if loggedInUser != nil && loggedInUser.Did == did {
followStatus = db.IsSelf
}
+1 -59
appview/strings/strings.go
···
"log/slog"
"net/http"
"path"
-
"slices"
"strconv"
"time"
···
}
func (s *Strings) dashboard(w http.ResponseWriter, r *http.Request) {
-
l := s.Logger.With("handler", "dashboard")
-
-
id, ok := r.Context().Value("resolvedId").(identity.Identity)
-
if !ok {
-
l.Error("malformed middleware")
-
w.WriteHeader(http.StatusInternalServerError)
-
return
-
}
-
l = l.With("did", id.DID, "handle", id.Handle)
-
-
all, err := db.GetStrings(
-
s.Db,
-
0,
-
db.FilterEq("did", id.DID),
-
)
-
if err != nil {
-
l.Error("failed to fetch strings", "err", err)
-
w.WriteHeader(http.StatusInternalServerError)
-
return
-
}
-
-
slices.SortFunc(all, func(a, b db.String) int {
-
if a.Created.After(b.Created) {
-
return -1
-
} else {
-
return 1
-
}
-
})
-
-
profile, err := db.GetProfile(s.Db, id.DID.String())
-
if err != nil {
-
l.Error("failed to fetch user profile", "err", err)
-
w.WriteHeader(http.StatusInternalServerError)
-
return
-
}
-
loggedInUser := s.OAuth.GetUser(r)
-
followStatus := db.IsNotFollowing
-
if loggedInUser != nil {
-
followStatus = db.GetFollowStatus(s.Db, loggedInUser.Did, id.DID.String())
-
}
-
-
followStats, err := db.GetFollowerFollowingCount(s.Db, id.DID.String())
-
if err != nil {
-
l.Error("failed to get follow stats", "err", err)
-
}
-
-
s.Pages.StringsDashboard(w, pages.StringsDashboardParams{
-
LoggedInUser: s.OAuth.GetUser(r),
-
Card: pages.ProfileCard{
-
UserDid: id.DID.String(),
-
UserHandle: id.Handle.String(),
-
Profile: profile,
-
FollowStatus: followStatus,
-
FollowersCount: followStats.Followers,
-
FollowingCount: followStats.Following,
-
},
-
Strings: all,
-
})
}
func (s *Strings) edit(w http.ResponseWriter, r *http.Request) {
···
"log/slog"
"net/http"
"path"
"strconv"
"time"
···
}
func (s *Strings) dashboard(w http.ResponseWriter, r *http.Request) {
+
http.Redirect(w, r, fmt.Sprintf("/%s?tab=strings", chi.URLParam(r, "user")), http.StatusFound)
}
func (s *Strings) edit(w http.ResponseWriter, r *http.Request) {