votes: show votes when hovering over post score.

Make it so you can hover over a post's score to see the list of public
upvotes. Also show the upvote count, the downvote count, and the upvote
ratio.
This commit is contained in:
evazion
2021-11-18 01:05:24 -06:00
parent a9997d0d2b
commit 5585d1f7d6
20 changed files with 262 additions and 58 deletions

View File

@@ -7,7 +7,9 @@
<% end %>
<% end %>
<span class="post-score"><%= post.score %></span>
<span class="post-score">
<%= link_to post.score, post_votes_path(search: { post_id: post.id }, variant: :compact) %>
</span>
<% if can_vote? %>
<% if downvoted? %>

View File

@@ -6,5 +6,11 @@
text-align: center;
min-width: 1.25em;
white-space: nowrap;
vertical-align: middle;
a {
color: var(--text-color);
&:hover { text-decoration: underline; }
}
}
}

View File

@@ -0,0 +1,34 @@
# frozen_string_literal: true
# This component represents the tooltip that displays when you hover over a post's score.
class PostVotesTooltipComponent < ApplicationComponent
attr_reader :post, :current_user
delegate :upvote_icon, :downvote_icon, to: :helpers
def initialize(post:, current_user:)
super
@post = post
@current_user = current_user
end
def votes
@post.votes.includes(:user).order(id: :desc)
end
def vote_icon(vote)
vote.is_positive? ? upvote_icon : downvote_icon
end
def upvote_ratio
return nil if votes.length == 0
sprintf("(%.1f%%)", 100.0 * votes.select(&:is_positive?).length / votes.length)
end
def voter_name(vote)
if policy(vote).can_see_voter?
link_to_user(vote.user, classes: "align-middle")
else
tag.i("hidden", class: "align-middle")
end
end
end

View File

@@ -0,0 +1,13 @@
<div class="post-votes-tooltip thin-scrollbar">
<div class="text-center text-muted">
+<%= post.up_score %> / <%= post.down_score %> <%= upvote_ratio %>
</div>
<div class="post-voters">
<% votes.each do |vote| %>
<div class="post-voter truncate">
<%= vote_icon(vote) %> <%= voter_name(vote) %>
</div>
<% end %>
</div>
</div>

View File

@@ -0,0 +1,65 @@
import Utility from "../../javascript/src/javascripts/utility.js";
import { delegate, hideAll } from 'tippy.js';
import 'tippy.js/dist/tippy.css';
class PostVotesTooltipComponent {
// Trigger on the post score link; see PostVotesComponent.
static TARGET_SELECTOR = "span.post-votes span.post-score a";
static SHOW_DELAY = 125;
static HIDE_DELAY = 125;
static DURATION = 250;
static instance = null;
static initialize() {
if ($(PostVotesTooltipComponent.TARGET_SELECTOR).length === 0) {
return;
}
PostVotesTooltipComponent.instance = delegate("body", {
allowHTML: true,
appendTo: document.querySelector("#post-votes-tooltips"),
delay: [PostVotesTooltipComponent.SHOW_DELAY, PostVotesTooltipComponent.HIDE_DELAY],
duration: PostVotesTooltipComponent.DURATION,
interactive: true,
maxWidth: "none",
target: PostVotesTooltipComponent.TARGET_SELECTOR,
theme: "common-tooltip",
touch: false,
onShow: PostVotesTooltipComponent.onShow,
onHide: PostVotesTooltipComponent.onHide,
});
}
static async onShow(instance) {
let $target = $(instance.reference);
let $tooltip = $(instance.popper);
let postId = $target.parents("[data-id]").data("id");
hideAll({ exclude: instance });
try {
$tooltip.addClass("tooltip-loading");
instance._request = $.get(`/post_votes?search[post_id]=${postId}`, { variant: "tooltip" });
let html = await instance._request;
instance.setContent(html);
$tooltip.removeClass("tooltip-loading");
} catch (error) {
if (error.status !== 0 && error.statusText !== "abort") {
Utility.error(`Error displaying votes for post #${postId} (error: ${error.status} ${error.statusText})`);
}
}
}
static async onHide(instance) {
if (instance._request?.state() === "pending") {
instance._request.abort();
}
}
}
$(document).ready(PostVotesTooltipComponent.initialize);
export default PostVotesTooltipComponent;

View File

@@ -0,0 +1,16 @@
.post-votes-tooltip {
font-size: var(--text-xs);
max-height: 240px;
.upvote-icon {
color: var(--post-upvote-color);
}
.downvote-icon {
color: var(--post-downvote-color);
}
.post-voter {
max-width: 160px;
}
}