Fix #4931: Add popup voter list for comments.
Show the comment's upvote and downvote count when you hover over a comment's score. For mods, show the list of voters as well.
This commit is contained in:
@@ -53,13 +53,13 @@
|
|||||||
<%= link_to upvote_icon, comment_comment_votes_path(comment_id: comment.id, score: "1"), class: "comment-upvote-link inactive-link", method: :post, remote: true %>
|
<%= link_to upvote_icon, comment_comment_votes_path(comment_id: comment.id, score: "1"), class: "comment-upvote-link inactive-link", method: :post, remote: true %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if policy(CommentVote).can_see_votes? %>
|
<span class="comment-score">
|
||||||
<%= link_to comment_votes_path(search: { comment_id: comment.id }, variant: "compact"), class: "inactive-link" do %>
|
<% if policy(CommentVote).can_see_votes? %>
|
||||||
<span class="comment-score"><%= comment.score %></span>
|
<%= link_to comment.score, comment_votes_path(search: { comment_id: comment.id }, variant: "compact"), class: "inactive-link" %>
|
||||||
|
<% else %>
|
||||||
|
<%= comment.score %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
</span>
|
||||||
<span class="comment-score"><%= comment.score %></span>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<% if current_user.is_anonymous? %>
|
<% if current_user.is_anonymous? %>
|
||||||
<%= link_to downvote_icon, login_path(url: request.fullpath), class: "comment-downvote-link inactive-link" %>
|
<%= link_to downvote_icon, login_path(url: request.fullpath), class: "comment-downvote-link inactive-link" %>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ article.comment {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
min-width: 1.25em;
|
min-width: 1.25em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
|||||||
32
app/components/comment_votes_tooltip_component.rb
Normal file
32
app/components/comment_votes_tooltip_component.rb
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# This component represents the tooltip that displays when you hover over a comment's score.
|
||||||
|
class CommentVotesTooltipComponent < ApplicationComponent
|
||||||
|
attr_reader :comment, :current_user
|
||||||
|
delegate :upvote_icon, :downvote_icon, to: :helpers
|
||||||
|
|
||||||
|
def initialize(comment:, current_user:)
|
||||||
|
super
|
||||||
|
@comment = comment
|
||||||
|
@current_user = current_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def votes
|
||||||
|
comment.votes.active.includes(:user).order(id: :desc)
|
||||||
|
end
|
||||||
|
|
||||||
|
def upvote_count
|
||||||
|
votes.select(&:is_positive?).length
|
||||||
|
end
|
||||||
|
|
||||||
|
def downvote_count
|
||||||
|
votes.select(&:is_negative?).length
|
||||||
|
end
|
||||||
|
|
||||||
|
def upvote_ratio
|
||||||
|
return nil if votes.length == 0
|
||||||
|
sprintf("(%.1f%%)", 100.0 * upvote_count / votes.length)
|
||||||
|
end
|
||||||
|
|
||||||
|
def vote_icon(vote)
|
||||||
|
vote.is_positive? ? upvote_icon : downvote_icon
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<div class="comment-votes-tooltip thin-scrollbar text-xs">
|
||||||
|
<div class="text-center text-muted whitespace-nowrap">
|
||||||
|
+<%= upvote_count %> / -<%= downvote_count %> <%= upvote_ratio %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if policy(CommentVote).can_see_votes? %>
|
||||||
|
<div class="comment-voters">
|
||||||
|
<% votes.each do |vote| %>
|
||||||
|
<div class="comment-voter truncate">
|
||||||
|
<%= vote_icon(vote) %> <%= link_to_user(vote.user, classes: "align-middle") %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
@@ -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 CommentVotesTooltipComponent {
|
||||||
|
// Trigger on the comment score link; see CommentComponent.
|
||||||
|
static TARGET_SELECTOR = "span.comment-score";
|
||||||
|
static SHOW_DELAY = 125;
|
||||||
|
static HIDE_DELAY = 125;
|
||||||
|
static DURATION = 250;
|
||||||
|
static instance = null;
|
||||||
|
|
||||||
|
static initialize() {
|
||||||
|
if ($(CommentVotesTooltipComponent.TARGET_SELECTOR).length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommentVotesTooltipComponent.instance = delegate("body", {
|
||||||
|
allowHTML: true,
|
||||||
|
appendTo: document.querySelector("#comment-votes-tooltips"),
|
||||||
|
delay: [CommentVotesTooltipComponent.SHOW_DELAY, CommentVotesTooltipComponent.HIDE_DELAY],
|
||||||
|
duration: CommentVotesTooltipComponent.DURATION,
|
||||||
|
interactive: true,
|
||||||
|
maxWidth: "none",
|
||||||
|
target: CommentVotesTooltipComponent.TARGET_SELECTOR,
|
||||||
|
theme: "common-tooltip",
|
||||||
|
touch: false,
|
||||||
|
|
||||||
|
onShow: CommentVotesTooltipComponent.onShow,
|
||||||
|
onHide: CommentVotesTooltipComponent.onHide,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async onShow(instance) {
|
||||||
|
let $target = $(instance.reference);
|
||||||
|
let $tooltip = $(instance.popper);
|
||||||
|
let commentId = $target.parents("[data-id]").data("id");
|
||||||
|
|
||||||
|
hideAll({ exclude: instance });
|
||||||
|
|
||||||
|
try {
|
||||||
|
$tooltip.addClass("tooltip-loading");
|
||||||
|
|
||||||
|
instance._request = $.get(`/comments/${commentId}/votes`, { 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 comment #${commentId} (error: ${error.status} ${error.statusText})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async onHide(instance) {
|
||||||
|
if (instance._request?.state() === "pending") {
|
||||||
|
instance._request.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(CommentVotesTooltipComponent.initialize);
|
||||||
|
|
||||||
|
export default CommentVotesTooltipComponent;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
.comment-votes-tooltip {
|
||||||
|
max-height: 240px;
|
||||||
|
|
||||||
|
.upvote-icon {
|
||||||
|
color: var(--post-upvote-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.downvote-icon {
|
||||||
|
color: var(--post-downvote-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-voter {
|
||||||
|
max-width: 160px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="post-votes-tooltip thin-scrollbar">
|
<div class="post-votes-tooltip thin-scrollbar text-xs">
|
||||||
<div class="text-center text-muted whitespace-nowrap">
|
<div class="text-center text-muted whitespace-nowrap">
|
||||||
+<%= post.up_score %> / -<%= post.down_score.abs %> <%= upvote_ratio %>
|
+<%= post.up_score %> / -<%= post.down_score.abs %> <%= upvote_ratio %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
.post-votes-tooltip {
|
.post-votes-tooltip {
|
||||||
font-size: var(--text-xs);
|
|
||||||
max-height: 240px;
|
max-height: 240px;
|
||||||
|
|
||||||
.upvote-icon {
|
.upvote-icon {
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ class CommentVotesController < ApplicationController
|
|||||||
@comment_votes = authorize CommentVote.visible(CurrentUser.user).paginated_search(params, count_pages: true)
|
@comment_votes = authorize CommentVote.visible(CurrentUser.user).paginated_search(params, count_pages: true)
|
||||||
@comment_votes = @comment_votes.includes(:user, comment: [:creator, { post: [:uploader, :media_asset] }]) if request.format.html?
|
@comment_votes = @comment_votes.includes(:user, comment: [:creator, { post: [:uploader, :media_asset] }]) if request.format.html?
|
||||||
|
|
||||||
|
comment_id = params[:comment_id] || params[:search][:comment_id]
|
||||||
|
@comment = Comment.find(comment_id) if comment_id
|
||||||
|
|
||||||
respond_with(@comment_votes)
|
respond_with(@comment_votes)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ module ComponentsHelper
|
|||||||
render PostVotesTooltipComponent.new(post: post, **options)
|
render PostVotesTooltipComponent.new(post: post, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_comment_votes_tooltip(comment, **options)
|
||||||
|
render CommentVotesTooltipComponent.new(comment: comment, **options)
|
||||||
|
end
|
||||||
|
|
||||||
def render_favorites_tooltip(post, **options)
|
def render_favorites_tooltip(post, **options)
|
||||||
render FavoritesTooltipComponent.new(post: post, **options)
|
render FavoritesTooltipComponent.new(post: post, **options)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ importAll(require.context('../../components', true, /\.s?css(?:\.erb)?$/));
|
|||||||
import Autocomplete from "../src/javascripts/autocomplete.js";
|
import Autocomplete from "../src/javascripts/autocomplete.js";
|
||||||
import Blacklist from "../src/javascripts/blacklists.js";
|
import Blacklist from "../src/javascripts/blacklists.js";
|
||||||
import CommentComponent from "../../components/comment_component/comment_component.js";
|
import CommentComponent from "../../components/comment_component/comment_component.js";
|
||||||
|
import CommentVotesTooltipComponent from "../../components/comment_votes_tooltip_component/comment_votes_tooltip_component.js";
|
||||||
import CurrentUser from "../src/javascripts/current_user.js";
|
import CurrentUser from "../src/javascripts/current_user.js";
|
||||||
import Dtext from "../src/javascripts/dtext.js";
|
import Dtext from "../src/javascripts/dtext.js";
|
||||||
import FavoritesTooltipComponent from "../../components/favorites_tooltip_component/favorites_tooltip_component.js";
|
import FavoritesTooltipComponent from "../../components/favorites_tooltip_component/favorites_tooltip_component.js";
|
||||||
@@ -58,6 +59,7 @@ let Danbooru = {};
|
|||||||
Danbooru.Autocomplete = Autocomplete;
|
Danbooru.Autocomplete = Autocomplete;
|
||||||
Danbooru.Blacklist = Blacklist;
|
Danbooru.Blacklist = Blacklist;
|
||||||
Danbooru.CommentComponent = CommentComponent;
|
Danbooru.CommentComponent = CommentComponent;
|
||||||
|
Danbooru.CommentVotesTooltipComponent = CommentVotesTooltipComponent;
|
||||||
Danbooru.CurrentUser = CurrentUser;
|
Danbooru.CurrentUser = CurrentUser;
|
||||||
Danbooru.Dtext = Dtext;
|
Danbooru.Dtext = Dtext;
|
||||||
Danbooru.FavoritesTooltipComponent = FavoritesTooltipComponent;
|
Danbooru.FavoritesTooltipComponent = FavoritesTooltipComponent;
|
||||||
|
|||||||
1
app/views/comment_votes/index.html+tooltip.erb
Normal file
1
app/views/comment_votes/index.html+tooltip.erb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<%= render_comment_votes_tooltip(@comment, current_user: CurrentUser.user) %>
|
||||||
@@ -101,6 +101,7 @@
|
|||||||
<div id="tooltips">
|
<div id="tooltips">
|
||||||
<div id="post-tooltips"></div>
|
<div id="post-tooltips"></div>
|
||||||
<div id="user-tooltips"></div>
|
<div id="user-tooltips"></div>
|
||||||
|
<div id="comment-votes-tooltips"></div>
|
||||||
<div id="post-votes-tooltips"></div>
|
<div id="post-votes-tooltips"></div>
|
||||||
<div id="post-favorites-tooltips"></div>
|
<div id="post-favorites-tooltips"></div>
|
||||||
<div id="popup-menus"></div>
|
<div id="popup-menus"></div>
|
||||||
|
|||||||
@@ -91,7 +91,9 @@ Rails.application.routes.draw do
|
|||||||
end
|
end
|
||||||
resources :comment_votes, only: [:index, :show, :destroy]
|
resources :comment_votes, only: [:index, :show, :destroy]
|
||||||
resources :comments do
|
resources :comments do
|
||||||
resource :votes, controller: "comment_votes", only: [:create, :destroy], as: "comment_votes"
|
resource :votes, controller: "comment_votes", only: [:create, :destroy], as: "comment_votes" do
|
||||||
|
get "/", action: :index
|
||||||
|
end
|
||||||
collection do
|
collection do
|
||||||
get :search
|
get :search
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ class CommentVotesControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_response :success
|
assert_response :success
|
||||||
end
|
end
|
||||||
|
|
||||||
|
should "render for a tooltip" do
|
||||||
|
get comment_votes_path(comment_id: @comment.id, variant: "tooltip")
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
should respond_to_search({}).with { [] }
|
should respond_to_search({}).with { [] }
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -35,6 +40,11 @@ class CommentVotesControllerTest < ActionDispatch::IntegrationTest
|
|||||||
CurrentUser.user = create(:mod_user)
|
CurrentUser.user = create(:mod_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
should "render for a tooltip" do
|
||||||
|
get comment_votes_path(comment_id: @comment.id, variant: "tooltip")
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
should respond_to_search({}).with { [@unrelated_vote, @negative_vote, @vote] }
|
should respond_to_search({}).with { [@unrelated_vote, @negative_vote, @vote] }
|
||||||
should respond_to_search(score: -1).with { @negative_vote }
|
should respond_to_search(score: -1).with { @negative_vote }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user