- {{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"