+48
-18
appview/db/issues.go
+48
-18
appview/db/issues.go
···
12
12
OwnerDid string
13
13
IssueId int
14
14
IssueAt string
15
-
Created *time.Time
15
+
Created time.Time
16
16
Title string
17
17
Body string
18
18
Open bool
19
+
20
+
// optionally, populate this when querying for reverse mappings
21
+
// like comment counts, parent repo etc.
19
22
Metadata *IssueMetadata
20
23
}
21
24
22
25
type IssueMetadata struct {
23
26
CommentCount int
27
+
Repo *Repo
24
28
// labels, assignee etc.
25
29
}
26
30
···
143
147
if err != nil {
144
148
return nil, err
145
149
}
146
-
issue.Created = &createdTime
150
+
issue.Created = createdTime
147
151
issue.Metadata = &metadata
148
152
149
153
issues = append(issues, issue)
···
156
160
return issues, nil
157
161
}
158
162
159
-
func GetIssuesByOwnerDid(e Execer, ownerDid string) ([]Issue, error) {
163
+
// timeframe here is directly passed into the sql query filter, and any
164
+
// timeframe in the past should be negative; e.g.: "-3 months"
165
+
func GetIssuesByOwnerDid(e Execer, ownerDid string, timeframe string) ([]Issue, error) {
160
166
var issues []Issue
161
167
162
168
rows, err := e.Query(
···
168
174
i.title,
169
175
i.body,
170
176
i.open,
171
-
count(c.id)
177
+
r.did,
178
+
r.name,
179
+
r.knot,
180
+
r.rkey,
181
+
r.created
172
182
from
173
183
issues i
174
-
left join
175
-
comments c on i.repo_at = c.repo_at and i.issue_id = c.issue_id
184
+
join
185
+
repos r on i.repo_at = r.at_uri
176
186
where
177
-
i.owner_did = ?
178
-
group by
179
-
i.id, i.owner_did, i.repo_at, i.issue_id, i.created, i.title, i.body, i.open
187
+
i.owner_did = ? and i.created >= date ('now', ?)
180
188
order by
181
189
i.created desc`,
182
-
ownerDid)
190
+
ownerDid, timeframe)
183
191
if err != nil {
184
192
return nil, err
185
193
}
···
187
195
188
196
for rows.Next() {
189
197
var issue Issue
190
-
var createdAt string
191
-
var metadata IssueMetadata
192
-
err := rows.Scan(&issue.OwnerDid, &issue.RepoAt, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open, &metadata.CommentCount)
198
+
var issueCreatedAt, repoCreatedAt string
199
+
var repo Repo
200
+
err := rows.Scan(
201
+
&issue.OwnerDid,
202
+
&issue.RepoAt,
203
+
&issue.IssueId,
204
+
&issueCreatedAt,
205
+
&issue.Title,
206
+
&issue.Body,
207
+
&issue.Open,
208
+
&repo.Did,
209
+
&repo.Name,
210
+
&repo.Knot,
211
+
&repo.Rkey,
212
+
&repoCreatedAt,
213
+
)
193
214
if err != nil {
194
215
return nil, err
195
216
}
196
217
197
-
createdTime, err := time.Parse(time.RFC3339, createdAt)
218
+
issueCreatedTime, err := time.Parse(time.RFC3339, issueCreatedAt)
219
+
if err != nil {
220
+
return nil, err
221
+
}
222
+
issue.Created = issueCreatedTime
223
+
224
+
repoCreatedTime, err := time.Parse(time.RFC3339, repoCreatedAt)
198
225
if err != nil {
199
226
return nil, err
200
227
}
201
-
issue.Created = &createdTime
202
-
issue.Metadata = &metadata
228
+
repo.Created = repoCreatedTime
229
+
230
+
issue.Metadata = &IssueMetadata{
231
+
Repo: &repo,
232
+
}
203
233
204
234
issues = append(issues, issue)
205
235
}
···
226
256
if err != nil {
227
257
return nil, err
228
258
}
229
-
issue.Created = &createdTime
259
+
issue.Created = createdTime
230
260
231
261
return &issue, nil
232
262
}
···
246
276
if err != nil {
247
277
return nil, nil, err
248
278
}
249
-
issue.Created = &createdTime
279
+
issue.Created = createdTime
250
280
251
281
comments, err := GetComments(e, repoAt, issueId)
252
282
if err != nil {
+119
-48
appview/db/profile.go
+119
-48
appview/db/profile.go
···
2
2
3
3
import (
4
4
"fmt"
5
-
"sort"
6
5
"time"
7
6
)
8
7
9
-
type ProfileTimelineEvent struct {
10
-
EventAt time.Time
11
-
Type string
12
-
*Issue
13
-
*Pull
14
-
*Repo
8
+
type RepoEvent struct {
9
+
Repo *Repo
10
+
Source *Repo
11
+
}
12
+
13
+
type ProfileTimeline struct {
14
+
ByMonth []ByMonth
15
+
}
15
16
16
-
// optional: populate only if Repo is a fork
17
-
Source *Repo
17
+
type ByMonth struct {
18
+
RepoEvents []RepoEvent
19
+
IssueEvents IssueEvents
20
+
PullEvents PullEvents
18
21
}
19
22
20
-
func MakeProfileTimeline(e Execer, forDid string) ([]ProfileTimelineEvent, error) {
21
-
timeline := []ProfileTimelineEvent{}
22
-
limit := 30
23
+
func (b ByMonth) IsEmpty() bool {
24
+
return len(b.RepoEvents) == 0 &&
25
+
len(b.IssueEvents.Items) == 0 &&
26
+
len(b.PullEvents.Items) == 0
27
+
}
28
+
29
+
type IssueEvents struct {
30
+
Items []*Issue
31
+
}
32
+
33
+
type IssueEventStats struct {
34
+
Open int
35
+
Closed int
36
+
}
37
+
38
+
func (i IssueEvents) Stats() IssueEventStats {
39
+
var open, closed int
40
+
for _, issue := range i.Items {
41
+
if issue.Open {
42
+
open += 1
43
+
} else {
44
+
closed += 1
45
+
}
46
+
}
47
+
48
+
return IssueEventStats{
49
+
Open: open,
50
+
Closed: closed,
51
+
}
52
+
}
53
+
54
+
type PullEvents struct {
55
+
Items []*Pull
56
+
}
57
+
58
+
func (p PullEvents) Stats() PullEventStats {
59
+
var open, merged, closed int
60
+
for _, pull := range p.Items {
61
+
switch pull.State {
62
+
case PullOpen:
63
+
open += 1
64
+
case PullMerged:
65
+
merged += 1
66
+
case PullClosed:
67
+
closed += 1
68
+
}
69
+
}
70
+
71
+
return PullEventStats{
72
+
Open: open,
73
+
Merged: merged,
74
+
Closed: closed,
75
+
}
76
+
}
77
+
78
+
type PullEventStats struct {
79
+
Closed int
80
+
Open int
81
+
Merged int
82
+
}
83
+
84
+
const TimeframeMonths = 3
85
+
86
+
func MakeProfileTimeline(e Execer, forDid string) (*ProfileTimeline, error) {
87
+
timeline := ProfileTimeline{
88
+
ByMonth: make([]ByMonth, TimeframeMonths),
89
+
}
90
+
currentMonth := time.Now().Month()
91
+
timeframe := fmt.Sprintf("-%d months", TimeframeMonths)
23
92
24
-
pulls, err := GetPullsByOwnerDid(e, forDid)
93
+
pulls, err := GetPullsByOwnerDid(e, forDid, timeframe)
25
94
if err != nil {
26
-
return timeline, fmt.Errorf("error getting pulls by owner did: %w", err)
95
+
return nil, fmt.Errorf("error getting pulls by owner did: %w", err)
27
96
}
28
97
98
+
// group pulls by month
29
99
for _, pull := range pulls {
30
-
repo, err := GetRepoByAtUri(e, string(pull.RepoAt))
31
-
if err != nil {
32
-
return timeline, fmt.Errorf("error getting repo by at uri: %w", err)
100
+
pullMonth := pull.Created.Month()
101
+
102
+
if currentMonth-pullMonth > TimeframeMonths {
103
+
// shouldn't happen; but times are weird
104
+
continue
33
105
}
34
106
35
-
timeline = append(timeline, ProfileTimelineEvent{
36
-
EventAt: pull.Created,
37
-
Type: "pull",
38
-
Pull: &pull,
39
-
Repo: repo,
40
-
})
107
+
idx := currentMonth - pullMonth
108
+
items := &timeline.ByMonth[idx].PullEvents.Items
109
+
110
+
*items = append(*items, &pull)
41
111
}
42
112
43
-
issues, err := GetIssuesByOwnerDid(e, forDid)
113
+
issues, err := GetIssuesByOwnerDid(e, forDid, timeframe)
44
114
if err != nil {
45
-
return timeline, fmt.Errorf("error getting issues by owner did: %w", err)
115
+
return nil, fmt.Errorf("error getting issues by owner did: %w", err)
46
116
}
47
117
48
118
for _, issue := range issues {
49
-
repo, err := GetRepoByAtUri(e, string(issue.RepoAt))
50
-
if err != nil {
51
-
return timeline, fmt.Errorf("error getting repo by at uri: %w", err)
119
+
issueMonth := issue.Created.Month()
120
+
121
+
if currentMonth-issueMonth > TimeframeMonths {
122
+
// shouldn't happen; but times are weird
123
+
continue
52
124
}
53
125
54
-
timeline = append(timeline, ProfileTimelineEvent{
55
-
EventAt: *issue.Created,
56
-
Type: "issue",
57
-
Issue: &issue,
58
-
Repo: repo,
59
-
})
126
+
idx := currentMonth - issueMonth
127
+
items := &timeline.ByMonth[idx].IssueEvents.Items
128
+
129
+
*items = append(*items, &issue)
60
130
}
61
131
62
132
repos, err := GetAllReposByDid(e, forDid)
63
133
if err != nil {
64
-
return timeline, fmt.Errorf("error getting all repos by did: %w", err)
134
+
return nil, fmt.Errorf("error getting all repos by did: %w", err)
65
135
}
66
136
67
137
for _, repo := range repos {
138
+
// TODO: get this in the original query; requires COALESCE because nullable
68
139
var sourceRepo *Repo
69
140
if repo.Source != "" {
70
141
sourceRepo, err = GetRepoByAtUri(e, repo.Source)
···
73
144
}
74
145
}
75
146
76
-
timeline = append(timeline, ProfileTimelineEvent{
77
-
EventAt: repo.Created,
78
-
Type: "repo",
79
-
Repo: &repo,
80
-
Source: sourceRepo,
81
-
})
82
-
}
147
+
repoMonth := repo.Created.Month()
148
+
149
+
if currentMonth-repoMonth > TimeframeMonths {
150
+
// shouldn't happen; but times are weird
151
+
continue
152
+
}
83
153
84
-
sort.Slice(timeline, func(i, j int) bool {
85
-
return timeline[i].EventAt.After(timeline[j].EventAt)
86
-
})
154
+
idx := currentMonth - repoMonth
87
155
88
-
if len(timeline) > limit {
89
-
timeline = timeline[:limit]
156
+
items := &timeline.ByMonth[idx].RepoEvents
157
+
*items = append(*items, RepoEvent{
158
+
Repo: &repo,
159
+
Source: sourceRepo,
160
+
})
90
161
}
91
162
92
-
return timeline, nil
163
+
return &timeline, nil
93
164
}
+40
-14
appview/db/pulls.go
+40
-14
appview/db/pulls.go
···
64
64
// meta
65
65
Created time.Time
66
66
PullSource *PullSource
67
+
68
+
// optionally, populate this when querying for reverse mappings
69
+
Repo *Repo
67
70
}
68
71
69
72
type PullSource struct {
···
522
525
return &pull, nil
523
526
}
524
527
525
-
func GetPullsByOwnerDid(e Execer, did string) ([]Pull, error) {
528
+
// timeframe here is directly passed into the sql query filter, and any
529
+
// timeframe in the past should be negative; e.g.: "-3 months"
530
+
func GetPullsByOwnerDid(e Execer, did, timeframe string) ([]Pull, error) {
526
531
var pulls []Pull
527
532
528
533
rows, err := e.Query(`
529
534
select
530
-
owner_did,
531
-
repo_at,
532
-
pull_id,
533
-
created,
534
-
title,
535
-
state
535
+
p.owner_did,
536
+
p.repo_at,
537
+
p.pull_id,
538
+
p.created,
539
+
p.title,
540
+
p.state,
541
+
r.did,
542
+
r.name,
543
+
r.knot,
544
+
r.rkey,
545
+
r.created
536
546
from
537
-
pulls
547
+
pulls p
548
+
join
549
+
repos r on p.repo_at = r.at_uri
538
550
where
539
-
owner_did = ?
551
+
p.owner_did = ? and p.created >= date ('now', ?)
540
552
order by
541
-
created desc`, did)
553
+
p.created desc`, did, timeframe)
542
554
if err != nil {
543
555
return nil, err
544
556
}
···
546
558
547
559
for rows.Next() {
548
560
var pull Pull
549
-
var createdAt string
561
+
var repo Repo
562
+
var pullCreatedAt, repoCreatedAt string
550
563
err := rows.Scan(
551
564
&pull.OwnerDid,
552
565
&pull.RepoAt,
553
566
&pull.PullId,
554
-
&createdAt,
567
+
&pullCreatedAt,
555
568
&pull.Title,
556
569
&pull.State,
570
+
&repo.Did,
571
+
&repo.Name,
572
+
&repo.Knot,
573
+
&repo.Rkey,
574
+
&repoCreatedAt,
557
575
)
558
576
if err != nil {
559
577
return nil, err
560
578
}
561
579
562
-
createdTime, err := time.Parse(time.RFC3339, createdAt)
580
+
pullCreatedTime, err := time.Parse(time.RFC3339, pullCreatedAt)
563
581
if err != nil {
564
582
return nil, err
565
583
}
566
-
pull.Created = createdTime
584
+
pull.Created = pullCreatedTime
585
+
586
+
repoCreatedTime, err := time.Parse(time.RFC3339, repoCreatedAt)
587
+
if err != nil {
588
+
return nil, err
589
+
}
590
+
repo.Created = repoCreatedTime
591
+
592
+
pull.Repo = &repo
567
593
568
594
pulls = append(pulls, pull)
569
595
}
+3
-1
appview/db/repos.go
+3
-1
appview/db/repos.go
+3
-2
appview/pages/pages.go
+3
-2
appview/pages/pages.go
···
178
178
CollaboratingRepos []db.Repo
179
179
ProfileStats ProfileStats
180
180
FollowStatus db.FollowStatus
181
-
DidHandleMap map[string]string
182
181
AvatarUri string
183
-
ProfileTimeline []db.ProfileTimelineEvent
182
+
ProfileTimeline *db.ProfileTimeline
183
+
184
+
DidHandleMap map[string]string
184
185
}
185
186
186
187
type ProfileStats struct {
+1
-1
appview/pages/templates/repo/blob.html
+1
-1
appview/pages/templates/repo/blob.html
+3
-3
appview/pages/templates/repo/tree.html
+3
-3
appview/pages/templates/repo/tree.html
···
17
17
{{ $containerstyle := "py-1" }}
18
18
{{ $linkstyle := "no-underline hover:underline" }}
19
19
20
-
<div class="pb-2 mb-3 text-base border-b border-gray-200 dark:border-gray-500">
20
+
<div class="pb-2 mb-3 text-base border-b border-gray-200 dark:border-gray-700">
21
21
<div class="flex flex-col md:flex-row md:justify-between gap-2">
22
-
<div id="breadcrumbs" class="overflow-x-auto whitespace-nowrap">
22
+
<div id="breadcrumbs" class="overflow-x-auto whitespace-nowrap text-gray-400 dark:text-gray-500">
23
23
{{ range .BreadCrumbs }}
24
-
<a href="{{ index . 1}}" class="text-bold text-gray-500 dark:text-gray-400 {{ $linkstyle }}">{{ index . 0 }}</a> /
24
+
<a href="{{ index . 1}}" class="text-bold text-gray-500 dark:text-gray-400 {{ $linkstyle }}">{{ index . 0 }}</a> /
25
25
{{ end }}
26
26
</div>
27
27
<div id="dir-info" class="text-gray-500 dark:text-gray-400 text-xs md:text-sm flex flex-wrap items-center gap-1 md:gap-0">
+193
-69
appview/pages/templates/user/profile.html
+193
-69
appview/pages/templates/user/profile.html
···
9
9
{{ block "ownRepos" . }}{{ end }}
10
10
{{ block "collaboratingRepos" . }}{{ end }}
11
11
</div>
12
-
13
12
<div class="md:col-span-2 order-3 md:order-3">
14
13
{{ block "profileTimeline" . }}{{ end }}
15
14
</div>
16
15
</div>
17
16
{{ end }}
18
17
18
+
{{ define "profileTimeline" }}
19
+
<p class="text-sm font-bold py-2 dark:text-white px-6">ACTIVITY</p>
20
+
<div class="flex flex-col gap-6 relative">
21
+
{{ with .ProfileTimeline }}
22
+
{{ range $idx, $byMonth := .ByMonth }}
23
+
{{ with $byMonth }}
24
+
<div class="bg-white dark:bg-gray-800 px-6 py-4 rounded drop-shadow-sm">
25
+
{{ if eq $idx 0 }}
26
+
27
+
{{ else }}
28
+
{{ $s := "s" }}
29
+
{{ if eq $idx 1 }}
30
+
{{ $s = "" }}
31
+
{{ end }}
32
+
<p class="text-sm font-bold dark:text-white mb-2">{{$idx}} month{{$s}} ago</p>
33
+
{{ end }}
19
34
20
-
{{ define "profileTimeline" }}
21
-
<div class="flex flex-col gap-3 relative">
22
-
<p class="px-6 text-sm font-bold py-2 dark:text-white">ACTIVITY</p>
23
-
{{ range .ProfileTimeline }}
24
-
{{ if eq .Type "issue" }}
25
-
<div class="px-6 py-2 bg-white dark:bg-gray-800 rounded drop-shadow-sm w-fit max-w-full flex items-center gap-2">
26
-
{{ $textColor := "text-gray-800 dark:text-gray-400" }}
27
-
{{ $icon := "ban" }}
28
-
{{ if .Issue.Open }}
29
-
{{ $textColor = "text-green-600 dark:text-green-500" }}
30
-
{{ $icon = "circle-dot" }}
31
-
{{ end }}
32
-
<div class="p-1 {{ $textColor }}">
33
-
{{ i $icon "w-5 h-5" }}
35
+
{{ if .IsEmpty }}
36
+
<div class="text-gray-500 dark:text-gray-400">
37
+
No activity for this month
34
38
</div>
35
-
<div>
36
-
<p class="text-gray-600 dark:text-gray-300">
37
-
<a href="/{{ index $.DidHandleMap .Repo.Did }}/{{ .Repo.Name }}/issues/{{ .Issue.IssueId }}" class="no-underline hover:underline">{{ .Issue.Title }} <span class="text-gray-500 dark:text-gray-400">#{{ .Issue.IssueId }}</span></a>
38
-
on
39
-
<a href="/{{ index $.DidHandleMap .Repo.Did }}/{{ .Repo.Name }}" class="no-underline hover:underline">{{ index $.DidHandleMap .Repo.Did }}<span class="select-none">/</span>{{ .Repo.Name }}</a>
40
-
<time class="text-gray-700 dark:text-gray-400 text-xs ml-2">{{ .Issue.Created | shortTimeFmt }}</time>
41
-
</p>
39
+
{{ else }}
40
+
<div class="flex flex-col gap-1">
41
+
{{ block "repoEvents" (list .RepoEvents $.DidHandleMap) }} {{ end }}
42
+
{{ block "issueEvents" (list .IssueEvents $.DidHandleMap) }} {{ end }}
43
+
{{ block "pullEvents" (list .PullEvents $.DidHandleMap) }} {{ end }}
42
44
</div>
45
+
{{ end }}
46
+
</div>
47
+
48
+
{{ end }}
49
+
{{ else }}
50
+
<p class="dark:text-white">This user does not have any activity yet.</p>
51
+
{{ end }}
52
+
{{ end }}
53
+
</div>
54
+
{{ end }}
55
+
56
+
{{ define "repoEvents" }}
57
+
{{ $items := index . 0 }}
58
+
{{ $handleMap := index . 1 }}
59
+
60
+
{{ if gt (len $items) 0 }}
61
+
<details>
62
+
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
63
+
<div class="flex flex-wrap items-center gap-2">
64
+
{{ i "book-plus" "w-4 h-4" }}
65
+
created {{ len $items }} {{if eq (len $items) 1 }}repository{{else}}repositories{{end}}
66
+
</div>
67
+
</summary>
68
+
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
69
+
{{ range $items }}
70
+
<div class="flex flex-wrap items-center gap-2">
71
+
<span class="text-gray-500 dark:text-gray-400">
72
+
{{ if .Source }}
73
+
{{ i "git-fork" "w-4 h-4" }}
74
+
{{ else }}
75
+
{{ i "book-plus" "w-4 h-4" }}
76
+
{{ end }}
77
+
</span>
78
+
<a href="/{{ index $handleMap .Repo.Did }}/{{ .Repo.Name }}" class="no-underline hover:underline">
79
+
{{- .Repo.Name -}}
80
+
</a>
43
81
</div>
44
-
{{ else if eq .Type "pull" }}
45
-
<div class="px-6 py-2 bg-white dark:bg-gray-800 rounded drop-shadow-sm w-fit flex items-center gap-3">
46
-
{{ $textColor := "text-gray-800 dark:text-gray-400" }}
47
-
{{ $icon := "git-pull-request-closed" }}
48
-
{{ if .Pull.State.IsOpen }}
49
-
{{ $textColor = "text-green-600 dark:text-green-500" }}
50
-
{{ $icon = "git-pull-request" }}
51
-
{{ else if .Pull.State.IsMerged }}
52
-
{{ $textColor = "text-purple-600 dark:text-purple-500" }}
53
-
{{ $icon = "git-merge" }}
82
+
{{ end }}
83
+
</div>
84
+
</details>
85
+
{{ end }}
86
+
{{ end }}
87
+
88
+
{{ define "issueEvents" }}
89
+
{{ $i := index . 0 }}
90
+
{{ $items := $i.Items }}
91
+
{{ $stats := $i.Stats }}
92
+
{{ $handleMap := index . 1 }}
93
+
94
+
{{ if gt (len $items) 0 }}
95
+
<details>
96
+
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
97
+
<div class="flex flex-wrap items-center gap-2">
98
+
{{ i "circle-dot" "w-4 h-4" }}
99
+
100
+
<div>
101
+
created {{ len $items }} {{if eq (len $items) 1 }}issue{{else}}issues{{end}}
102
+
</div>
103
+
104
+
{{ if gt $stats.Open 0 }}
105
+
<span class="px-2 py-1/2 text-sm rounded text-white bg-green-600 dark:bg-green-700">
106
+
{{$stats.Open}} open
107
+
</span>
108
+
{{ end }}
109
+
110
+
{{ if gt $stats.Closed 0 }}
111
+
<span class="px-2 py-1/2 text-sm rounded text-white bg-gray-800 dark:bg-gray-700">
112
+
{{$stats.Closed}} closed
113
+
</span>
114
+
{{ end }}
115
+
116
+
</div>
117
+
</summary>
118
+
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
119
+
{{ range $items }}
120
+
{{ $repoOwner := index $handleMap .Metadata.Repo.Did }}
121
+
{{ $repoName := .Metadata.Repo.Name }}
122
+
{{ $repoUrl := printf "%s/%s" $repoOwner $repoName }}
123
+
124
+
<div class="flex gap-2 text-gray-600 dark:text-gray-300">
125
+
{{ if .Open }}
126
+
<span class="text-green-600 dark:text-green-500">
127
+
{{ i "circle-dot" "w-4 h-4" }}
128
+
</span>
129
+
{{ else }}
130
+
<span class="text-gray-500 dark:text-gray-400">
131
+
{{ i "ban" "w-4 h-4" }}
132
+
</span>
54
133
{{ end }}
55
-
<div class="{{ $textColor }} p-1">
56
-
{{ i $icon "w-5 h-5" }}
134
+
<div class="flex-none min-w-8 text-right">
135
+
<span class="text-gray-500 dark:text-gray-400">#{{ .IssueId }}</span>
57
136
</div>
58
-
<div>
59
-
<p class="text-gray-600 dark:text-gray-300">
60
-
<a href="/{{ index $.DidHandleMap .Repo.Did }}/{{ .Repo.Name }}/pulls/{{ .Pull.PullId }}" class="no-underline hover:underline">{{ .Pull.Title }} <span class="text-gray-500 dark:text-gray-400">#{{ .Pull.PullId }}</span></a>
137
+
<div class="break-words max-w-full">
138
+
<a href="/{{$repoUrl}}/issues/{{ .IssueId }}" class="no-underline hover:underline">
139
+
{{ .Title -}}
140
+
</a>
61
141
on
62
-
<a href="/{{ index $.DidHandleMap .Repo.Did }}/{{ .Repo.Name }}" class="no-underline hover:underline">
63
-
{{ index $.DidHandleMap .Repo.Did }}<span class="select-none">/</span>{{ .Repo.Name }}</a>
64
-
<time class="text-gray-700 dark:text-gray-400 text-xs ml-2">{{ .Pull.Created | shortTimeFmt }}</time>
65
-
</p>
142
+
<a href="/{{$repoUrl}}" class="no-underline hover:underline whitespace-nowrap">
143
+
{{$repoUrl}}
144
+
</a>
66
145
</div>
67
146
</div>
68
-
{{ else if eq .Type "repo" }}
69
-
<div class="px-6 py-2 bg-white dark:bg-gray-800 rounded drop-shadow-sm w-fit flex items-center gap-3">
70
-
{{ if .Source }}
71
-
<div class="text-gray-800 dark:text-gray-400 p-1">
72
-
{{ i "git-fork" "w-5 h-5" }}
73
-
</div>
147
+
{{ end }}
148
+
</div>
149
+
</details>
150
+
{{ end }}
151
+
{{ end }}
152
+
153
+
{{ define "pullEvents" }}
154
+
{{ $i := index . 0 }}
155
+
{{ $items := $i.Items }}
156
+
{{ $stats := $i.Stats }}
157
+
{{ $handleMap := index . 1 }}
158
+
{{ if gt (len $items) 0 }}
159
+
<details>
160
+
<summary class="list-none cursor-pointer hover:text-gray-500 hover:dark:text-gray-400">
161
+
<div class="flex flex-wrap items-center gap-2">
162
+
{{ i "git-pull-request" "w-4 h-4" }}
163
+
164
+
<div>
165
+
created {{ len $items }} {{if eq (len $items) 1 }}pull request{{else}}pull requests{{end}}
166
+
</div>
167
+
168
+
{{ if gt $stats.Open 0 }}
169
+
<span class="px-2 py-1/2 text-sm rounded text-white bg-green-600 dark:bg-green-700">
170
+
{{$stats.Open}} open
171
+
</span>
172
+
{{ end }}
173
+
174
+
{{ if gt $stats.Merged 0 }}
175
+
<span class="px-2 py-1/2 text-sm rounded text-white bg-purple-600 dark:bg-purple-700">
176
+
{{$stats.Merged}} merged
177
+
</span>
178
+
{{ end }}
179
+
180
+
181
+
{{ if gt $stats.Closed 0 }}
182
+
<span class="px-2 py-1/2 text-sm rounded text-black dark:text-white bg-gray-50 dark:bg-gray-700 ">
183
+
{{$stats.Closed}} closed
184
+
</span>
185
+
{{ end }}
186
+
187
+
</div>
188
+
</summary>
189
+
<div class="py-2 text-sm flex flex-col gap-3 mb-2">
190
+
{{ range $items }}
191
+
{{ $repoOwner := index $handleMap .Repo.Did }}
192
+
{{ $repoName := .Repo.Name }}
193
+
{{ $repoUrl := printf "%s/%s" $repoOwner $repoName }}
194
+
195
+
<div class="flex gap-2 text-gray-600 dark:text-gray-300">
196
+
{{ if .State.IsOpen }}
197
+
<span class="text-green-600 dark:text-green-500">
198
+
{{ i "git-pull-request" "w-4 h-4" }}
199
+
</span>
200
+
{{ else if .State.IsMerged }}
201
+
<span class="text-purple-600 dark:text-purple-500">
202
+
{{ i "git-merge" "w-4 h-4" }}
203
+
</span>
74
204
{{ else }}
75
-
<div class="text-gray-800 dark:text-gray-400 p-1">
76
-
{{ i "book-plus" "w-5 h-5" }}
205
+
<span class="text-gray-600 dark:text-gray-300">
206
+
{{ i "git-pull-request-closed" "w-4 h-4" }}
207
+
</span>
208
+
{{ end }}
209
+
<div class="flex-none min-w-8 text-right">
210
+
<span class="text-gray-500 dark:text-gray-400">#{{ .PullId }}</span>
77
211
</div>
78
-
{{ end }}
79
-
<div>
80
-
<p class="text-gray-600 dark:text-gray-300">
81
-
82
-
{{ if .Source }}
83
-
forked
84
-
<a href="/{{ index $.DidHandleMap .Source.Did }}/{{ .Source.Name }}" class="no-underline hover:underline">
85
-
{{ index $.DidHandleMap .Source.Did }}/{{ .Source.Name }}
86
-
</a>
87
-
to
88
-
<a href="/{{ didOrHandle $.UserHandle $.UserDid }}/{{ .Repo.Name }}" class="no-underline hover:underline">{{ .Repo.Name }}</a>
89
-
{{ else }}
90
-
created
91
-
<a href="/{{ index $.DidHandleMap .Repo.Did }}/{{ .Repo.Name }}" class="no-underline hover:underline">{{ .Repo.Name }}</a>
92
-
{{ end }}
93
-
<time class="text-gray-700 dark:text-gray-400 text-xs ml-2">{{ .Repo.Created | shortTimeFmt }}</time>
94
-
</p>
212
+
<div class="break-words max-w-full">
213
+
<a href="/{{$repoUrl}}/pulls/{{ .PullId }}" class="no-underline hover:underline">
214
+
{{ .Title -}}
215
+
</a>
216
+
on
217
+
<a href="/{{$repoUrl}}" class="no-underline hover:underline whitespace-nowrap">
218
+
{{$repoUrl}}
219
+
</a>
95
220
</div>
96
221
</div>
97
-
{{ end }}
98
-
{{ else }}
99
-
<p class="px-6 dark:text-white">This user does not have any activity yet.</p>
100
222
{{ end }}
101
223
</div>
224
+
</details>
225
+
{{ end }}
102
226
{{ end }}
103
227
104
228
{{ define "profileCard" }}
+11
-5
appview/state/profile.go
+11
-5
appview/state/profile.go
···
43
43
for _, r := range collaboratingRepos {
44
44
didsToResolve = append(didsToResolve, r.Did)
45
45
}
46
-
for _, evt := range timeline {
47
-
if evt.Repo != nil {
48
-
if evt.Repo.Source != "" {
49
-
didsToResolve = append(didsToResolve, evt.Source.Did)
46
+
for _, byMonth := range timeline.ByMonth {
47
+
for _, pe := range byMonth.PullEvents.Items {
48
+
didsToResolve = append(didsToResolve, pe.Repo.Did)
49
+
}
50
+
for _, ie := range byMonth.IssueEvents.Items {
51
+
didsToResolve = append(didsToResolve, ie.Metadata.Repo.Did)
52
+
}
53
+
for _, re := range byMonth.RepoEvents {
54
+
didsToResolve = append(didsToResolve, re.Repo.Did)
55
+
if re.Source != nil {
56
+
didsToResolve = append(didsToResolve, re.Source.Did)
50
57
}
51
58
}
52
-
didsToResolve = append(didsToResolve, evt.Repo.Did)
53
59
}
54
60
55
61
resolvedIds := s.resolver.ResolveIdents(r.Context(), didsToResolve)
+1
-1
go.mod
+1
-1
go.mod
···
26
26
github.com/sethvargo/go-envconfig v1.1.0
27
27
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e
28
28
github.com/yuin/goldmark v1.4.13
29
-
golang.org/x/crypto v0.36.0
30
29
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028
31
30
)
32
31
···
107
106
go.uber.org/atomic v1.11.0 // indirect
108
107
go.uber.org/multierr v1.11.0 // indirect
109
108
go.uber.org/zap v1.26.0 // indirect
109
+
golang.org/x/crypto v0.36.0 // indirect
110
110
golang.org/x/net v0.37.0 // indirect
111
111
golang.org/x/sys v0.31.0 // indirect
112
112
golang.org/x/time v0.5.0 // indirect