Monorepo for Tangled — https://tangled.org
fork

Configure Feed

Select the types of activity you want to include in your feed.

group timeline events in profile

timeline events in profiles are grouped by month (each month is a card) and by type (repos, issues and pulls).

authored by oppi.li and committed by

Tangled d50fdb31 d8a12dae

+422 -162
+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
··· 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
··· 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
··· 77 77 where 78 78 r.did = ? 79 79 group by 80 - r.at_uri`, did) 80 + r.at_uri 81 + order by r.created desc`, 82 + did) 81 83 if err != nil { 82 84 return nil, err 83 85 }
+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
··· 29 29 > 30 30 / 31 31 {{ else }} 32 - <span class="text-bold text-gray-600 dark:text-gray-300" 32 + <span class="text-bold text-black dark:text-white" 33 33 >{{ index . 0 }}</span 34 34 > 35 35 {{ end }}
+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
··· 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
··· 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
··· 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