0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 13:08:40 +08:00
discourse/lib/nested_replies/post_tree_serializer.rb
Mark VanLandingham acbdfa445f
FIX: Add small action controls to nested activity log (#42108)
Previously, small actions in nested replies' Activity Log modal could
not be edited, deleted, or recovered, and refreshes collapsed the modal.

This change renders the activity log as proper small action posts with
all the actions you expect from flat view.
2026-07-29 10:19:47 -05:00

118 lines
3.8 KiB
Ruby
Vendored

# frozen_string_literal: true
module NestedReplies
class PostTreeSerializer
def initialize(topic:, topic_view:, guardian:)
@topic = topic
@topic_view = topic_view
@guardian = guardian
@ignored_user_ids = IgnoredUser.ignored_ids_for(guardian.user)
end
SUGGESTED_AND_RELATED_KEYS = %i[
suggested_topics
suggested_group_name
related_topics
related_messages
].freeze
def serialize_topic
topic_view_json.merge(has_activity_log: activity_log_present?).except(
:post_stream,
:timeline_lookup,
:user_badges,
*SUGGESTED_AND_RELATED_KEYS,
)
end
# Produces the suggested/related payload we piggyback on whichever
# response has has_more_roots=false — mirroring how the flat view
# ships suggested_topics on the final /t/:id/posts.json chunk.
def serialize_suggested_and_related
topic_view_json.slice(*SUGGESTED_AND_RELATED_KEYS)
end
def serialize_post(post, reply_counts, descendant_counts = {})
# Assign the already-loaded topic to avoid an N+1 query per post
# in PostSerializer#topic, which reads object.topic.
post.topic = @topic
serializer = PostSerializer.new(post, scope: @guardian, root: false)
serializer.topic_view = @topic_view
serializer.notice_created_by_users = post_notice_created_by_users if @guardian.is_staff?
json = serializer.as_json
json[:direct_reply_count] = reply_counts[post.post_number] || 0
json[:total_descendant_count] = descendant_counts[post.id] || 0
if post.deleted_at.present?
json[:deleted_post_placeholder] = true
unless @guardian.is_staff?
json[:cooked] = ""
json[:raw] = nil
json[:actions_summary] = []
json =
json.slice(
:id,
:post_number,
:reply_to_post_number,
:deleted_post_placeholder,
:cooked,
:raw,
:actions_summary,
:direct_reply_count,
:total_descendant_count,
)
end
elsif post.post_number != 1 && @ignored_user_ids.include?(post.user_id)
json[:ignored_post_placeholder] = true
json[:cooked] = ""
json[:raw] = nil
json[:actions_summary] = []
end
json
end
def serialize_tree(post, children_map, reply_counts, descendant_counts = {})
node = serialize_post(post, reply_counts, descendant_counts)
children = children_map[post.post_number] || []
node[:children] = children.map do |child|
serialize_tree(child, children_map, reply_counts, descendant_counts)
end
node
end
private
def topic_view_json
@topic_view_json ||=
TopicViewSerializer.new(@topic_view, scope: @guardian, root: false).as_json
end
def post_notice_created_by_users
@post_notice_created_by_users ||=
begin
user_ids =
(@topic_view.post_custom_fields || {})
.filter_map do |_, custom_fields|
custom_fields.dig(Post::NOTICE, "created_by_user_id")
end
.uniq
User.real.where(id: user_ids).to_a
end
end
# Filters mirror Guardian#can_see_post? so the boolean does not leak
# the existence of small_actions/whispers the user cannot see.
def activity_log_present?
post_types = [Post.types[:small_action]]
post_types << Post.types[:whisper] if @guardian.user&.whisperer?
scope = @topic.posts
scope = scope.with_deleted if @guardian.can_see_deleted_posts?(@topic.category)
scope = scope.where(post_type: post_types).where.not(action_code: [nil, ""])
@guardian.filter_hidden_posts(scope, category: @topic.category).exists?
end
end
end