Signed-off-by: oppiliappan me@oppi.li
+1
-1
appview/pages/templates/layouts/fragments/topbar.html
+1
-1
appview/pages/templates/layouts/fragments/topbar.html
···
61
61
>
62
62
<a href="/{{ $user }}">profile</a>
63
63
<a href="/{{ $user }}?tab=repos">repositories</a>
64
-
<a href="/strings/{{ $user }}">strings</a>
64
+
<a href="/{{ $user }}?tab=strings">strings</a>
65
65
<a href="/knots">knots</a>
66
66
<a href="/spindles">spindles</a>
67
67
<a href="/settings">settings</a>
+45
appview/pages/templates/user/strings.html
+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
+57
-10
appview/state/profile.go
···
24
24
func (s *State) Profile(w http.ResponseWriter, r *http.Request) {
25
25
tabVal := r.URL.Query().Get("tab")
26
26
switch tabVal {
27
-
case "", "overview":
28
-
s.profileOverview(w, r)
29
27
case "repos":
30
28
s.reposPage(w, r)
31
29
case "followers":
···
34
32
s.followingPage(w, r)
35
33
case "starred":
36
34
s.starredPage(w, r)
35
+
case "strings":
36
+
s.stringsPage(w, r)
37
+
default:
38
+
s.profileOverview(w, r)
37
39
}
38
40
}
39
41
···
54
56
return nil, fmt.Errorf("failed to get profile: %w", err)
55
57
}
56
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
+
57
74
followStats, err := db.GetFollowerFollowingCount(s.db, did)
58
75
if err != nil {
59
76
return nil, fmt.Errorf("failed to get follower stats: %w", err)
···
78
95
}
79
96
80
97
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,
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,
88
110
}, nil
89
111
}
90
112
···
218
240
})
219
241
}
220
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
+
221
268
type FollowsPageParams struct {
222
269
Follows []pages.FollowCard
223
270
Card *pages.ProfileCard
···
283
330
followStatus := db.IsNotFollowing
284
331
if _, exists := loggedInUserFollowing[did]; exists {
285
332
followStatus = db.IsFollowing
286
-
} else if loggedInUser.Did == did {
333
+
} else if loggedInUser != nil && loggedInUser.Did == did {
287
334
followStatus = db.IsSelf
288
335
}
289
336
+1
-59
appview/strings/strings.go
+1
-59
appview/strings/strings.go
···
5
5
"log/slog"
6
6
"net/http"
7
7
"path"
8
-
"slices"
9
8
"strconv"
10
9
"time"
11
10
···
161
160
}
162
161
163
162
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
-
})
163
+
http.Redirect(w, r, fmt.Sprintf("/%s?tab=strings", chi.URLParam(r, "user")), http.StatusFound)
222
164
}
223
165
224
166
func (s *Strings) edit(w http.ResponseWriter, r *http.Request) {