From a45e6b5cfe79d6b72a4872fc21bd155947985490 Mon Sep 17 00:00:00 2001 From: evazion Date: Wed, 24 Nov 2021 22:13:32 -0600 Subject: [PATCH] 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. --- .../comment_component.html.erb | 12 ++-- .../comment_component/comment_component.scss | 1 + .../comment_votes_tooltip_component.rb | 32 +++++++++ .../comment_votes_tooltip_component.html.erb | 15 +++++ .../comment_votes_tooltip_component.js | 65 +++++++++++++++++++ .../comment_votes_tooltip_component.scss | 15 +++++ .../post_votes_tooltip_component.html.erb | 2 +- .../post_votes_tooltip_component.scss | 1 - app/controllers/comment_votes_controller.rb | 3 + app/helpers/components_helper.rb | 4 ++ app/javascript/packs/application.js | 2 + .../comment_votes/index.html+tooltip.erb | 1 + app/views/layouts/default.html.erb | 1 + config/routes.rb | 4 +- .../comment_votes_controller_test.rb | 10 +++ 15 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 app/components/comment_votes_tooltip_component.rb create mode 100644 app/components/comment_votes_tooltip_component/comment_votes_tooltip_component.html.erb create mode 100644 app/components/comment_votes_tooltip_component/comment_votes_tooltip_component.js create mode 100644 app/components/comment_votes_tooltip_component/comment_votes_tooltip_component.scss create mode 100644 app/views/comment_votes/index.html+tooltip.erb diff --git a/app/components/comment_component/comment_component.html.erb b/app/components/comment_component/comment_component.html.erb index 0bcd59987..63494df20 100644 --- a/app/components/comment_component/comment_component.html.erb +++ b/app/components/comment_component/comment_component.html.erb @@ -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 %> <% end %> - <% if policy(CommentVote).can_see_votes? %> - <%= link_to comment_votes_path(search: { comment_id: comment.id }, variant: "compact"), class: "inactive-link" do %> - <%= comment.score %> + + <% if policy(CommentVote).can_see_votes? %> + <%= link_to comment.score, comment_votes_path(search: { comment_id: comment.id }, variant: "compact"), class: "inactive-link" %> + <% else %> + <%= comment.score %> <% end %> - <% else %> - <%= comment.score %> - <% end %> + <% if current_user.is_anonymous? %> <%= link_to downvote_icon, login_path(url: request.fullpath), class: "comment-downvote-link inactive-link" %> diff --git a/app/components/comment_component/comment_component.scss b/app/components/comment_component/comment_component.scss index d50b8ca61..e0e1fd756 100644 --- a/app/components/comment_component/comment_component.scss +++ b/app/components/comment_component/comment_component.scss @@ -36,6 +36,7 @@ article.comment { text-align: center; min-width: 1.25em; white-space: nowrap; + vertical-align: middle; } .icon { diff --git a/app/components/comment_votes_tooltip_component.rb b/app/components/comment_votes_tooltip_component.rb new file mode 100644 index 000000000..4645ffe8c --- /dev/null +++ b/app/components/comment_votes_tooltip_component.rb @@ -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 diff --git a/app/components/comment_votes_tooltip_component/comment_votes_tooltip_component.html.erb b/app/components/comment_votes_tooltip_component/comment_votes_tooltip_component.html.erb new file mode 100644 index 000000000..170a5c6ce --- /dev/null +++ b/app/components/comment_votes_tooltip_component/comment_votes_tooltip_component.html.erb @@ -0,0 +1,15 @@ +
+
+ +<%= upvote_count %> / -<%= downvote_count %> <%= upvote_ratio %> +
+ + <% if policy(CommentVote).can_see_votes? %> +
+ <% votes.each do |vote| %> +
+ <%= vote_icon(vote) %> <%= link_to_user(vote.user, classes: "align-middle") %> +
+ <% end %> +
+ <% end %> +
diff --git a/app/components/comment_votes_tooltip_component/comment_votes_tooltip_component.js b/app/components/comment_votes_tooltip_component/comment_votes_tooltip_component.js new file mode 100644 index 000000000..6501131ee --- /dev/null +++ b/app/components/comment_votes_tooltip_component/comment_votes_tooltip_component.js @@ -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; diff --git a/app/components/comment_votes_tooltip_component/comment_votes_tooltip_component.scss b/app/components/comment_votes_tooltip_component/comment_votes_tooltip_component.scss new file mode 100644 index 000000000..db9b794a9 --- /dev/null +++ b/app/components/comment_votes_tooltip_component/comment_votes_tooltip_component.scss @@ -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; + } +} diff --git a/app/components/post_votes_tooltip_component/post_votes_tooltip_component.html.erb b/app/components/post_votes_tooltip_component/post_votes_tooltip_component.html.erb index a50e1e3e0..b90f76119 100644 --- a/app/components/post_votes_tooltip_component/post_votes_tooltip_component.html.erb +++ b/app/components/post_votes_tooltip_component/post_votes_tooltip_component.html.erb @@ -1,4 +1,4 @@ -
+
+<%= post.up_score %> / -<%= post.down_score.abs %> <%= upvote_ratio %>
diff --git a/app/components/post_votes_tooltip_component/post_votes_tooltip_component.scss b/app/components/post_votes_tooltip_component/post_votes_tooltip_component.scss index 2cc839b5d..4f3c213a1 100644 --- a/app/components/post_votes_tooltip_component/post_votes_tooltip_component.scss +++ b/app/components/post_votes_tooltip_component/post_votes_tooltip_component.scss @@ -1,5 +1,4 @@ .post-votes-tooltip { - font-size: var(--text-xs); max-height: 240px; .upvote-icon { diff --git a/app/controllers/comment_votes_controller.rb b/app/controllers/comment_votes_controller.rb index 701c65276..02881d920 100644 --- a/app/controllers/comment_votes_controller.rb +++ b/app/controllers/comment_votes_controller.rb @@ -5,6 +5,9 @@ class CommentVotesController < ApplicationController @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_id = params[:comment_id] || params[:search][:comment_id] + @comment = Comment.find(comment_id) if comment_id + respond_with(@comment_votes) end diff --git a/app/helpers/components_helper.rb b/app/helpers/components_helper.rb index ade8022d7..05ccd28e2 100644 --- a/app/helpers/components_helper.rb +++ b/app/helpers/components_helper.rb @@ -24,6 +24,10 @@ module ComponentsHelper render PostVotesTooltipComponent.new(post: post, **options) end + def render_comment_votes_tooltip(comment, **options) + render CommentVotesTooltipComponent.new(comment: comment, **options) + end + def render_favorites_tooltip(post, **options) render FavoritesTooltipComponent.new(post: post, **options) end diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index acfb2096a..0b17829c3 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -35,6 +35,7 @@ importAll(require.context('../../components', true, /\.s?css(?:\.erb)?$/)); import Autocomplete from "../src/javascripts/autocomplete.js"; import Blacklist from "../src/javascripts/blacklists.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 Dtext from "../src/javascripts/dtext.js"; import FavoritesTooltipComponent from "../../components/favorites_tooltip_component/favorites_tooltip_component.js"; @@ -58,6 +59,7 @@ let Danbooru = {}; Danbooru.Autocomplete = Autocomplete; Danbooru.Blacklist = Blacklist; Danbooru.CommentComponent = CommentComponent; +Danbooru.CommentVotesTooltipComponent = CommentVotesTooltipComponent; Danbooru.CurrentUser = CurrentUser; Danbooru.Dtext = Dtext; Danbooru.FavoritesTooltipComponent = FavoritesTooltipComponent; diff --git a/app/views/comment_votes/index.html+tooltip.erb b/app/views/comment_votes/index.html+tooltip.erb new file mode 100644 index 000000000..d56be1343 --- /dev/null +++ b/app/views/comment_votes/index.html+tooltip.erb @@ -0,0 +1 @@ +<%= render_comment_votes_tooltip(@comment, current_user: CurrentUser.user) %> diff --git a/app/views/layouts/default.html.erb b/app/views/layouts/default.html.erb index 3dee6ac89..8cde245b3 100644 --- a/app/views/layouts/default.html.erb +++ b/app/views/layouts/default.html.erb @@ -101,6 +101,7 @@
+
diff --git a/config/routes.rb b/config/routes.rb index e9f216909..78d9b5ac9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -91,7 +91,9 @@ Rails.application.routes.draw do end resources :comment_votes, only: [:index, :show, :destroy] 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 get :search end diff --git a/test/functional/comment_votes_controller_test.rb b/test/functional/comment_votes_controller_test.rb index e87cc3364..eba9d3713 100644 --- a/test/functional/comment_votes_controller_test.rb +++ b/test/functional/comment_votes_controller_test.rb @@ -27,6 +27,11 @@ class CommentVotesControllerTest < ActionDispatch::IntegrationTest assert_response :success 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 { [] } end @@ -35,6 +40,11 @@ class CommentVotesControllerTest < ActionDispatch::IntegrationTest CurrentUser.user = create(:mod_user) 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(score: -1).with { @negative_vote }