Skip to content

Commit 4c8ed2d

Browse files
authored
Add patch diff highlighting and line stats #2 (#64)
1 parent abc4bbb commit 4c8ed2d

File tree

7 files changed

+174
-7
lines changed

7 files changed

+174
-7
lines changed

app/assets/stylesheets/components/messages.css

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,35 @@
252252
color: var(--color-text-secondary);
253253
font-weight: var(--font-weight-semibold);
254254
}
255+
256+
& code.diff-highlighted {
257+
display: block;
258+
}
259+
260+
& .diff-line {
261+
display: block;
262+
padding: 0 var(--spacing-2);
263+
margin: 0 calc(-1 * var(--spacing-2));
264+
}
265+
266+
& .diff-line-add {
267+
background: var(--color-success-soft);
268+
color: var(--color-success);
269+
}
270+
271+
& .diff-line-del {
272+
background: var(--color-danger-soft);
273+
color: var(--color-danger);
274+
}
275+
276+
& .diff-line-hunk {
277+
background: var(--color-info-soft);
278+
color: var(--color-info);
279+
}
280+
281+
& .diff-line-header {
282+
color: var(--color-text-muted);
283+
}
255284
}
256285

257286
.attachment {
@@ -286,6 +315,24 @@ summary.attachment-info {
286315
text-decoration: none;
287316
}
288317

318+
.patchset-stats {
319+
display: inline-flex;
320+
align-items: center;
321+
gap: var(--spacing-2);
322+
margin-left: var(--spacing-2);
323+
font-size: var(--font-size-xs);
324+
font-weight: var(--font-weight-semibold);
325+
white-space: nowrap;
326+
}
327+
328+
.patchset-added {
329+
color: var(--color-success);
330+
}
331+
332+
.patchset-removed {
333+
color: var(--color-danger);
334+
}
335+
289336
.attachment-download:hover {
290337
color: var(--color-text-link-hover);
291338
text-decoration: underline;

app/assets/stylesheets/components/sidebar.css

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,31 @@
176176
margin: var(--spacing-3) 0;
177177
}
178178

179+
.sidebar .download-patchset {
180+
display: flex;
181+
align-items: center;
182+
justify-content: space-between;
183+
gap: var(--spacing-3);
184+
margin-bottom: var(--spacing-4);
185+
}
186+
187+
.sidebar .patchset-stats {
188+
display: inline-flex;
189+
align-items: center;
190+
gap: var(--spacing-2);
191+
font-size: var(--font-size-sm);
192+
font-weight: var(--font-weight-semibold);
193+
white-space: nowrap;
194+
}
195+
196+
.sidebar .patchset-added {
197+
color: var(--color-success);
198+
}
199+
200+
.sidebar .patchset-removed {
201+
color: var(--color-danger);
202+
}
203+
179204
.sidebar .attachments-list li + li {
180205
margin-top: var(--spacing-2);
181206
}

app/controllers/topics_controller.rb

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,19 @@ def show
6565
end
6666

6767
@has_patches = @messages.any? { |msg| msg.attachments.any?(&:patch_extension?) }
68+
if @has_patches
69+
latest_message = latest_patchset_message
70+
if latest_message
71+
patch_attachments = latest_message.attachments.select(&:patch?)
72+
totals = patch_attachments.reduce({ added: 0, removed: 0 }) do |acc, attachment|
73+
stats = attachment.diff_line_stats
74+
acc[:added] += stats[:added]
75+
acc[:removed] += stats[:removed]
76+
acc
77+
end
78+
@latest_patchset_stats = totals
79+
end
80+
end
6881
end
6982

7083
def aware
@@ -137,11 +150,7 @@ def unstar
137150
end
138151

139152
def latest_patchset
140-
latest_message = @topic.messages
141-
.where(id: Attachment.where(message_id: @topic.messages.select(:id))
142-
.select(:message_id))
143-
.order(created_at: :desc)
144-
.find { |msg| msg.attachments.any?(&:patch_extension?) }
153+
latest_message = latest_patchset_message
145154

146155
return head :not_found unless latest_message
147156

@@ -303,6 +312,14 @@ def user_state_frame
303312

304313
private
305314

315+
def latest_patchset_message
316+
@latest_patchset_message ||= @topic.messages
317+
.where(id: Attachment.where(message_id: @topic.messages.select(:id))
318+
.select(:message_id))
319+
.order(created_at: :desc)
320+
.find { |msg| msg.attachments.any?(&:patch_extension?) }
321+
end
322+
306323
def set_topic
307324
@topic = Topic.includes(
308325
:creator,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Controller } from "@hotwired/stimulus"
2+
3+
export default class extends Controller {
4+
connect() {
5+
if (this.element.dataset.diffHighlighted === "true") return
6+
7+
const text = this.element.textContent
8+
if (!text) return
9+
10+
const fragment = document.createDocumentFragment()
11+
const lines = text.split("\n")
12+
13+
lines.forEach((line) => {
14+
const span = document.createElement("span")
15+
span.classList.add("diff-line")
16+
17+
if (line.startsWith("+") && !line.startsWith("+++")) {
18+
span.classList.add("diff-line-add")
19+
} else if (line.startsWith("-") && !line.startsWith("---")) {
20+
span.classList.add("diff-line-del")
21+
} else if (line.startsWith("@@")) {
22+
span.classList.add("diff-line-hunk")
23+
} else if (
24+
line.startsWith("diff ") ||
25+
line.startsWith("index ") ||
26+
line.startsWith("---") ||
27+
line.startsWith("+++")
28+
) {
29+
span.classList.add("diff-line-header")
30+
}
31+
32+
span.textContent = line.length ? line : " "
33+
fragment.appendChild(span)
34+
})
35+
36+
this.element.textContent = ""
37+
this.element.appendChild(fragment)
38+
this.element.dataset.diffHighlighted = "true"
39+
this.element.classList.add("diff-highlighted")
40+
}
41+
}

app/models/attachment.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,34 @@ def decoded_body_utf8
2828
raw.encode("UTF-8", invalid: :replace, undef: :replace, replace: "\uFFFD")
2929
end
3030

31+
def diff_line_stats
32+
return { added: 0, removed: 0 } unless patch?
33+
return @diff_line_stats if defined?(@diff_line_stats)
34+
35+
text = decoded_body_utf8
36+
return @diff_line_stats = { added: 0, removed: 0 } unless text.present?
37+
38+
added = 0
39+
removed = 0
40+
41+
text.each_line do |line|
42+
if line.start_with?("+") && !line.start_with?("+++")
43+
added += 1
44+
elsif line.start_with?("-") && !line.start_with?("---")
45+
removed += 1
46+
elsif line.start_with?("> ")
47+
added += 1
48+
elsif line.start_with?("< ")
49+
removed += 1
50+
elsif line.start_with?("! ")
51+
added += 1
52+
removed += 1
53+
end
54+
end
55+
56+
@diff_line_stats = { added: added, removed: removed }
57+
end
58+
3159
private
3260

3361
def patch_content?

app/views/topics/_message.html.slim

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,13 @@
7272
span.filename = attachment.file_name
7373
span.content-type = attachment.content_type if attachment.content_type
7474
= link_to "Download", attachment_path(attachment), class: "attachment-download", download: attachment.file_name, data: { turbo: false }
75+
- stats = attachment.diff_line_stats
76+
- if stats[:added].positive? || stats[:removed].positive?
77+
span.patchset-stats aria-label="Patch line changes" title="Lines added and removed by this patch"
78+
span.patchset-added +#{stats[:added]}
79+
span.patchset-removed -#{stats[:removed]}
7580
pre.attachment-content
76-
code.language-diff
81+
code.language-diff data-controller="diff-highlight"
7782
= attachment.decoded_body_utf8
7883
- else
7984
.attachment

app/views/topics/show.html.slim

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,14 @@
7575
summary.sidebar-heading Attachments
7676
.sidebar-section
7777
- if @has_patches
78-
.download-patchset style="margin-bottom: 1rem;"
78+
.download-patchset
7979
= link_to latest_patchset_topic_path(@topic), class: "button download-button", download: "topic-#{@topic.id}-patchset.tar.gz", data: { turbo: false } do
8080
i.fas.fa-download
8181
span Download Latest Patchset
82+
- if @latest_patchset_stats
83+
.patchset-stats aria-label="Patchset line changes" title="Lines added and removed by this patchset"
84+
span.patchset-added +#{@latest_patchset_stats[:added]}
85+
span.patchset-removed -#{@latest_patchset_stats[:removed]}
8286

8387
- if attachment_messages.any?
8488
ul.attachments-list

0 commit comments

Comments
 (0)