discourse/app/models/user_summary.rb
David Battersby d06c60ca7c
FEATURE: add icons and emojis to category (#31795)
This feature allow admins to personalize their communities by
associating emojis or icons with their site categories.

There are now 3 style types for categories:
- Square (the default)
- Emoji
- Icon

### How it looks 🎨 

Adding an icon:

<img width="502" alt="Category with an icon"
src="https://github.com/user-attachments/assets/8f711340-166e-4781-a7b7-7267469dbabd"
/>

Adding an emoji:

<img width="651" alt="Category with an emoji"
src="https://github.com/user-attachments/assets/588c38ce-c719-4ed5-83f9-f1e1cb52c929"
/>

Sidebar:

<img width="248" alt="Sidebar with emojis"
src="https://github.com/user-attachments/assets/cd03d591-6170-4515-998c-0cec20118568"
/>

Category menus:

<img width="621" alt="Screenshot 2025-03-13 at 10 32 30 AM"
src="https://github.com/user-attachments/assets/7d89797a-f69f-45e5-bf64-a92d4cff8753"
/>

Within posts/topics:

<img width="382" alt="Screenshot 2025-03-13 at 10 33 41 AM"
src="https://github.com/user-attachments/assets/b7b1a951-44c6-4a4f-82ad-8ee31ddd6061"
/>

Chat messages:

<img width="392" alt="Screenshot 2025-03-13 at 10 30 20 AM"
src="https://github.com/user-attachments/assets/126f8076-0ea3-4f19-8452-1041fd2af29f"
/>

Autocomplete:

<img width="390" alt="Screenshot 2025-03-13 at 10 29 53 AM"
src="https://github.com/user-attachments/assets/cad75669-225f-4b8e-a7b5-ae5aa8f1bcad"
/>

---------

Co-authored-by: Martin Brennan <martin@discourse.org>
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
2025-03-26 09:46:17 +04:00

242 lines
5.8 KiB
Ruby

# frozen_string_literal: true
# ViewModel used on Summary tab on User page
class UserSummary
MAX_SUMMARY_RESULTS = 6
MAX_BADGES = 6
alias read_attribute_for_serialization send
def initialize(user, guardian)
@user = user
@guardian = guardian
end
def topics
Topic
.secured(@guardian)
.listable_topics
.visible
.where(user: @user)
.order("like_count DESC, created_at DESC")
.limit(MAX_SUMMARY_RESULTS)
end
def replies
post_query
.where("post_number > 1")
.order("posts.like_count DESC, posts.created_at DESC")
.limit(MAX_SUMMARY_RESULTS)
end
def links
TopicLink
.joins(:topic, :post)
.where(posts: { user_id: @user.id, hidden: false })
.includes(:topic, :post)
.where("posts.post_type IN (?)", Topic.visible_post_types(@guardian && @guardian.user))
.merge(Topic.listable_topics.visible.secured(@guardian))
.where(user: @user)
.where(internal: false, reflection: false, quote: false)
.order("clicks DESC, topic_links.created_at DESC")
.limit(MAX_SUMMARY_RESULTS)
end
class UserWithCount < OpenStruct
include ActiveModel::SerializerSupport
end
def most_liked_by_users
likers = {}
UserAction
.joins(:target_topic, :target_post)
.merge(Topic.listable_topics.visible.secured(@guardian))
.where(user: @user)
.where(action_type: UserAction::WAS_LIKED)
.group(:acting_user_id)
.order("COUNT(*) DESC")
.limit(MAX_SUMMARY_RESULTS)
.pluck("acting_user_id, COUNT(*)")
.each { |l| likers[l[0]] = l[1] }
user_counts(likers)
end
def most_liked_users
liked_users = {}
UserAction
.joins(:target_topic, :target_post)
.merge(Topic.listable_topics.visible.secured(@guardian))
.where(action_type: UserAction::WAS_LIKED)
.where(acting_user_id: @user.id)
.group(:user_id)
.order("COUNT(*) DESC")
.limit(MAX_SUMMARY_RESULTS)
.pluck("user_actions.user_id, COUNT(*)")
.each { |l| liked_users[l[0]] = l[1] }
user_counts(liked_users)
end
REPLY_ACTIONS = [UserAction::RESPONSE, UserAction::QUOTE, UserAction::MENTION]
def most_replied_to_users
replied_users = {}
post_query
.joins(
"JOIN posts replies ON posts.topic_id = replies.topic_id AND posts.reply_to_post_number = replies.post_number",
)
.joins(
"JOIN topics ON replies.topic_id = topics.id AND topics.archetype <> 'private_message'",
)
.joins(
"AND replies.post_type IN (#{Topic.visible_post_types(@user, include_moderator_actions: false).join(",")})",
)
.where("replies.user_id <> posts.user_id")
.group("replies.user_id")
.order("COUNT(*) DESC")
.limit(MAX_SUMMARY_RESULTS)
.pluck("replies.user_id, COUNT(*)")
.each { |r| replied_users[r[0]] = r[1] }
user_counts(replied_users)
end
def badges
@user.featured_user_badges(MAX_BADGES)
end
def user_id
@user.id
end
def user
@user
end
def user_stat
@user.user_stat
end
def bookmark_count
Bookmark.where(user: @user).count
end
def recent_time_read
@user.recent_time_read
end
class CategoryWithCounts < OpenStruct
include ActiveModel::SerializerSupport
KEYS = %i[
id
name
color
text_color
style_type
icon
emoji
slug
read_restricted
parent_category_id
]
end
def top_categories
post_count_query = post_query.group("topics.category_id")
top_categories = {}
Category
.where(
id: post_count_query.order("count(*) DESC").limit(MAX_SUMMARY_RESULTS).pluck("category_id"),
)
.pluck(
:id,
:name,
:color,
:text_color,
:style_type,
:icon,
:emoji,
:slug,
:read_restricted,
:parent_category_id,
)
.each do |c|
top_categories[c[0].to_i] = CategoryWithCounts.new(
Hash[CategoryWithCounts::KEYS.zip(c)].merge(topic_count: 0, post_count: 0),
)
end
post_count_query
.where("post_number > 1")
.where("topics.category_id in (?)", top_categories.keys)
.pluck("category_id, COUNT(*)")
.each { |r| top_categories[r[0].to_i].post_count = r[1] }
Topic
.listable_topics
.visible
.secured(@guardian)
.where("topics.category_id in (?)", top_categories.keys)
.where(user: @user)
.group("topics.category_id")
.pluck("category_id, COUNT(*)")
.each { |r| top_categories[r[0].to_i].topic_count = r[1] }
top_categories.values.sort_by { |r| -(r[:post_count] + r[:topic_count]) }
end
delegate :likes_given,
:likes_received,
:days_visited,
:topics_entered,
:posts_read_count,
:topic_count,
:post_count,
:time_read,
to: :user_stat
protected
def user_counts(user_hash)
user_ids = user_hash.keys
lookup = UserLookup.new(user_ids)
user_ids
.map do |user_id|
lookup_hash = lookup[user_id]
if lookup_hash.present?
primary_group = lookup.primary_groups[user_id]
flair_group = lookup.flair_groups[user_id]
UserWithCount.new(
lookup_hash.attributes.merge(
count: user_hash[user_id],
primary_group: primary_group,
flair_group: flair_group,
),
)
end
end
.compact
.sort_by { |u| -u[:count] }
end
def post_query
Post
.joins(:topic)
.includes(:topic)
.where(
"posts.post_type IN (?)",
Topic.visible_post_types(@guardian&.user, include_moderator_actions: false),
)
.merge(Topic.listable_topics.visible.secured(@guardian))
.where(user: @user)
end
end