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
··· 61 > 62 <a href="/{{ $user }}">profile</a> 63 <a href="/{{ $user }}?tab=repos">repositories</a> 64 - <a href="/strings/{{ $user }}">strings</a> 65 <a href="/knots">knots</a> 66 <a href="/spindles">spindles</a> 67 <a href="/settings">settings</a>
··· 61 > 62 <a href="/{{ $user }}">profile</a> 63 <a href="/{{ $user }}?tab=repos">repositories</a> 64 + <a href="/{{ $user }}?tab=strings">strings</a> 65 <a href="/knots">knots</a> 66 <a href="/spindles">spindles</a> 67 <a href="/settings">settings</a>
+45
appview/pages/templates/user/strings.html
···
··· 1 + {{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }} · strings {{ end }} 2 + 3 + {{ define "profileContent" }} 4 + <div id="all-strings" class="md:col-span-8 order-2 md:order-2"> 5 + {{ block "allStrings" . }}{{ end }} 6 + </div> 7 + {{ end }} 8 + 9 + {{ define "allStrings" }} 10 + <div id="strings" class="grid grid-cols-1 gap-4 mb-6"> 11 + {{ range .Strings }} 12 + <div class="border border-gray-200 dark:border-gray-700 rounded-sm"> 13 + {{ template "singleString" (list $ .) }} 14 + </div> 15 + {{ else }} 16 + <p class="px-6 dark:text-white">This user does not have any strings yet.</p> 17 + {{ end }} 18 + </div> 19 + {{ end }} 20 + 21 + {{ define "singleString" }} 22 + {{ $root := index . 0 }} 23 + {{ $s := index . 1 }} 24 + <div class="py-4 px-6 rounded bg-white dark:bg-gray-800"> 25 + <div class="font-medium dark:text-white flex gap-2 items-center"> 26 + <a href="/strings/{{ or $root.Card.UserHandle $root.Card.UserDid }}/{{ $s.Rkey }}">{{ $s.Filename }}</a> 27 + </div> 28 + {{ with $s.Description }} 29 + <div class="text-gray-600 dark:text-gray-300 text-sm"> 30 + {{ . }} 31 + </div> 32 + {{ end }} 33 + 34 + {{ $stat := $s.Stats }} 35 + <div class="text-gray-400 pt-4 text-sm font-mono inline-flex gap-2 mt-auto"> 36 + <span>{{ $stat.LineCount }} line{{if ne $stat.LineCount 1}}s{{end}}</span> 37 + <span class="select-none [&:before]:content-['·']"></span> 38 + {{ with $s.Edited }} 39 + <span>edited {{ template "repo/fragments/shortTimeAgo" . }}</span> 40 + {{ else }} 41 + {{ template "repo/fragments/shortTimeAgo" $s.Created }} 42 + {{ end }} 43 + </div> 44 + </div> 45 + {{ end }}
+57 -10
appview/state/profile.go
··· 24 func (s *State) Profile(w http.ResponseWriter, r *http.Request) { 25 tabVal := r.URL.Query().Get("tab") 26 switch tabVal { 27 - case "", "overview": 28 - s.profileOverview(w, r) 29 case "repos": 30 s.reposPage(w, r) 31 case "followers": ··· 34 s.followingPage(w, r) 35 case "starred": 36 s.starredPage(w, r) 37 } 38 } 39 ··· 54 return nil, fmt.Errorf("failed to get profile: %w", err) 55 } 56 57 followStats, err := db.GetFollowerFollowingCount(s.db, did) 58 if err != nil { 59 return nil, fmt.Errorf("failed to get follower stats: %w", err) ··· 78 } 79 80 return &pages.ProfileCard{ 81 - UserDid: did, 82 - UserHandle: ident.Handle.String(), 83 - Profile: profile, 84 - FollowStatus: followStatus, 85 - FollowersCount: followStats.Followers, 86 - FollowingCount: followStats.Following, 87 - Punchcard: punchcard, 88 }, nil 89 } 90 ··· 218 }) 219 } 220 221 type FollowsPageParams struct { 222 Follows []pages.FollowCard 223 Card *pages.ProfileCard ··· 283 followStatus := db.IsNotFollowing 284 if _, exists := loggedInUserFollowing[did]; exists { 285 followStatus = db.IsFollowing 286 - } else if loggedInUser.Did == did { 287 followStatus = db.IsSelf 288 } 289
··· 24 func (s *State) Profile(w http.ResponseWriter, r *http.Request) { 25 tabVal := r.URL.Query().Get("tab") 26 switch tabVal { 27 case "repos": 28 s.reposPage(w, r) 29 case "followers": ··· 32 s.followingPage(w, r) 33 case "starred": 34 s.starredPage(w, r) 35 + case "strings": 36 + s.stringsPage(w, r) 37 + default: 38 + s.profileOverview(w, r) 39 } 40 } 41 ··· 56 return nil, fmt.Errorf("failed to get profile: %w", err) 57 } 58 59 + repoCount, err := db.CountRepos(s.db, db.FilterEq("did", did)) 60 + if err != nil { 61 + return nil, fmt.Errorf("failed to get repo count: %w", err) 62 + } 63 + 64 + stringCount, err := db.CountStrings(s.db, db.FilterEq("did", did)) 65 + if err != nil { 66 + return nil, fmt.Errorf("failed to get string count: %w", err) 67 + } 68 + 69 + starredCount, err := db.CountStars(s.db, db.FilterEq("starred_by_did", did)) 70 + if err != nil { 71 + return nil, fmt.Errorf("failed to get starred repo count: %w", err) 72 + } 73 + 74 followStats, err := db.GetFollowerFollowingCount(s.db, did) 75 if err != nil { 76 return nil, fmt.Errorf("failed to get follower stats: %w", err) ··· 95 } 96 97 return &pages.ProfileCard{ 98 + UserDid: did, 99 + UserHandle: ident.Handle.String(), 100 + Profile: profile, 101 + FollowStatus: followStatus, 102 + Stats: pages.ProfileStats{ 103 + RepoCount: repoCount, 104 + StringCount: stringCount, 105 + StarredCount: starredCount, 106 + FollowersCount: followStats.Followers, 107 + FollowingCount: followStats.Following, 108 + }, 109 + Punchcard: punchcard, 110 }, nil 111 } 112 ··· 240 }) 241 } 242 243 + func (s *State) stringsPage(w http.ResponseWriter, r *http.Request) { 244 + l := s.logger.With("handler", "stringsPage") 245 + 246 + profile, err := s.profile(r) 247 + if err != nil { 248 + l.Error("failed to build profile card", "err", err) 249 + s.pages.Error500(w) 250 + return 251 + } 252 + l = l.With("profileDid", profile.UserDid, "profileHandle", profile.UserHandle) 253 + 254 + strings, err := db.GetStrings(s.db, 0, db.FilterEq("did", profile.UserDid)) 255 + if err != nil { 256 + l.Error("failed to get strings", "err", err) 257 + s.pages.Error500(w) 258 + return 259 + } 260 + 261 + err = s.pages.ProfileStrings(w, pages.ProfileStringsParams{ 262 + LoggedInUser: s.oauth.GetUser(r), 263 + Strings: strings, 264 + Card: profile, 265 + }) 266 + } 267 + 268 type FollowsPageParams struct { 269 Follows []pages.FollowCard 270 Card *pages.ProfileCard ··· 330 followStatus := db.IsNotFollowing 331 if _, exists := loggedInUserFollowing[did]; exists { 332 followStatus = db.IsFollowing 333 + } else if loggedInUser != nil && loggedInUser.Did == did { 334 followStatus = db.IsSelf 335 } 336
+1 -59
appview/strings/strings.go
··· 5 "log/slog" 6 "net/http" 7 "path" 8 - "slices" 9 "strconv" 10 "time" 11 ··· 161 } 162 163 func (s *Strings) dashboard(w http.ResponseWriter, r *http.Request) { 164 - l := s.Logger.With("handler", "dashboard") 165 - 166 - id, ok := r.Context().Value("resolvedId").(identity.Identity) 167 - if !ok { 168 - l.Error("malformed middleware") 169 - w.WriteHeader(http.StatusInternalServerError) 170 - return 171 - } 172 - l = l.With("did", id.DID, "handle", id.Handle) 173 - 174 - all, err := db.GetStrings( 175 - s.Db, 176 - 0, 177 - db.FilterEq("did", id.DID), 178 - ) 179 - if err != nil { 180 - l.Error("failed to fetch strings", "err", err) 181 - w.WriteHeader(http.StatusInternalServerError) 182 - return 183 - } 184 - 185 - slices.SortFunc(all, func(a, b db.String) int { 186 - if a.Created.After(b.Created) { 187 - return -1 188 - } else { 189 - return 1 190 - } 191 - }) 192 - 193 - profile, err := db.GetProfile(s.Db, id.DID.String()) 194 - if err != nil { 195 - l.Error("failed to fetch user profile", "err", err) 196 - w.WriteHeader(http.StatusInternalServerError) 197 - return 198 - } 199 - loggedInUser := s.OAuth.GetUser(r) 200 - followStatus := db.IsNotFollowing 201 - if loggedInUser != nil { 202 - followStatus = db.GetFollowStatus(s.Db, loggedInUser.Did, id.DID.String()) 203 - } 204 - 205 - followStats, err := db.GetFollowerFollowingCount(s.Db, id.DID.String()) 206 - if err != nil { 207 - l.Error("failed to get follow stats", "err", err) 208 - } 209 - 210 - s.Pages.StringsDashboard(w, pages.StringsDashboardParams{ 211 - LoggedInUser: s.OAuth.GetUser(r), 212 - Card: pages.ProfileCard{ 213 - UserDid: id.DID.String(), 214 - UserHandle: id.Handle.String(), 215 - Profile: profile, 216 - FollowStatus: followStatus, 217 - FollowersCount: followStats.Followers, 218 - FollowingCount: followStats.Following, 219 - }, 220 - Strings: all, 221 - }) 222 } 223 224 func (s *Strings) edit(w http.ResponseWriter, r *http.Request) {
··· 5 "log/slog" 6 "net/http" 7 "path" 8 "strconv" 9 "time" 10 ··· 160 } 161 162 func (s *Strings) dashboard(w http.ResponseWriter, r *http.Request) { 163 + http.Redirect(w, r, fmt.Sprintf("/%s?tab=strings", chi.URLParam(r, "user")), http.StatusFound) 164 } 165 166 func (s *Strings) edit(w http.ResponseWriter, r *http.Request) {