mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-04-30 05:07:19 +08:00
Removed in 2022 because of perf considerations. This restores it and updates the implementation and styling. The perf should not be an issue anymore: 1. it's now rendered only in the crawler view (and not for regular logged-in users or anons) 2. it previously used Rails' partial rendering which re-read file on each request. `register_html_builder` is more efficient (and this particular block skips rendering completely when not applicable)
277 lines
9.8 KiB
Ruby
277 lines
9.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# name: discourse-post-voting
|
|
# about: Allows the creation of topics with votable posts.
|
|
# meta_topic_id: 227808
|
|
# version: 0.0.1
|
|
# authors: Alan Tan
|
|
# url: https://github.com/discourse/discourse/tree/main/plugins/discourse-post-voting
|
|
|
|
register_asset "stylesheets/common/post-voting.scss"
|
|
register_asset "stylesheets/common/post-voting-crawler.scss"
|
|
|
|
enabled_site_setting :post_voting_enabled
|
|
|
|
module ::PostVoting
|
|
PLUGIN_NAME = "discourse-post-voting"
|
|
end
|
|
|
|
require_relative "lib/post_voting/engine"
|
|
|
|
after_initialize do
|
|
Discourse::Application.routes.append { mount PostVoting::Engine, at: "post_voting" }
|
|
|
|
require_relative "lib/post_voting/vote_manager"
|
|
require_relative "lib/post_voting/guardian_extension"
|
|
require_relative "lib/post_voting/comment_creator"
|
|
require_relative "lib/post_voting/comment_review_queue"
|
|
require_relative "extensions/post_extension"
|
|
require_relative "extensions/post_serializer_extension"
|
|
require_relative "extensions/topic_extension"
|
|
require_relative "extensions/topic_list_item_serializer_extension"
|
|
require_relative "extensions/topic_view_serializer_extension"
|
|
require_relative "extensions/topic_view_extension"
|
|
require_relative "extensions/user_extension"
|
|
require_relative "app/validators/post_voting_comment_validator"
|
|
require_relative "app/controllers/post_voting/votes_controller"
|
|
require_relative "app/controllers/post_voting/comments_controller"
|
|
require_relative "app/models/post_voting_vote"
|
|
require_relative "app/models/post_voting_comment"
|
|
require_relative "app/models/post_voting_comment_custom_field"
|
|
require_relative "app/models/reviewable_post_voting_comment"
|
|
require_relative "app/serializers/basic_voter_serializer"
|
|
require_relative "app/serializers/post_voting_comment_serializer"
|
|
require_relative "app/serializers/reviewable_post_voting_comment_serializer"
|
|
|
|
register_svg_icon "vote-up"
|
|
register_svg_icon "vote-up-filled"
|
|
register_svg_icon "info"
|
|
|
|
register_post_custom_field_type("vote_history", :json)
|
|
register_post_custom_field_type("vote_count", :integer)
|
|
|
|
register_reviewable_type ReviewablePostVotingComment
|
|
|
|
if Rails.env.local? && enabled?
|
|
require_relative "lib/discourse_dev/reviewable_post_voting_comment"
|
|
DiscoursePluginRegistry.discourse_dev_populate_reviewable_types.add DiscourseDev::ReviewablePostVotingComment
|
|
end
|
|
|
|
reloadable_patch do
|
|
Post.include(PostVoting::PostExtension)
|
|
Topic.include(PostVoting::TopicExtension)
|
|
PostSerializer.include(PostVoting::PostSerializerExtension)
|
|
TopicView.prepend(PostVoting::TopicViewExtension)
|
|
TopicViewSerializer.include(PostVoting::TopicViewSerializerExtension)
|
|
TopicListItemSerializer.include(PostVoting::TopicListItemSerializerExtension)
|
|
User.include(PostVoting::UserExtension)
|
|
Guardian.prepend(PostVoting::GuardianExtension)
|
|
end
|
|
|
|
# TODO: Performance of the query degrades as the number of posts a user has voted
|
|
# on increases. We should probably keep a counter cache in the user's
|
|
# custom fields.
|
|
add_to_class(:user, :vote_count) { Post.where(user_id: self.id).sum(:qa_vote_count) }
|
|
|
|
add_to_serializer(:user_card, :vote_count) { object.vote_count }
|
|
|
|
add_to_class(:topic_view, :user_voted_posts) do |user|
|
|
@user_voted_posts ||= {}
|
|
|
|
@user_voted_posts[user.id] ||= begin
|
|
PostVotingVote.where(user: user, post: @posts).distinct.pluck(:post_id)
|
|
end
|
|
end
|
|
|
|
add_to_class(:topic_view, :user_voted_posts_last_timestamp) do |user|
|
|
@user_voted_posts_last_timestamp ||= {}
|
|
|
|
@user_voted_posts_last_timestamp[user.id] ||= begin
|
|
PostVotingVote
|
|
.where(user: user, post: @posts)
|
|
.group(:votable_id, :created_at)
|
|
.pluck(:votable_id, :created_at)
|
|
end
|
|
end
|
|
|
|
TopicView.apply_custom_default_scope do |scope, topic_view|
|
|
if topic_view.topic.is_post_voting? &&
|
|
!topic_view.instance_variable_get(:@replies_to_post_number) &&
|
|
!topic_view.instance_variable_get(:@post_ids)
|
|
scope = scope.where(reply_to_post_number: nil)
|
|
|
|
if topic_view.instance_variable_get(:@filter) != TopicView::ACTIVITY_FILTER
|
|
scope =
|
|
scope
|
|
.where.not(post_type: [Post.types[:whisper], Post.types[:small_action]])
|
|
.unscope(:order)
|
|
.order("CASE post_number WHEN 1 THEN 0 ELSE 1 END, qa_vote_count DESC, post_number ASC")
|
|
end
|
|
|
|
scope
|
|
else
|
|
scope
|
|
end
|
|
end
|
|
|
|
register_html_builder("server:topic-show-crawler-post-end") do |controller, post:|
|
|
topic_view = controller.instance_variable_get(:@topic_view)
|
|
next if !topic_view&.topic&.is_post_voting?
|
|
|
|
comments = topic_view.comments[post.id]
|
|
reply_count = post.is_first_post? ? topic_view.filtered_posts.count - 1 : 0
|
|
next if comments.blank? && reply_count <= 0
|
|
|
|
ApplicationController.render(
|
|
template: "post_voting/crawler_post",
|
|
layout: false,
|
|
assigns: {
|
|
comments: comments,
|
|
reply_count: reply_count,
|
|
},
|
|
)
|
|
end
|
|
|
|
TopicView.on_preload do |topic_view|
|
|
next if !topic_view.topic.is_post_voting?
|
|
|
|
topic_view.comments = {}
|
|
topic_view.comments_counts = {}
|
|
topic_view.posts_user_voted = {}
|
|
topic_view.comments_user_voted = {}
|
|
topic_view.posts_voted_on = []
|
|
|
|
post_ids = topic_view.posts.pluck(:id)
|
|
next if post_ids.blank?
|
|
|
|
post_ids_sql = post_ids.join(",")
|
|
|
|
comment_ids_sql = <<~SQL
|
|
SELECT
|
|
post_voting_comments.id
|
|
FROM post_voting_comments
|
|
INNER JOIN LATERAL (
|
|
SELECT 1
|
|
FROM (
|
|
SELECT
|
|
comments.id
|
|
FROM post_voting_comments comments
|
|
WHERE comments.post_id = post_voting_comments.post_id
|
|
AND comments.deleted_at IS NULL
|
|
ORDER BY comments.id ASC
|
|
LIMIT #{TopicView::PRELOAD_COMMENTS_COUNT}
|
|
) X
|
|
WHERE X.id = post_voting_comments.id
|
|
) Y ON true
|
|
WHERE post_voting_comments.post_id IN (#{post_ids_sql})
|
|
AND post_voting_comments.deleted_at IS NULL
|
|
SQL
|
|
|
|
PostVotingComment
|
|
.includes(:user)
|
|
.where("id IN (#{comment_ids_sql})")
|
|
.order(id: :asc)
|
|
.each do |comment|
|
|
topic_view.comments[comment.post_id] ||= []
|
|
topic_view.comments[comment.post_id] << comment
|
|
end
|
|
|
|
topic_view.comments_counts = PostVotingComment.where(post_id: post_ids).group(:post_id).count
|
|
|
|
if topic_view.guardian.user
|
|
PostVotingVote
|
|
.where(user: topic_view.guardian.user, votable_type: "Post", votable_id: post_ids)
|
|
.pluck(:votable_id, :direction)
|
|
.each { |post_id, direction| topic_view.posts_user_voted[post_id] = direction }
|
|
|
|
PostVotingVote
|
|
.joins(
|
|
"INNER JOIN post_voting_comments comments ON comments.id = post_voting_votes.votable_id",
|
|
)
|
|
.where(user: topic_view.guardian.user, votable_type: "PostVotingComment")
|
|
.where("comments.post_id IN (?)", post_ids)
|
|
.pluck(:votable_id)
|
|
.each { |votable_id| topic_view.comments_user_voted[votable_id] = true }
|
|
end
|
|
|
|
topic_view.posts_voted_on =
|
|
PostVotingVote.where(votable_type: "Post", votable_id: post_ids).distinct.pluck(:votable_id)
|
|
end
|
|
|
|
add_permitted_post_create_param(:create_as_post_voting)
|
|
|
|
# TODO: Core should be exposing the following as proper plugin interfaces.
|
|
NewPostManager.add_plugin_payload_attribute(:subtype)
|
|
TopicSubtype.register(Topic::POST_VOTING_SUBTYPE)
|
|
|
|
NewPostManager.add_handler do |manager|
|
|
if !manager.args[:topic_id] && manager.args[:create_as_post_voting].to_s == "true" &&
|
|
(manager.args[:archetype].blank? || manager.args[:archetype] == Archetype.default)
|
|
manager.args[:subtype] = Topic::POST_VOTING_SUBTYPE
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
register_modifier(:topic_embed_import_create_args) do |args|
|
|
category_id = args[:category]
|
|
next args unless category_id
|
|
next args if args[:archetype] != Archetype.default && args[:archetype].present?
|
|
|
|
category = Category.find_by(id: category_id)
|
|
|
|
if category&.create_as_post_voting_default || category&.only_post_voting_in_this_category
|
|
args[:subtype] = Topic::POST_VOTING_SUBTYPE
|
|
end
|
|
|
|
args
|
|
end
|
|
|
|
register_category_custom_field_type(PostVoting::CREATE_AS_POST_VOTING_DEFAULT, :boolean)
|
|
register_preloaded_category_custom_fields(PostVoting::CREATE_AS_POST_VOTING_DEFAULT)
|
|
|
|
add_to_class(:category, :create_as_post_voting_default) do
|
|
ActiveModel::Type::Boolean.new.cast(
|
|
self.custom_fields[PostVoting::CREATE_AS_POST_VOTING_DEFAULT],
|
|
)
|
|
end
|
|
add_to_serializer(:basic_category, :create_as_post_voting_default) do
|
|
object.create_as_post_voting_default
|
|
end
|
|
|
|
add_to_serializer(:current_user, :can_flag_post_voting_comments) do
|
|
scope.can_flag_post_voting_comments?
|
|
end
|
|
|
|
register_category_custom_field_type(PostVoting::ONLY_POST_VOTING_IN_THIS_CATEGORY, :boolean)
|
|
register_preloaded_category_custom_fields PostVoting::ONLY_POST_VOTING_IN_THIS_CATEGORY
|
|
register_preloaded_category_custom_fields PostVoting::CREATE_AS_POST_VOTING_DEFAULT
|
|
|
|
add_to_class(:category, :only_post_voting_in_this_category) do
|
|
ActiveModel::Type::Boolean.new.cast(
|
|
self.custom_fields[PostVoting::ONLY_POST_VOTING_IN_THIS_CATEGORY],
|
|
)
|
|
end
|
|
add_to_serializer(:basic_category, :only_post_voting_in_this_category) do
|
|
object.only_post_voting_in_this_category
|
|
end
|
|
|
|
add_model_callback(:post, :before_create) do
|
|
if SiteSetting.post_voting_enabled && self.is_post_voting_topic? && self.via_email &&
|
|
self.reply_to_post_number == 1
|
|
self.reply_to_post_number = nil
|
|
end
|
|
end
|
|
|
|
register_user_destroyer_on_content_deletion_callback(
|
|
Proc.new do |user|
|
|
post_voting_comment_ids = PostVotingComment.where(user_id: user.id).pluck(:id)
|
|
PostVotingComment.where(id: post_voting_comment_ids).delete_all
|
|
ReviewablePostVotingComment.where(
|
|
target_id: post_voting_comment_ids,
|
|
target_type: "PostVotingComment",
|
|
).delete_all
|
|
PostVoting::VoteManager.bulk_remove_votes_by(user)
|
|
end,
|
|
)
|
|
end
|