Skip to content

Commit ea3a257

Browse files
committed
Detailed contributor list for the thread index
On hover now it shows the actual contributors, ordered by message count. This view reuses the cards from the thread view by extracting it to a partial. In the future we'll have to change this to something better, but that will be a separate commit.
1 parent 8556336 commit ea3a257

7 files changed

Lines changed: 136 additions & 20 deletions

File tree

app/assets/stylesheets/colors.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
--color-border-contributor: var(--color-secondary2-2);
4848

4949
--color-bg-icon: var(--color-secondary1-2);
50+
--color-bg-hoverbox: var(--color-secondary2-1);
5051
--color-bg-contributor: var(--color-secondary1-2);
5152
--color-bg-committer: var(--color-secondary1-1);
5253
}

app/assets/stylesheets/components/topics.css

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,26 @@
7373
padding: 3px;
7474
margin: 5px 0px;
7575
margin-right: 10px;
76+
position: relative;
77+
}
78+
79+
.topic-icon-badge {
80+
position: absolute;
81+
bottom: -6px;
82+
right: -6px;
83+
min-width: 18px;
84+
height: 18px;
85+
padding: 2px 4px;
86+
border-radius: 999px;
87+
background: var(--color-bg-icon);
88+
color: #000;
89+
font-size: var(--font-size-xs);
90+
font-weight: var(--font-weight-bold);
91+
line-height: 1;
92+
display: inline-flex;
93+
align-items: center;
94+
justify-content: center;
95+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
7696
}
7797

7898
.activity-core_team {
@@ -89,6 +109,31 @@
89109
}
90110
}
91111

