Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 39 additions & 15 deletions lib/termcourse/ui.rb
Original file line number Diff line number Diff line change
Expand Up @@ -694,10 +694,9 @@ def build_post_block(post, expanded, width)
cached = @post_block_cache[cache_key]
return cached if cached

liked = post_liked?(post)
liked_marker = ""
username = post["username"].to_s
heart = liked ? "❤️" : "🤍"
like_indicator = post_like_indicator(post)
username_text = theme_text("@#{username}", fg: "post_username")
header = "#{liked_marker}#{username_text}"

Expand All @@ -719,13 +718,13 @@ def build_post_block(post, expanded, width)
end

if expanded
header_line = pad_line(format_line(header, width, heart), width)
header_line = pad_line(format_line(header, width, like_indicator), width)
header_line = highlight(header_line)
lines = [header_line] + content_lines
else
preview = content_lines.first(3)
preview = [""] if preview.empty?
lines = [format_line(header, width, heart)] + preview
lines = [format_line(header, width, like_indicator)] + preview
end

cache_post_block(cache_key, lines)
Expand Down Expand Up @@ -1749,14 +1748,16 @@ def clamp_visible(text, max_width)
end

def ljust_visible(text, width)
pad = [width - display_width(text), 0].max
pad = [width - visible_length(text), 0].max
text + (" " * pad)
end

def display_width(text)
text.each_codepoint.sum do |cp|
if combining_codepoint?(cp) || zero_width_codepoint?(cp)
0
elsif narrow_like_heart_codepoint?(cp)
1
else
wide_codepoint?(cp) ? 2 : 1
end
Expand All @@ -1778,6 +1779,10 @@ def zero_width_codepoint?(cp)
(cp >= 0xE0100 && cp <= 0xE01EF) # supplemental variation selectors
end

def narrow_like_heart_codepoint?(cp)
cp == 0x2661 || cp == 0x2665
end

def take_by_display_width(text, max_width)
return ["", text] if max_width <= 0 || text.empty?

Expand Down Expand Up @@ -1808,14 +1813,14 @@ def wide_codepoint?(cp)
(cp >= 0x1F300 && cp <= 0x1FAFF)
end

def format_line(text, width, heart = nil)
heart_width = 2
heart = " " if heart.nil?
heart = clamp(heart, heart_width)
body_width = [width - heart_width - 1, 1].max
body = clamp(text.to_s, body_width).ljust(body_width)
line = "#{body} #{heart}"
line
def format_line(text, width, right = nil)
return ljust_visible(truncate_visible_with_ansi(text.to_s, width), width) if right.nil?

right = right.to_s
right_width = visible_length(right)
body_width = [width - right_width, 1].max
body = truncate_visible_with_ansi(text.to_s, body_width)
"#{ljust_visible(body, body_width)}#{right}"
end

def clamp(text, width)
Expand Down Expand Up @@ -2278,13 +2283,31 @@ def toggle_like(post)
end

def post_liked?(post)
like_action(post)&.fetch("acted", false) ? true : false
end

def post_like_count(post)
like_action(post)&.fetch("count", 0).to_i
end

def post_like_indicator(post)
count = post_like_count(post)
liked = post_liked?(post)
heart = colorize(liked ? "♥" : (count.positive? ? "♥" : "♡"), fg: parse_color(liked ? "red" : "white"))

return heart if count <= 1

"#{count} #{heart}"
end

def like_action(post)
summary = post["actions_summary"] || []
summary.any? { |action| action["id"] == 2 && action["acted"] }
summary.find { |action| action["id"].to_i == 2 }
end

def apply_local_like_state(post, liked)
summary = post["actions_summary"] ||= []
action = summary.find { |item| item["id"] == 2 }
action = like_action(post)
unless action
action = { "id" => 2, "count" => 0, "acted" => false }
summary << action
Expand Down Expand Up @@ -2732,6 +2755,7 @@ def post_block_cache_key(post, expanded, width)
raw.bytesize,
post["username"].to_s,
post_liked?(post),
post_like_count(post),
expanded,
width,
@images_enabled,
Expand Down
47 changes: 47 additions & 0 deletions test/ui_renderer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,53 @@ def test_initial_topic_selected_index_accepts_selected_post_number

assert_equal 1, selected
end

def test_format_line_places_like_control_at_right_edge
line = @ui.send(:format_line, "@sam", 10, "♡")

assert_equal "@sam ♡", line
assert_equal "♡", line[-1]
end

def test_format_line_places_like_control_at_right_edge_with_styled_left_text
line = @ui.send(:format_line, @ui.send(:theme_text, "@sam", fg: "post_username"), 10, "♡")
visible = @ui.send(:strip_all_ansi, line)

assert_equal "@sam ♡", visible
assert_equal "♡", visible[-1]
end

def test_post_like_indicator_shows_empty_heart_without_likes
indicator = @ui.send(:post_like_indicator, { "actions_summary" => [] })

assert_equal "♡", @ui.send(:strip_all_ansi, indicator)
end

def test_post_like_indicator_shows_solid_heart_for_one_other_like
post = { "actions_summary" => [{ "id" => 2, "count" => 1, "acted" => false }] }

indicator = @ui.send(:post_like_indicator, post)

assert_equal "♥", @ui.send(:strip_all_ansi, indicator)
end

def test_post_like_indicator_shows_count_for_multiple_likes
post = { "actions_summary" => [{ "id" => 2, "count" => 3, "acted" => false }] }

indicator = @ui.send(:post_like_indicator, post)

assert_equal "3 ♥", @ui.send(:strip_all_ansi, indicator)
end

def test_post_like_indicator_uses_red_heart_when_current_user_liked
post = { "actions_summary" => [{ "id" => 2, "count" => 2, "acted" => true }] }

indicator = @ui.send(:post_like_indicator, post)
red = @ui.send(:ansi_fg, @ui.send(:parse_color, "red"))

assert_equal "2 ♥", @ui.send(:strip_all_ansi, indicator)
assert_includes indicator, red
end
end

class UINotificationStateTest < Minitest::Test
Expand Down
Loading