diff --git a/app/components/comment_component.rb b/app/components/comment_component.rb index 77fbdafdc..39793e0c3 100644 --- a/app/components/comment_component.rb +++ b/app/components/comment_component.rb @@ -28,12 +28,16 @@ class CommentComponent < ApplicationComponent def upvoted? return false if current_user.is_anonymous? - comment.votes.active.select(&:is_positive?).map(&:user_id).include?(current_user.id) + current_vote&.is_positive? end def downvoted? return false if current_user.is_anonymous? - comment.votes.active.select(&:is_negative?).map(&:user_id).include?(current_user.id) + current_vote&.is_negative? + end + + def current_vote + @current_vote ||= comment.votes.active.find { |v| v.user_id == current_user.id } end def reported? diff --git a/app/components/comment_component/comment_component.html.erb b/app/components/comment_component/comment_component.html.erb index 987e80783..bf29ec764 100644 --- a/app/components/comment_component/comment_component.html.erb +++ b/app/components/comment_component/comment_component.html.erb @@ -48,7 +48,7 @@ <% if current_user.is_anonymous? %> <%= link_to upvote_icon, login_path(url: request.fullpath), class: "comment-upvote-link inactive-link" %> <% elsif upvoted? %> - <%= link_to upvote_icon, comment_comment_votes_path(comment_id: comment.id), class: "comment-upvote-link comment-unvote-link active-link", method: :delete, remote: true %> + <%= link_to upvote_icon, comment_vote_path(current_vote), class: "comment-upvote-link comment-unvote-link active-link", method: :delete, remote: true %> <% else %> <%= 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 %> @@ -64,7 +64,7 @@ <% if current_user.is_anonymous? %> <%= link_to downvote_icon, login_path(url: request.fullpath), class: "comment-downvote-link inactive-link" %> <% elsif downvoted? %> - <%= link_to downvote_icon, comment_comment_votes_path(comment_id: comment.id), class: "comment-downvote-link comment-unvote-link active-link", method: :delete, remote: true %> + <%= link_to downvote_icon, comment_vote_path(current_vote), class: "comment-downvote-link comment-unvote-link active-link", method: :delete, remote: true %> <% else %> <%= link_to downvote_icon, comment_comment_votes_path(comment_id: comment.id, score: "-1"), class: "comment-downvote-link inactive-link", method: :post, remote: true %> <% end %> diff --git a/app/controllers/comment_votes_controller.rb b/app/controllers/comment_votes_controller.rb index 4235e8492..5b53dab0d 100644 --- a/app/controllers/comment_votes_controller.rb +++ b/app/controllers/comment_votes_controller.rb @@ -26,8 +26,7 @@ class CommentVotesController < ApplicationController end def destroy - # XXX should find by comment vote id. - @comment_vote = authorize CommentVote.active.find_by!(comment_id: params[:comment_id], user: CurrentUser.user) + @comment_vote = authorize CommentVote.find(params[:id]) @comment_vote.soft_delete(updater: CurrentUser.user) respond_with(@comment_vote) diff --git a/app/policies/comment_vote_policy.rb b/app/policies/comment_vote_policy.rb index 25706185c..3ca44bb6b 100644 --- a/app/policies/comment_vote_policy.rb +++ b/app/policies/comment_vote_policy.rb @@ -4,7 +4,7 @@ class CommentVotePolicy < ApplicationPolicy end def destroy? - record.user_id == user.id + !record.is_deleted? && (record.user_id == user.id || user.is_admin?) end def can_see_votes? diff --git a/app/views/comment_votes/_search.html.erb b/app/views/comment_votes/_search.html.erb index 2fcdde6a0..b33796678 100644 --- a/app/views/comment_votes/_search.html.erb +++ b/app/views/comment_votes/_search.html.erb @@ -6,6 +6,7 @@ <%= fc.input :post_id, label: "Post", input_html: { value: params.dig(:search, :comment, :post_id) } %> <% end %> <%= f.input :comment_id, label: "Comment", input_html: { value: params[:search][:comment_id] } %> + <%= f.input :is_deleted, label: "Deleted?", as: :select, include_blank: true, selected: params[:search][:is_deleted] %> <%= f.input :score, collection: [["+1", "1"], ["-1", "-1"]], include_blank: true, selected: params[:search][:score] %> <%= f.submit "Search" %> <% end %> diff --git a/app/views/comment_votes/destroy.js+listing.erb b/app/views/comment_votes/destroy.js+listing.erb new file mode 100644 index 000000000..2edde040f --- /dev/null +++ b/app/views/comment_votes/destroy.js+listing.erb @@ -0,0 +1,2 @@ +Danbooru.Utility.notice("Vote removed"); +location.reload(); diff --git a/app/views/comment_votes/index.html+compact.erb b/app/views/comment_votes/index.html+compact.erb index 053c3b37e..f3cf672f4 100644 --- a/app/views/comment_votes/index.html+compact.erb +++ b/app/views/comment_votes/index.html+compact.erb @@ -14,9 +14,23 @@ <%= link_to "»", comment_votes_path(search: { user_name: vote.user.name }) %> <% end %> + <% t.column "Status" do |vote| %> + <%= "Deleted" if vote.is_deleted? %> + <% end %> + <% t.column "Created" do |vote| %> <%= time_ago_in_words_tagged(vote.created_at) %> <% end %> + + <% t.column column: "control" do |vote| %> + <% if policy(vote).destroy? %> + <%= render PopupMenuComponent.new do |menu| %> + <%= menu.item do %> + <%= link_to "Remove", comment_vote_path(vote, variant: "listing"), remote: true, method: :delete %> + <% end %> + <% end %> + <% end %> + <% end %> <% end %> <%= numbered_paginator(@comment_votes) %> diff --git a/app/views/comment_votes/index.html.erb b/app/views/comment_votes/index.html.erb index 8c17a05d7..ce6b3e902 100644 --- a/app/views/comment_votes/index.html.erb +++ b/app/views/comment_votes/index.html.erb @@ -8,27 +8,40 @@ <% t.column "Post" do |vote| %> <%= post_preview(vote.comment.post, show_deleted: true) %> <% end %> + <% t.column "Comment", td: {class: "col-expand"} do |vote| %>
<%= format_text(vote.comment.body) %>
<% end %> + + <% t.column "Status" do |vote| %> + <%= "Deleted" if vote.is_deleted? %> + <% end %> + <% t.column "Score" do |vote| %> <%= link_to sprintf("%+d", vote.score), comment_votes_path(search: { score: vote.score }) %> <% end %> + <% t.column "Commenter" do |vote| %> <%= link_to_user vote.comment.creator %> <%= link_to "»", comment_votes_path(search: { comment: { creator_name: vote.comment.creator.name }}) %>
<%= time_ago_in_words_tagged(vote.comment.created_at) %>
<% end %> + <% t.column "Voter" do |vote| %> <%= link_to_user vote.user %> <%= link_to "»", comment_votes_path(search: { user_name: vote.user.name }) %>
<%= time_ago_in_words_tagged(vote.created_at) %>
<% end %> + <% t.column column: "control" do |vote| %> - <% if vote.user == CurrentUser.user %> - <%= link_to "unvote", comment_comment_votes_path(vote.comment), remote: true, method: :delete %> + <% if policy(vote).destroy? %> + <%= render PopupMenuComponent.new do |menu| %> + <%= menu.item do %> + <%= link_to "Remove", comment_vote_path(vote, variant: "listing"), remote: true, method: :delete %> + <% end %> + <% end %> <% end %> <% end %> <% end %> diff --git a/config/routes.rb b/config/routes.rb index 82df33b1c..753d3d991 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -81,7 +81,7 @@ Rails.application.routes.draw do post :approve end end - resources :comment_votes, only: [:index] + resources :comment_votes, only: [:index, :destroy] resources :comments do resource :votes, controller: "comment_votes", only: [:create, :destroy], as: "comment_votes" collection do diff --git a/test/functional/comment_votes_controller_test.rb b/test/functional/comment_votes_controller_test.rb index 2b6e0227c..f46c4bbf5 100644 --- a/test/functional/comment_votes_controller_test.rb +++ b/test/functional/comment_votes_controller_test.rb @@ -141,21 +141,51 @@ class CommentVotesControllerTest < ActionDispatch::IntegrationTest context "#destroy" do should "allow users to remove their own comment votes" do @vote = create(:comment_vote, user: @user) + assert_equal(1, @vote.comment.score) assert_difference("CommentVote.count", 0) do - delete_auth comment_comment_votes_path(@vote.comment), @user, xhr: true + delete_auth comment_vote_path(@vote), @user, xhr: true + assert_response :success assert_equal(true, @vote.reload.is_deleted?) + assert_equal(0, @vote.comment.score) + + assert_equal(false, ModAction.comment_vote_delete.exists?) end end - should "not allow users to remove comment votes by other users" do + should "not allow normal users to remove comment votes by other users" do @vote = create(:comment_vote) + assert_equal(1, @vote.comment.score) assert_difference("CommentVote.count", 0) do - delete_auth comment_comment_votes_path(@vote.comment), @user, xhr: true - assert_response 404 + delete_auth comment_vote_path(@vote), @user, xhr: true, params: { variant: "listing" } + + assert_response 403 assert_equal(false, @vote.reload.is_deleted?) + assert_equal(1, @vote.comment.score) + end + end + + should "not allow deleting already deleted votes" do + @vote = create(:comment_vote, is_deleted: true) + delete_auth comment_vote_path(@vote), @user, xhr: true + assert_response 403 + end + + should "allow admins to remove comment votes by other users" do + @vote = create(:comment_vote) + assert_equal(1, @vote.comment.score) + + assert_difference("CommentVote.count", 0) do + delete_auth comment_vote_path(@vote), create(:admin_user), xhr: true, params: { variant: "listing" } + + assert_response :success + assert_equal(true, @vote.reload.is_deleted?) + assert_equal(0, @vote.comment.score) + + assert_equal("comment_vote_delete", ModAction.last.category) + assert_match(/deleted comment vote/, ModAction.last.description) end end @@ -167,7 +197,7 @@ class CommentVotesControllerTest < ActionDispatch::IntegrationTest should "delete the current active vote" do @vote = create(:comment_vote, comment: @comment, user: @user) - delete_auth comment_comment_votes_path(@vote.comment), @user, xhr: true + delete_auth comment_vote_path(@vote), @user, xhr: true assert_response :success assert_equal(true, @vote.reload.is_deleted?)