diff --git a/app/assets/javascripts/admin/controllers/admin_user_badges_controller.js b/app/assets/javascripts/admin/controllers/admin_user_badges_controller.js
index af120daca11..4c1fdea0cbd 100644
--- a/app/assets/javascripts/admin/controllers/admin_user_badges_controller.js
+++ b/app/assets/javascripts/admin/controllers/admin_user_badges_controller.js
@@ -57,7 +57,10 @@ Discourse.AdminUserBadgesController = Ember.ArrayController.extend({
self.pushObject(userBadge);
Ember.run.next(function() {
// Update the selected badge ID after the combobox has re-rendered.
- self.set('selectedBadgeId', self.get('grantableBadges')[0].get('id'));
+ var newSelectedBadge = self.get('grantableBadges')[0];
+ if (newSelectedBadge) {
+ self.set('selectedBadgeId', newSelectedBadge.get('id'));
+ }
});
}, function() {
// Failure
diff --git a/app/assets/javascripts/discourse/controllers/poster_expansion_controller.js b/app/assets/javascripts/discourse/controllers/poster_expansion_controller.js
index ae3959f778d..99cca8f5572 100644
--- a/app/assets/javascripts/discourse/controllers/poster_expansion_controller.js
+++ b/app/assets/javascripts/discourse/controllers/poster_expansion_controller.js
@@ -20,6 +20,16 @@ Discourse.PosterExpansionController = Discourse.ObjectController.extend({
hasUserFilters: Em.computed.gt('postStream.userFilters.length', 0),
+ showBadges: function() {
+ return Discourse.SiteSettings.enable_badges;
+ }.property(),
+
+ moreBadgesCount: function() {
+ return this.get('user.badge_count') - this.get('user.featured_user_badges.length');
+ }.property('user.badge_count', 'user.featured_user_badges.@each'),
+
+ showMoreBadges: Em.computed.gt('moreBadgesCount', 0),
+
show: function(post) {
// Don't show on mobile
diff --git a/app/assets/javascripts/discourse/models/user.js b/app/assets/javascripts/discourse/models/user.js
index bc5186dd68f..fc78724871a 100644
--- a/app/assets/javascripts/discourse/models/user.js
+++ b/app/assets/javascripts/discourse/models/user.js
@@ -312,10 +312,21 @@ Discourse.User = Discourse.Model.extend({
return Discourse.Group.create(g);
});
}
+
if (json.user.invited_by) {
json.user.invited_by = Discourse.User.create(json.user.invited_by);
}
+ if (!Em.isEmpty(json.user.featured_user_badge_ids)) {
+ var userBadgesMap = {};
+ Discourse.UserBadge.createFromJson(json).forEach(function(userBadge) {
+ userBadgesMap[ userBadge.get('id') ] = userBadge;
+ });
+ json.user.featured_user_badges = json.user.featured_user_badge_ids.map(function(id) {
+ return userBadgesMap[id];
+ });
+ }
+
user.setProperties(json.user);
return user;
});
diff --git a/app/assets/javascripts/discourse/templates/poster_expansion.handlebars b/app/assets/javascripts/discourse/templates/poster_expansion.handlebars
index d07cf38c780..12957c1e43a 100644
--- a/app/assets/javascripts/discourse/templates/poster_expansion.handlebars
+++ b/app/assets/javascripts/discourse/templates/poster_expansion.handlebars
@@ -1,16 +1,31 @@
{{#if model}}
{{#link-to 'user' user}}{{boundAvatar model imageSize="huge"}}{{/link-to}}
-
- {{#if showName}}
-
- {{/if}}
+
+
+ {{#if showName}}
+
+ {{/if}}
+
{{#if user}}
- {{i18n last_post}} {{unboundDate path="user.last_posted_at" leaveAgo="true"}}
- {{i18n joined}} {{unboundDate path="user.created_at" leaveAgo="true"}}
+
+
{{i18n last_post}} {{unboundDate path="user.last_posted_at" leaveAgo="true"}}
+ {{i18n joined}} {{unboundDate path="user.created_at" leaveAgo="true"}}
+ {{groups-list groups=user.custom_groups}}
+
- {{groups-list groups=user.custom_groups}}
+ {{#if showBadges}}
+
+
{{i18n badges.badge_count count=user.badge_count}}
+ {{#each user.featured_user_badges}}
+ {{badge.name}}
+ {{/each}}
+ {{#if showMoreBadges}}
+ {{i18n badges.more_badges count=moreBadgesCount}}
+ {{/if}}
+
+ {{/if}}
{{#if user.bio_cooked}}
{{{user.bio_cooked}}}
{{/if}}
diff --git a/app/assets/javascripts/discourse/views/poster_expansion_view.js b/app/assets/javascripts/discourse/views/poster_expansion_view.js
index 62c175246fe..c2bc872e70d 100644
--- a/app/assets/javascripts/discourse/views/poster_expansion_view.js
+++ b/app/assets/javascripts/discourse/views/poster_expansion_view.js
@@ -10,7 +10,7 @@ var clickOutsideEventName = "mousedown.outside-poster-expansion";
Discourse.PosterExpansionView = Discourse.View.extend({
elementId: 'poster-expansion',
- classNameBindings: ['controller.visible::hidden'],
+ classNameBindings: ['controller.visible::hidden', 'controller.showBadges'],
// Position the expansion when the post changes
_visibleChanged: function() {
diff --git a/app/assets/stylesheets/desktop/poster_expansion.scss b/app/assets/stylesheets/desktop/poster_expansion.scss
index 066bc3d9e0e..bbd1fb9e20d 100644
--- a/app/assets/stylesheets/desktop/poster_expansion.scss
+++ b/app/assets/stylesheets/desktop/poster_expansion.scss
@@ -65,7 +65,7 @@
}
p.loading {
- margin-top: 30px;
+ margin-top: 45px;
color: $secondary_text_color;
}
@@ -76,4 +76,47 @@
.new-user a {
color: $secondary_text_color;
}
+
+ &.show-badges {
+ width: 560px;
+
+ .names {
+ width: 250px;
+ float: left;
+ }
+
+ .metadata {
+ float: right;
+ max-width: 180px;
+ }
+
+ h2 {
+ line-height: 15px;
+ }
+
+ .badge-section {
+ h3 {
+ line-height: 43px;
+ color: $primary_text_color;
+ font-size: 14px;
+ margin-bottom: -8px;
+ }
+
+ .badge, .more-badges {
+ font-size: 12px;
+ margin: 0;
+ line-height: 16px;
+ display: inline-block;
+ }
+
+ .badge {
+ padding: 3px 8px;
+ border: 1px solid $secondary-border-color;
+ }
+
+ .more-badges {
+ padding: 4px 8px;
+ }
+ }
+ }
}
diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb
index cdd25726af6..1431ccd9f8b 100644
--- a/app/serializers/user_serializer.rb
+++ b/app/serializers/user_serializer.rb
@@ -21,10 +21,12 @@ class UserSerializer < BasicUserSerializer
:admin,
:title,
:suspend_reason,
- :suspended_till
+ :suspended_till,
+ :badge_count
has_one :invited_by, embed: :object, serializer: 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)
@@ -128,4 +130,13 @@ class UserSerializer < BasicUserSerializer
def watched_category_ids
CategoryUser.lookup(object, :watching).pluck(:category_id)
end
+
+ def badge_count
+ object.user_badges.count
+ end
+
+ def featured_user_badges
+ # The three rarest badges this user has received should be featured.
+ object.user_badges.joins(:badge).order('badges.grant_count ASC').includes(:granted_by, badge: :badge_type).limit(3)
+ end
end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index e2d44f9853f..044d2b17472 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1765,6 +1765,12 @@ en:
mark_watching: '
m then
w Mark topic as watching'
badges:
+ badge_count:
+ one: "1 Badge"
+ other: "%{count} Badges"
+ more_badges:
+ one: "+1 More"
+ other: "+%{count} More"
example_badge:
name: Example Badge
description: This is a generic example badge.
diff --git a/lib/freedom_patches/ams_include_without_root.rb b/lib/freedom_patches/ams_include_without_root.rb
new file mode 100644
index 00000000000..424489b3f5c
--- /dev/null
+++ b/lib/freedom_patches/ams_include_without_root.rb
@@ -0,0 +1,59 @@
+# Just ignore included associations that are to be embedded in the root instead of
+# throwing an exception in AMS 0.8.x.
+#
+# The 0.9.0 branch does exactly this, see:
+# https://github.com/rails-api/active_model_serializers/issues/377
+module ActiveModel
+ class Serializer
+ # This method is copied over verbatim from the AMS version, except for silently
+ # ignoring associations that cannot be embedded without a root instead of
+ # raising an exception.
+ def include!(name, options={})
+ unique_values =
+ if hash = options[:hash]
+ if @options[:hash] == hash
+ @options[:unique_values] ||= {}
+ else
+ {}
+ end
+ else
+ hash = @options[:hash]
+ @options[:unique_values] ||= {}
+ end
+
+ node = options[:node] ||= @node
+ value = options[:value]
+
+ if options[:include] == nil
+ if @options.key?(:include)
+ options[:include] = @options[:include].include?(name)
+ elsif @options.include?(:exclude)
+ options[:include] = !@options[:exclude].include?(name)
+ end
+ end
+
+ association_class =
+ if klass = _associations[name]
+ klass
+ elsif value.respond_to?(:to_ary)
+ Associations::HasMany
+ else
+ Associations::HasOne
+ end
+
+ association = association_class.new(name, self, options)
+
+ if association.embed_ids?
+ node[association.key] = association.serialize_ids
+
+ if association.embed_in_root? && hash.nil?
+ # Don't raise an error!
+ elsif association.embed_in_root? && association.embeddable?
+ merge_association hash, association.root, association.serializables, unique_values
+ end
+ elsif association.embed_objects?
+ node[association.key] = association.serialize
+ end
+ end
+ end
+end