Signed-off-by: oppiliappan me@oppi.li
+14
appview/db/profile.go
+14
appview/db/profile.go
···
22
ByMonth []ByMonth
23
}
24
25
+
func (p *ProfileTimeline) IsEmpty() bool {
26
+
if p == nil {
27
+
return true
28
+
}
29
+
30
+
for _, m := range p.ByMonth {
31
+
if !m.IsEmpty() {
32
+
return false
33
+
}
34
+
}
35
+
36
+
return true
37
+
}
38
+
39
type ByMonth struct {
40
RepoEvents []RepoEvent
41
IssueEvents IssueEvents
+4
-4
appview/db/punchcard.go
+4
-4
appview/db/punchcard.go
···
29
Punches []Punch
30
}
31
32
-
func MakePunchcard(e Execer, filters ...filter) (Punchcard, error) {
33
-
punchcard := Punchcard{}
34
now := time.Now()
35
startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
36
endOfYear := time.Date(now.Year(), 12, 31, 0, 0, 0, 0, time.UTC)
···
63
64
rows, err := e.Query(query, args...)
65
if err != nil {
66
-
return punchcard, err
67
}
68
defer rows.Close()
69
···
72
var date string
73
var count sql.NullInt64
74
if err := rows.Scan(&date, &count); err != nil {
75
-
return punchcard, err
76
}
77
78
punch.Date, err = time.Parse(time.DateOnly, date)
···
29
Punches []Punch
30
}
31
32
+
func MakePunchcard(e Execer, filters ...filter) (*Punchcard, error) {
33
+
punchcard := &Punchcard{}
34
now := time.Now()
35
startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
36
endOfYear := time.Date(now.Year(), 12, 31, 0, 0, 0, 0, time.UTC)
···
63
64
rows, err := e.Query(query, args...)
65
if err != nil {
66
+
return nil, err
67
}
68
defer rows.Close()
69
···
72
var date string
73
var count sql.NullInt64
74
if err := rows.Scan(&date, &count); err != nil {
75
+
return nil, err
76
}
77
78
punch.Date, err = time.Parse(time.DateOnly, date)
+28
-9
appview/pages/pages.go
+28
-9
appview/pages/pages.go
···
409
return p.execute("repo/fork", w, params)
410
}
411
412
-
type ProfileHomePageParams struct {
413
-
LoggedInUser *oauth.User
414
-
Repos []db.Repo
415
-
CollaboratingRepos []db.Repo
416
-
ProfileTimeline *db.ProfileTimeline
417
-
Card ProfileCard
418
-
Punchcard db.Punchcard
419
-
}
420
-
421
type ProfileCard struct {
422
UserDid string
423
UserHandle string
424
FollowStatus db.FollowStatus
425
FollowersCount int
426
FollowingCount int
427
428
Profile *db.Profile
429
}
···
409
return p.execute("repo/fork", w, params)
410
}
411
412
type ProfileCard struct {
413
UserDid string
414
UserHandle string
415
FollowStatus db.FollowStatus
416
FollowersCount int
417
FollowingCount int
418
+
Punchcard *db.Punchcard
419
+
Profile *db.Profile
420
+
Active string
421
+
}
422
+
423
+
func (p *ProfileCard) GetTabs() [][]string {
424
+
tabs := [][]string{
425
+
{"overview", "overview", "square-chart-gantt"},
426
+
{"repos", "repos", "book-marked"},
427
+
{"starred", "starred", "star"},
428
+
}
429
+
430
+
return tabs
431
+
}
432
+
433
+
type ProfileOverviewParams struct {
434
+
LoggedInUser *oauth.User
435
+
Repos []db.Repo
436
+
CollaboratingRepos []db.Repo
437
+
ProfileTimeline *db.ProfileTimeline
438
+
Card *ProfileCard
439
+
Active string
440
+
}
441
+
442
+
func (p *Pages) ProfileOverview(w io.Writer, params ProfileOverviewParams) error {
443
+
params.Active = "overview"
444
+
return p.executeProfile("user/overview", w, params)
445
+
}
446
447
Profile *db.Profile
448
}
+1
-1
appview/pages/templates/user/fragments/editBio.html
+1
-1
appview/pages/templates/user/fragments/editBio.html
-2
appview/pages/templates/user/fragments/profileCard.html
-2
appview/pages/templates/user/fragments/profileCard.html
···
1
{{ define "user/fragments/profileCard" }}
2
{{ $userIdent := didOrHandle .UserDid .UserHandle }}
3
-
<div class="bg-white dark:bg-gray-800 px-6 py-4 rounded drop-shadow-sm max-h-fit">
4
<div class="grid grid-cols-3 md:grid-cols-1 gap-1 items-center">
5
<div id="avatar" class="col-span-1 flex justify-center items-center">
6
<div class="w-3/4 aspect-square relative">
···
85
<div id="update-profile" class="text-red-400 dark:text-red-500"></div>
86
</div>
87
</div>
88
-
</div>
89
{{ end }}
90
91
{{ define "followerFollowing" }}
···
1
{{ define "user/fragments/profileCard" }}
2
{{ $userIdent := didOrHandle .UserDid .UserHandle }}
3
<div class="grid grid-cols-3 md:grid-cols-1 gap-1 items-center">
4
<div id="avatar" class="col-span-1 flex justify-center items-center">
5
<div class="w-3/4 aspect-square relative">
···
84
<div id="update-profile" class="text-red-400 dark:text-red-500"></div>
85
</div>
86
</div>
87
{{ end }}
88
89
{{ define "followerFollowing" }}
+37
-97
appview/pages/templates/user/profile.html
appview/pages/templates/user/overview.html
+37
-97
appview/pages/templates/user/profile.html
appview/pages/templates/user/overview.html
···
1
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }}{{ end }}
2
3
-
{{ define "extrameta" }}
4
-
<meta property="og:title" content="{{ or .Card.UserHandle .Card.UserDid }}" />
5
-
<meta property="og:type" content="profile" />
6
-
<meta property="og:url" content="https://tangled.sh/{{ or .Card.UserHandle .Card.UserDid }}" />
7
-
<meta property="og:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />
8
-
{{ end }}
9
-
10
-
{{ define "content" }}
11
-
<div class="grid grid-cols-1 md:grid-cols-11 gap-4">
12
-
<div class="md:col-span-3 order-1 md:order-1">
13
-
<div class="grid grid-cols-1 gap-4">
14
-
{{ template "user/fragments/profileCard" .Card }}
15
-
{{ block "punchcard" .Punchcard }} {{ end }}
16
-
</div>
17
-
</div>
18
-
<div id="all-repos" class="md:col-span-4 order-2 md:order-2">
19
-
<div class="grid grid-cols-1 gap-4">
20
-
{{ block "ownRepos" . }}{{ end }}
21
-
{{ block "collaboratingRepos" . }}{{ end }}
22
-
</div>
23
-
</div>
24
-
<div class="md:col-span-4 order-3 md:order-3">
25
-
{{ block "profileTimeline" . }}{{ end }}
26
</div>
27
-
</div>
28
{{ end }}
29
30
{{ define "profileTimeline" }}
31
-
<p class="text-sm font-bold p-2 dark:text-white">ACTIVITY</p>
32
<div class="flex flex-col gap-4 relative">
33
{{ with .ProfileTimeline }}
34
{{ range $idx, $byMonth := .ByMonth }}
35
{{ with $byMonth }}
36
-
<div class="bg-white dark:bg-gray-800 px-6 py-4 rounded drop-shadow-sm">
37
-
{{ if eq $idx 0 }}
38
-
39
-
{{ else }}
40
-
{{ $s := "s" }}
41
-
{{ if eq $idx 1 }}
42
-
{{ $s = "" }}
43
-
{{ end }}
44
-
<p class="text-sm font-bold dark:text-white mb-2">{{$idx}} month{{$s}} ago</p>
45
-
{{ end }}
46
47
-
{{ if .IsEmpty }}
48
-
<div class="text-gray-500 dark:text-gray-400">
49
-
No activity for this month
50
-
</div>
51
-
{{ else }}
52
-
<div class="flex flex-col gap-1">
53
-
{{ block "repoEvents" .RepoEvents }} {{ end }}
54
-
{{ block "issueEvents" .IssueEvents }} {{ end }}
55
-
{{ block "pullEvents" .PullEvents }} {{ end }}
56
</div>
57
{{ end }}
58
-
</div>
59
-
60
{{ end }}
61
-
{{ else }}
62
-
<p class="dark:text-white">This user does not have any activity yet.</p>
63
{{ end }}
64
{{ end }}
65
</div>
···
232
233
{{ define "ownRepos" }}
234
<div>
235
-
<div class="text-sm font-bold p-2 pr-0 dark:text-white flex items-center justify-between gap-2">
236
<a href="/@{{ or $.Card.UserHandle $.Card.UserDid }}?tab=repos"
237
class="flex text-black dark:text-white items-center gap-2 no-underline hover:no-underline group">
238
<span>PINNED REPOS</span>
239
-
<span class="flex gap-1 items-center font-normal text-sm text-gray-500 dark:text-gray-400 ">
240
-
view all {{ i "chevron-right" "w-4 h-4" }}
241
-
</span>
242
</a>
243
{{ if and .LoggedInUser (eq .LoggedInUser.Did .Card.UserDid) }}
244
-
<button
245
hx-get="profile/edit-pins"
246
hx-target="#all-repos"
247
-
class="btn py-0 font-normal text-sm flex gap-2 items-center group">
248
{{ i "pencil" "w-3 h-3" }}
249
-
edit
250
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
251
</button>
252
{{ end }}
253
</div>
254
<div id="repos" class="grid grid-cols-1 gap-4 items-stretch">
255
{{ range .Repos }}
256
{{ template "user/fragments/repoCard" (list $ . false) }}
257
{{ else }}
258
-
<p class="px-6 dark:text-white">This user does not have any repos yet.</p>
259
{{ end }}
260
</div>
261
</div>
···
264
{{ define "collaboratingRepos" }}
265
{{ if gt (len .CollaboratingRepos) 0 }}
266
<div>
267
-
<p class="text-sm font-bold p-2 dark:text-white">COLLABORATING ON</p>
268
<div id="collaborating" class="grid grid-cols-1 gap-4">
269
{{ range .CollaboratingRepos }}
270
{{ template "user/fragments/repoCard" (list $ . true) }}
271
{{ else }}
272
<p class="px-6 dark:text-white">This user is not collaborating.</p>
273
{{ end }}
···
276
{{ end }}
277
{{ end }}
278
279
-
{{ define "punchcard" }}
280
-
{{ $now := now }}
281
-
<div>
282
-
<p class="p-2 flex gap-2 text-sm font-bold dark:text-white">
283
-
PUNCHCARD
284
-
<span class="font-normal text-sm text-gray-500 dark:text-gray-400 ">
285
-
{{ .Total | int64 | commaFmt }} commits
286
-
</span>
287
-
</p>
288
-
<div class="bg-white dark:bg-gray-800 px-6 py-4 rounded drop-shadow-sm">
289
-
<div class="grid grid-cols-28 md:grid-cols-14 gap-y-2 w-full h-full">
290
-
{{ range .Punches }}
291
-
{{ $count := .Count }}
292
-
{{ $theme := "bg-gray-200 dark:bg-gray-700 size-[4px]" }}
293
-
{{ if lt $count 1 }}
294
-
{{ $theme = "bg-gray-200 dark:bg-gray-700 size-[4px]" }}
295
-
{{ else if lt $count 2 }}
296
-
{{ $theme = "bg-green-200 dark:bg-green-900 size-[5px]" }}
297
-
{{ else if lt $count 4 }}
298
-
{{ $theme = "bg-green-300 dark:bg-green-800 size-[5px]" }}
299
-
{{ else if lt $count 8 }}
300
-
{{ $theme = "bg-green-400 dark:bg-green-700 size-[6px]" }}
301
-
{{ else }}
302
-
{{ $theme = "bg-green-500 dark:bg-green-600 size-[7px]" }}
303
-
{{ end }}
304
-
305
-
{{ if .Date.After $now }}
306
-
{{ $theme = "border border-gray-200 dark:border-gray-700 size-[4px]" }}
307
-
{{ end }}
308
-
<div class="w-full h-full flex justify-center items-center">
309
-
<div
310
-
class="aspect-square rounded-full transition-all duration-300 {{ $theme }} max-w-full max-h-full"
311
-
title="{{ .Date.Format "2006-01-02" }}: {{ .Count }} commits">
312
-
</div>
313
-
</div>
314
-
{{ end }}
315
-
</div>
316
-
</div>
317
-
</div>
318
-
{{ end }}
···
1
{{ define "title" }}{{ or .Card.UserHandle .Card.UserDid }}{{ end }}
2
3
+
{{ define "profileContent" }}
4
+
<div id="all-repos" class="md:col-span-4 order-2 md:order-2">
5
+
<div class="grid grid-cols-1 gap-4">
6
+
{{ block "ownRepos" . }}{{ end }}
7
+
{{ block "collaboratingRepos" . }}{{ end }}
8
</div>
9
+
</div>
10
+
<div class="md:col-span-4 order-3 md:order-3">
11
+
{{ block "profileTimeline" . }}{{ end }}
12
+
</div>
13
{{ end }}
14
15
{{ define "profileTimeline" }}
16
+
<p class="text-sm font-bold px-2 pb-4 dark:text-white">ACTIVITY</p>
17
<div class="flex flex-col gap-4 relative">
18
+
{{ if .ProfileTimeline.IsEmpty }}
19
+
<p class="dark:text-white">This user does not have any activity yet.</p>
20
+
{{ end }}
21
+
22
{{ with .ProfileTimeline }}
23
{{ range $idx, $byMonth := .ByMonth }}
24
{{ with $byMonth }}
25
+
{{ if not .IsEmpty }}
26
+
<div class="border border-gray-200 dark:border-gray-700 rounded-sm py-4 px-6">
27
+
<p class="text-sm font-mono mb-2 text-gray-500 dark:text-gray-400">
28
+
{{ if eq $idx 0 }}
29
+
this month
30
+
{{ else }}
31
+
{{$idx}} month{{if ne $idx 1}}s{{end}} ago
32
+
{{ end }}
33
+
</p>
34
35
+
<div class="flex flex-col gap-1">
36
+
{{ block "repoEvents" .RepoEvents }} {{ end }}
37
+
{{ block "issueEvents" .IssueEvents }} {{ end }}
38
+
{{ block "pullEvents" .PullEvents }} {{ end }}
39
+
</div>
40
</div>
41
{{ end }}
42
{{ end }}
43
{{ end }}
44
{{ end }}
45
</div>
···
212
213
{{ define "ownRepos" }}
214
<div>
215
+
<div class="text-sm font-bold px-2 pb-4 dark:text-white flex items-center gap-2">
216
<a href="/@{{ or $.Card.UserHandle $.Card.UserDid }}?tab=repos"
217
class="flex text-black dark:text-white items-center gap-2 no-underline hover:no-underline group">
218
<span>PINNED REPOS</span>
219
</a>
220
{{ if and .LoggedInUser (eq .LoggedInUser.Did .Card.UserDid) }}
221
+
<button
222
hx-get="profile/edit-pins"
223
hx-target="#all-repos"
224
+
class="py-0 font-normal text-sm flex gap-2 items-center group">
225
{{ i "pencil" "w-3 h-3" }}
226
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
227
</button>
228
{{ end }}
229
</div>
230
<div id="repos" class="grid grid-cols-1 gap-4 items-stretch">
231
{{ range .Repos }}
232
+
<div class="border border-gray-200 dark:border-gray-700 rounded-sm">
233
{{ template "user/fragments/repoCard" (list $ . false) }}
234
+
</div>
235
{{ else }}
236
+
<p class="dark:text-white">This user does not have any pinned repos.</p>
237
{{ end }}
238
</div>
239
</div>
···
242
{{ define "collaboratingRepos" }}
243
{{ if gt (len .CollaboratingRepos) 0 }}
244
<div>
245
+
<p class="text-sm font-bold px-2 pb-4 dark:text-white">COLLABORATING ON</p>
246
<div id="collaborating" class="grid grid-cols-1 gap-4">
247
{{ range .CollaboratingRepos }}
248
+
<div class="border border-gray-200 dark:border-gray-700 rounded-sm">
249
{{ template "user/fragments/repoCard" (list $ . true) }}
250
+
</div>
251
{{ else }}
252
<p class="px-6 dark:text-white">This user is not collaborating.</p>
253
{{ end }}
···
256
{{ end }}
257
{{ end }}
258