diff --git a/app/assets/javascripts/discourse/templates/user/user.js.handlebars b/app/assets/javascripts/discourse/templates/user/user.js.handlebars index 68115932082..e9ee00f74a3 100644 --- a/app/assets/javascripts/discourse/templates/user/user.js.handlebars +++ b/app/assets/javascripts/discourse/templates/user/user.js.handlebars @@ -64,25 +64,40 @@
- {{bound-avatar model "huge"}} +
+ {{#if number_of_flags_given}} +
{{number_of_flags_given}} {{i18n user.staff_counters.flags_given}}
+ {{/if}} + {{#if number_of_flagged_posts}} +
{{number_of_flagged_posts}} {{i18n user.staff_counters.flagged_posts}}
+ {{/if}} + {{#if number_of_deleted_posts}} +
{{number_of_deleted_posts}} {{i18n user.staff_counters.deleted_posts}}
+ {{/if}} + {{#if number_of_suspensions}} +
{{number_of_suspensions}} {{i18n user.staff_counters.suspensions}}
+ {{/if}} +
-
-

{{username}} {{{statusIcon}}}

-

{{name}}

+ {{bound-avatar model "huge"}} -
{{{bio_cooked}}}
+
+

{{username}} {{{statusIcon}}}

+

{{name}}

- {{groups-list groups=custom_groups}} +
{{{bio_cooked}}}
- {{plugin-outlet "user-profile-primary"}} + {{groups-list groups=custom_groups}} - {{#if isSuspended}} -
- - {{i18n user.suspended_notice date="suspendedTillDate"}}
- {{i18n user.suspended_reason}} {{suspend_reason}} -
- {{/if}} + {{plugin-outlet "user-profile-primary"}} + + {{#if isSuspended}} +
+ + {{i18n user.suspended_notice date="suspendedTillDate"}}
+ {{i18n user.suspended_reason}} {{suspend_reason}} +
+ {{/if}}
diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss index 4ff58176e56..ccb8650db82 100644 --- a/app/assets/stylesheets/desktop/user.scss +++ b/app/assets/stylesheets/desktop/user.scss @@ -190,6 +190,7 @@ } .primary { + position: relative; margin-top: 20px; float: left; width: 65%; @@ -248,6 +249,10 @@ margin-top: 0; } + .staff-counters { + display: none; + } + .details { .secondary { display: none; } .bio { display: none; } @@ -332,4 +337,36 @@ margin-top: -4px; } } + + .staff-counters { + text-align: left; + position: absolute; + top: -20px; + > div { + margin-bottom: 10px; + } + } + + .pill { + font-size: 2em; + border-radius: 100%; + display: inline-block; + height: 30px; + width: 40px; + text-align: center; + vertical-align: middle; + padding-top: 10px; + } + .helpful-flags { + background-color: green; + } + .flagged-posts { + background-color: #E49735; + } + .deleted-posts { + background-color: #EC441B; + } + .suspensions { + background-color: #c22020; + } } diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 279b76b9d8c..72b0211a04f 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -1,5 +1,23 @@ class UserSerializer < BasicUserSerializer + def self.staff_attributes(*attrs) + attributes(*attrs) + attrs.each do |attr| + define_method "include_#{attr}?" do + scope.is_staff? + end + end + end + + def self.private_attributes(*attrs) + attributes(*attrs) + attrs.each do |attr| + define_method "include_#{attr}?" do + can_edit + end + end + end + attributes :name, :email, :last_posted_at, @@ -30,27 +48,12 @@ class UserSerializer < BasicUserSerializer has_many :custom_groups, embed: :object, serializer: BasicGroupSerializer has_many :featured_user_badges, embed: :ids, serializer: UserBadgeSerializer, root: :user_badges - def self.private_attributes(*attrs) - attributes(*attrs) - attrs.each do |attr| - define_method "include_#{attr}?" do - can_edit - end - end - end - def bio_excerpt - # If they have a bio return it - excerpt = object.user_profile.bio_excerpt - return excerpt if excerpt.present? + staff_attributes :number_of_deleted_posts, + :number_of_flagged_posts, + :number_of_flags_given, + :number_of_suspensions - # Without a bio, determine what message to show - if scope.user && scope.user.id == object.id - I18n.t('user_profile.no_info_me', username_lower: object.username_lower) - else - I18n.t('user_profile.no_info_other', name: object.name) - end - end private_attributes :email, :locale, @@ -74,24 +77,44 @@ class UserSerializer < BasicUserSerializer :custom_avatar_upload_id, :custom_fields - def gravatar_avatar_upload_id - object.user_avatar.try(:gravatar_upload_id) + ### + ### ATTRIBUTES + ### + + def bio_raw + object.user_profile.bio_raw end - def custom_avatar_upload_id - object.user_avatar.try(:custom_upload_id) + def include_bio_raw? + bio_raw.present? end - def auto_track_topics_after_msecs - object.auto_track_topics_after_msecs || SiteSetting.auto_track_topics_after + def bio_cooked + object.user_profile.bio_processed end - def new_topic_duration_minutes - object.new_topic_duration_minutes || SiteSetting.new_topic_duration_minutes + def website + object.user_profile.website end - def can_send_private_message_to_user - scope.can_send_private_message?(object) + def include_website? + website.present? + end + + def profile_background + object.user_profile.profile_background + end + + def include_profile_background? + profile_background.present? + end + + def location + object.user_profile.location + end + + def include_location? + location.present? end def can_edit @@ -110,45 +133,27 @@ class UserSerializer < BasicUserSerializer scope.can_edit_name?(object) end - def location - object.user_profile.location - end - def include_location? - location.present? - end - - def website - object.user_profile.website - end - def include_website? - website.present? - end - - def include_bio_raw? - bio_raw.present? - end - def bio_raw - object.user_profile.bio_raw - end - - def bio_cooked - object.user_profile.bio_processed - end - - def profile_background - object.user_profile.profile_background - end - def include_profile_background? - profile_background.present? - end - def stats UserAction.stats(object.id, scope) end - def include_suspended? - object.suspended? + def can_send_private_message_to_user + scope.can_send_private_message?(object) end + + def bio_excerpt + # If they have a bio return it + excerpt = object.user_profile.bio_excerpt + return excerpt if excerpt.present? + + # Without a bio, determine what message to show + if scope.user && scope.user.id == object.id + I18n.t('user_profile.no_info_me', username_lower: object.username_lower) + else + I18n.t('user_profile.no_info_other', name: object.name) + end + end + def include_suspend_reason? object.suspended? end @@ -157,6 +162,47 @@ class UserSerializer < BasicUserSerializer object.suspended? end + ### + ### STAFF ATTRIBUTES + ### + + def number_of_deleted_posts + Post.with_deleted + .where(user_id: object.id) + .where(user_deleted: false) + .where.not(deleted_by: object.id) + .count + end + + def number_of_flagged_posts + Post.with_deleted + .where(user_id: object.id) + .where(hidden: true) + .count + end + + def number_of_flags_given + PostAction.where(post_action_type_id: PostActionType.notify_flag_type_ids) + .where(user_id: object.id) + .count + end + + def number_of_suspensions + UserHistory.for(object, :suspend_user).count + end + + ### + ### PRIVATE ATTRIBUTES + ### + + def auto_track_topics_after_msecs + object.auto_track_topics_after_msecs || SiteSetting.auto_track_topics_after + end + + def new_topic_duration_minutes + object.new_topic_duration_minutes || SiteSetting.new_topic_duration_minutes + end + def muted_category_ids CategoryUser.lookup(object, :muted).pluck(:category_id) end @@ -172,4 +218,13 @@ class UserSerializer < BasicUserSerializer def private_messages_stats UserAction.private_messages_stats(object.id, scope) end + + def gravatar_avatar_upload_id + object.user_avatar.try(:gravatar_upload_id) + end + + def custom_avatar_upload_id + object.user_avatar.try(:custom_upload_id) + end + end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 547fa6b7e4e..80715f16f74 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -291,6 +291,12 @@ en: delete_yourself_not_allowed: "You cannot delete your account right now. Contact an admin to do delete your account for you." unread_message_count: "Messages" + staff_counters: + flags_given: "helpful flags cast" + flagged_posts: "flagged posts" + deleted_posts: "deleted posts" + suspensions: "suspensions" + messages: all: "All" mine: "Mine"