112+
.topic-icon-hover {
113+
display: none;
114+
position: absolute;
115+
top: calc(100% + 6px);
116+
left: 0;
117+
z-index: 20;
118+
background: var(--color-bg-hoverbox);
119+
border-radius: var(--border-radius-md);
120+
padding: var(--spacing-3);
121+
box-shadow: var(--shadow-lg);
122+
min-width: 260px;
123+
}
124+
125+
.topic-icon.is-open .topic-icon-hover {
126+
display: block;
127+
}
128+
129+
.topic-icon-hover .participant-row {
130+
margin-bottom: var(--spacing-2);
131+
}
132+
133+
.topic-icon-hover .participant-row:last-child {
134+
margin-bottom: 0;
135+
}
136+
92137
.topic-meta {
93138
& span {
94139
padding-right: var(--spacing-2);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Controller } from "@hotwired/stimulus"
2+
3+
// Adds a small delay before hiding popovers so users can move the cursor into them.
4+
export default class extends Controller {
5+
static targets = ["popover"]
6+
static values = { delay: Number }
7+
8+
connect() {
9+
this.hideTimeout = null
10+
this.delay = this.delayValue || 150
11+
}
12+
13+
show() {
14+
this._clearTimeout()
15+
this.element.classList.add("is-open")
16+
}
17+
18+
scheduleHide() {
19+
this._clearTimeout()
20+
this.hideTimeout = setTimeout(() => {
21+
this.element.classList.remove("is-open")
22+
}, this.delay)
23+
}
24+
25+
_clearTimeout() {
26+
if (this.hideTimeout) {
27+
clearTimeout(this.hideTimeout)
28+
this.hideTimeout = null
29+
}
30+
}
31+
}

app/models/topic.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,30 @@ def has_committer_activity?
6666
end
6767
end
6868

69+
def contributor_participants
70+
@contributor_participants ||= begin
71+
contributor_alias_ids = Contributor.joins(:aliases).pluck('aliases.id').uniq
72+
return [] if contributor_alias_ids.empty?
73+
74+
stats = messages.where(sender_id: contributor_alias_ids)
75+
.group(:sender_id)
76+
.select('sender_id, COUNT(*) AS message_count, MAX(created_at) AS last_at')
77+
78+
alias_map = Alias.includes(:contributors).where(id: stats.map(&:sender_id)).index_by(&:id)
79+
80+
stats.map do |row|
81+
alias_record = alias_map[row.sender_id]
82+
next unless alias_record
83+
84+
{
85+
alias: alias_record,
86+
message_count: row.read_attribute(:message_count).to_i,
87+
last_at: row.read_attribute(:last_at)
88+
}
89+
end.compact.sort_by { |p| [-p[:message_count], p[:alias].name] }
90+
end
91+
end
92+
6993
def highest_contributor_activity
7094
return "core_team" if has_core_team_activity?
7195
return "committer" if has_committer_activity?
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
- alias_record = participant[:alias]
2+
- return unless alias_record
3+
- avatar_size = local_assigns[:avatar_size] || 40
4+
- message_count = participant[:message_count]
5+
- last_at = participant[:last_at]
6+
- tooltip_parts = []
7+
- tooltip_parts << "#{message_count} messages" if message_count
8+
- tooltip_parts << "last #{smart_time_display(last_at)}" if last_at
9+
- tooltip = local_assigns[:tooltip] || (tooltip_parts.any? ? tooltip_parts.join(", ") : alias_record.name)
10+
- avatar_classes = ["participant-avatar"]
11+
- avatar_classes << "is-core-team" if alias_record.core_team?
12+
- avatar_classes << "is-committer" if !alias_record.core_team? && alias_record.committer?
13+
- avatar_classes << "is-major-contributor" if !alias_record.core_team? && !alias_record.committer? && alias_record.major_contributor?
14+
- avatar_classes << "is-significant-contributor" if !alias_record.core_team? && !alias_record.committer? && !alias_record.major_contributor? && alias_record.significant_contributor?
15+
- avatar_classes << "is-past-contributor" if alias_record.contributor&.past_contributor?
16+
- row_classes = ["participant-row"]
17+
- row_classes << "is-core-team" if alias_record.core_team?
18+
- row_classes << "is-committer" if alias_record.committer?
19+
- row_classes << "is-major-contributor" if alias_record.major_contributor?
20+
- row_classes << "is-significant-contributor" if alias_record.significant_contributor?
21+
- row_classes << "is-past-contributor" if alias_record.contributor&.past_contributor?
22+
.participant-row class=row_classes.join(" ") title=tooltip
23+
= image_tag alias_record.gravatar_url(size: avatar_size), class: avatar_classes.join(" "), alt: alias_record.name, title: tooltip
24+
.participant-details
25+
.participant-name = alias_record.name

app/views/topics/_topics.html.slim

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@
22
tr.topic-row data-topic-id=topic.id data-last-message-id=topic.messages.maximum(:id)
33
td.topic-title data-label="Topic"
44
- if topic.has_contributor_activity?
5-
div class ="topic-icon activity-#{ topic.highest_contributor_activity }"
5+
- contributor_participants = topic.contributor_participants
6+
- contributor_count = contributor_participants.size
7+
div class ="topic-icon activity-#{ topic.highest_contributor_activity }" data-controller="hover-popover" data-hover-popover-delay-value="200" data-action="mouseenter->hover-popover#show mouseleave->hover-popover#scheduleHide"
68
i.fa-solid.fa-users-rays
9+
- if contributor_count.positive?
10+
span.topic-icon-badge = contributor_count
11+
- if contributor_participants.any?
12+
.topic-icon-hover data-hover-popover-target="popover" data-action="mouseenter->hover-popover#show mouseleave->hover-popover#scheduleHide"
13+
- contributor_participants.each do |participant|
14+
= render partial: "participant_row", locals: { participant: participant, avatar_size: 32 }
715
- if topic.attachments.exists?
816
.topic-icon
917
i.fa-solid.fa-paperclip

app/views/topics/show.html.slim

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,7 @@
88
.participants-list
99
- max_participants = 12
1010
- @participants.first(max_participants).each do |participant|
11-
- alias_record = participant[:alias]
12-
- badge_text = alias_record.contributor_badge
13-
- avatar_classes = ["participant-avatar"]
14-
- avatar_classes << "is-core-team" if alias_record.core_team?
15-
- avatar_classes << "is-committer" if !alias_record.core_team? && alias_record.committer?
16-
- avatar_classes << "is-major-contributor" if !alias_record.core_team? && !alias_record.committer? && alias_record.major_contributor?
17-
- avatar_classes << "is-significant-contributor" if !alias_record.core_team? && !alias_record.committer? && !alias_record.major_contributor? && alias_record.significant_contributor?
18-
- avatar_classes << "is-past-contributor" if alias_record.contributor&.past_contributor?
19-
- row_classes = ["participant-row"]
20-
- row_classes << "is-core-team" if alias_record.core_team?
21-
- row_classes << "is-committer" if alias_record.committer?
22-
- row_classes << "is-major-contributor" if alias_record.major_contributor?
23-
- row_classes << "is-significant-contributor" if alias_record.significant_contributor?
24-
- row_classes << "is-past-contributor" if alias_record.contributor&.past_contributor?
25-
- tooltip = "#{participant[:message_count]} messages, last #{smart_time_display(participant[:last_at])}"
26-
.participant-row class=row_classes.join(" ") title=tooltip
27-
= image_tag alias_record.gravatar_url(size: 40), class: avatar_classes.join(" "), alt: alias_record.name, title: tooltip
28-
.participant-details
29-
.participant-name = alias_record.name
11+
= render partial: "participant_row", locals: { participant: participant, avatar_size: 40 }
3012
- if @participants.size > max_participants
3113
.participant-more "+ #{(@participants.size - max_participants)} more"
3214
- else

0 commit comments

Comments
 (0)