diff --git a/app/assets/javascripts/discourse/components/user-badge.js.es6 b/app/assets/javascripts/discourse/components/user-badge.js.es6 index c7fce8b1a..0db738d41 100644 --- a/app/assets/javascripts/discourse/components/user-badge.js.es6 +++ b/app/assets/javascripts/discourse/components/user-badge.js.es6 @@ -3,5 +3,13 @@ export default Ember.Component.extend({ showGrantCount: function() { return this.get('count') && this.get('count') > 1; - }.property('count') + }.property('count'), + + badgeUrl: function(){ + // NOTE: I tried using a link-to helper here but the queryParams mean it fails + var username = this.get('user.username_lower') || ''; + username = username !== '' ? "?username=" + username : ''; + return this.get('badge.url') + username; + }.property("badge", "user") + }); diff --git a/app/assets/javascripts/discourse/controllers/badges/show.js.es6 b/app/assets/javascripts/discourse/controllers/badges/show.js.es6 index a5a899f64..afe640bc6 100644 --- a/app/assets/javascripts/discourse/controllers/badges/show.js.es6 +++ b/app/assets/javascripts/discourse/controllers/badges/show.js.es6 @@ -1,17 +1,33 @@ import UserBadge from 'discourse/models/user-badge'; export default Ember.Controller.extend({ + queryParams: ['username'], noMoreBadges: false, userBadges: null, needs: ["application"], + user: function(){ + if (this.get("username")) { + return this.get('userBadges')[0].get('user'); + } + }.property("username"), + + grantCount: function() { + if (this.get("username")) { + return this.get('userBadges.grant_count'); + } else { + return this.get('model.grant_count'); + } + }.property('username', 'model', 'userBadges'), + actions: { loadMore() { const self = this; const userBadges = this.get('userBadges'); UserBadge.findByBadgeId(this.get('model.id'), { - offset: userBadges.length + offset: userBadges.length, + username: this.get('username'), }).then(function(result) { userBadges.pushObjects(result); if(userBadges.length === 0){ @@ -22,11 +38,12 @@ export default Ember.Controller.extend({ }, layoutClass: function(){ + var user = this.get("user") ? " single-user" : ""; var ub = this.get("userBadges"); if(ub && ub[0] && ub[0].post_id){ - return "user-badge-with-posts"; + return "user-badge-with-posts" + user; } else { - return "user-badge-no-posts"; + return "user-badge-no-posts" + user; } }.property("userBadges"), @@ -34,7 +51,7 @@ export default Ember.Controller.extend({ if (this.get('noMoreBadges')) { return false; } if (this.get('userBadges')) { - return this.get('model.grant_count') > this.get('userBadges.length'); + return this.get('grantCount') > this.get('userBadges.length'); } else { return false; } diff --git a/app/assets/javascripts/discourse/controllers/user-badges.js.es6 b/app/assets/javascripts/discourse/controllers/user-badges.js.es6 index 3ceae1ec1..76b2a05cd 100644 --- a/app/assets/javascripts/discourse/controllers/user-badges.js.es6 +++ b/app/assets/javascripts/discourse/controllers/user-badges.js.es6 @@ -1,4 +1,6 @@ export default Ember.ArrayController.extend({ + needs: ["user"], + user: Em.computed.alias("controllers.user.model"), sortProperties: ['badge.badge_type.sort_order', 'badge.name'], orderBy: function(ub1, ub2){ var sr1 = ub1.get('badge.badge_type.sort_order'); diff --git a/app/assets/javascripts/discourse/models/badge.js.es6 b/app/assets/javascripts/discourse/models/badge.js.es6 index 1e4d79433..2b070a0ab 100644 --- a/app/assets/javascripts/discourse/models/badge.js.es6 +++ b/app/assets/javascripts/discourse/models/badge.js.es6 @@ -5,6 +5,10 @@ const Badge = RestModel.extend({ newBadge: Em.computed.none('id'), + url: function() { + return Discourse.getURL(`/badges/${this.get('id')}/${this.get('slug')}`); + }.property(), + /** @private diff --git a/app/assets/javascripts/discourse/models/user-badge.js.es6 b/app/assets/javascripts/discourse/models/user-badge.js.es6 index 8f938298d..a99c997d6 100644 --- a/app/assets/javascripts/discourse/models/user-badge.js.es6 +++ b/app/assets/javascripts/discourse/models/user-badge.js.es6 @@ -48,7 +48,7 @@ UserBadge.reopenClass({ if ("user_badge" in json) { userBadges = [json.user_badge]; } else { - userBadges = json.user_badges; + userBadges = (json.user_badge_info && json.user_badge_info.user_badges) || json.user_badges; } userBadges = userBadges.map(function(userBadgeJson) { @@ -73,6 +73,10 @@ UserBadge.reopenClass({ if ("user_badge" in json) { return userBadges[0]; } else { + if (json.user_badge_info) { + userBadges.grant_count = json.user_badge_info.grant_count; + userBadges.username = json.user_badge_info.username; + } return userBadges; } }, diff --git a/app/assets/javascripts/discourse/routes/badges-show.js.es6 b/app/assets/javascripts/discourse/routes/badges-show.js.es6 index 42bc89e9a..74e04c644 100644 --- a/app/assets/javascripts/discourse/routes/badges-show.js.es6 +++ b/app/assets/javascripts/discourse/routes/badges-show.js.es6 @@ -2,6 +2,11 @@ import UserBadge from 'discourse/models/user-badge'; import Badge from 'discourse/models/badge'; export default Discourse.Route.extend({ + queryParams: { + username: { + refreshModel: true + } + }, actions: { didTransition() { this.controllerFor("badges/show")._showFooter(); @@ -24,10 +29,13 @@ export default Discourse.Route.extend({ } }, - afterModel(model) { - return UserBadge.findByBadgeId(model.get("id")).then(userBadges => { + afterModel(model,transition) { + const username = transition.queryParams && transition.queryParams.username; + + return UserBadge.findByBadgeId(model.get("id"), {username}).then(userBadges => { this.userBadges = userBadges; }); + }, titleToken() { diff --git a/app/assets/javascripts/discourse/templates/badges/show.hbs b/app/assets/javascripts/discourse/templates/badges/show.hbs index 4b245ee2f..7cfada243 100644 --- a/app/assets/javascripts/discourse/templates/badges/show.hbs +++ b/app/assets/javascripts/discourse/templates/badges/show.hbs @@ -9,7 +9,9 @@
{{user-badge badge=model}}
{{{model.displayDescriptionHtml}}}
-
{{i18n 'badges.granted' count=model.grant_count}}
+ {{#unless user}} +
{{i18n 'badges.granted' count=grantCount}}
+ {{/unless}}
{{i18n 'badges.allow_title'}} {{{view.allowTitle}}}
{{i18n 'badges.multiple_grant'}} {{{view.multipleGrant}}}
@@ -22,23 +24,48 @@ {{/if}} + {{#if user}} +
+ {{#link-to 'user' user}} + {{avatar user imageSize="extra_large"}} +
+ {{poster-name post=user}} +
+ {{/link-to}} +
+ {{i18n 'badges.earned_n_times' count=grantCount}} +
+
+ {{/if}} + {{#if userBadges}}
{{#each ub in userBadges}}
- {{#link-to 'user' ub.user classNames="badge-info"}} - {{avatar ub.user imageSize="large"}} -
- {{ub.user.username}} - {{format-date ub.granted_at}} -
- {{/link-to}} + {{#if user}} + {{format-date ub.granted_at}} + {{else}} + {{#link-to 'user' ub.user classNames="badge-info"}} + {{avatar ub.user imageSize="large"}} +
+ {{ub.user.username}} + {{format-date ub.granted_at}} +
+ {{/link-to}} + {{/if}} {{#if ub.post_number}} {{{ub.topic.fancyTitle}}} {{/if}}
{{/each}} + + {{#unless canLoadMore}} + {{#if user}} + {{i18n 'badges.more_with_badge'}} + {{/if}} + {{/unless}} +
{{conditional-loading-spinner condition=canLoadMore}} diff --git a/app/assets/javascripts/discourse/templates/components/user-badge.hbs b/app/assets/javascripts/discourse/templates/components/user-badge.hbs index 69e5393dd..24334c6a7 100644 --- a/app/assets/javascripts/discourse/templates/components/user-badge.hbs +++ b/app/assets/javascripts/discourse/templates/components/user-badge.hbs @@ -1,7 +1,7 @@ -{{#link-to 'badges.show' badge}} + {{#badge-button badge=badge}} {{#if showGrantCount}} (× {{count}}) {{/if}} {{/badge-button}} -{{/link-to}} + diff --git a/app/assets/javascripts/discourse/templates/user-card.hbs b/app/assets/javascripts/discourse/templates/user-card.hbs index 6c0762d22..9b8bf40bb 100644 --- a/app/assets/javascripts/discourse/templates/user-card.hbs +++ b/app/assets/javascripts/discourse/templates/user-card.hbs @@ -68,7 +68,7 @@ {{#if showBadges}}
{{#each ub in user.featured_user_badges}} - {{user-badge badge=ub.badge}} + {{user-badge badge=ub.badge user=user}} {{/each}} {{#if showMoreBadges}} {{#link-to 'user.badges' user class="btn more-user-badges"}} diff --git a/app/assets/javascripts/discourse/templates/user/badges.hbs b/app/assets/javascripts/discourse/templates/user/badges.hbs index e2d943dcb..7cfc318ba 100644 --- a/app/assets/javascripts/discourse/templates/user/badges.hbs +++ b/app/assets/javascripts/discourse/templates/user/badges.hbs @@ -1,5 +1,5 @@
{{#each ub in controller}} - {{user-badge badge=ub.badge count=ub.count}} + {{user-badge badge=ub.badge count=ub.count user=user}} {{/each}}
diff --git a/app/assets/stylesheets/common/base/user-badges.scss b/app/assets/stylesheets/common/base/user-badges.scss index a7d25e877..8f4d7a21b 100644 --- a/app/assets/stylesheets/common/base/user-badges.scss +++ b/app/assets/stylesheets/common/base/user-badges.scss @@ -187,6 +187,58 @@ } } +.show-badge .badge-user-info { + margin-left: 2%; + .earned { + margin-top: 15px; + font-size: 1.3em; + } + .username { + margin-top: 5px; + display: block; + color: dark-light-choose(scale-color($primary, $lightness: 30%), scale-color($secondary, $lightness: 70%)); + } +} + +.show-badge .single-user { + margin-left: 2%; + padding-bottom: 20px; + .load-more { + padding-top: 30px; + display: block; + font-size: 1.2em; + } +} + +.show-badge .single-user .badge-user { + padding-left: 0; + + text-align: left; + display: block; + margin: 20px 0; + .badge-info { + display: none; + } + .date { + display: inline-block; + font-size: 1.1em; + margin-left: 10px; + } + .post-link { + font-size: 1.3em; + width: 500px; + margin: 0; + padding: 0; + } + width: 800px; + + &:after { + content: ""; + clear: both; + display: table; + } +} + .long-description.banner { width: 88%; margin-bottom: 20px; diff --git a/app/controllers/user_badges_controller.rb b/app/controllers/user_badges_controller.rb index 397ce503f..5f1499f91 100644 --- a/app/controllers/user_badges_controller.rb +++ b/app/controllers/user_badges_controller.rb @@ -1,16 +1,28 @@ class UserBadgesController < ApplicationController def index - params.permit [:granted_before, :offset] + params.permit [:granted_before, :offset, :username] badge = fetch_badge_from_params user_badges = badge.user_badges.order('granted_at DESC, id DESC').limit(96) user_badges = user_badges.includes(:user, :granted_by, badge: :badge_type, post: :topic) + grant_count = nil + + if params[:username] + user_id = User.where(username_lower: params[:username].downcase).pluck(:id).first + user_badges = user_badges.where(user_id: user_id) if user_id + grant_count = user_badges.count + end + if offset = params[:offset] user_badges = user_badges.offset(offset.to_i) end - render_serialized(user_badges, UserBadgeSerializer, root: "user_badges", include_long_description: true) + user_badges = UserBadges.new(user_badges: user_badges, + username: params[:username], + grant_count: grant_count) + + render_serialized(user_badges, UserBadgesSerializer, root: :user_badge_info, include_long_description: true) end def username @@ -28,7 +40,7 @@ class UserBadgesController < ApplicationController .includes(post: :topic) .includes(:granted_by) - render_serialized(user_badges, DetailedUserBadgeSerializer, root: "user_badges") + render_serialized(user_badges, DetailedUserBadgeSerializer, root: :user_badges) end def create diff --git a/app/models/user_badges.rb b/app/models/user_badges.rb new file mode 100644 index 000000000..187ee1bc9 --- /dev/null +++ b/app/models/user_badges.rb @@ -0,0 +1,12 @@ +# view model for user badges +class UserBadges + alias :read_attribute_for_serialization :send + + attr_accessor :user_badges, :username, :grant_count + + def initialize(opts={}) + @user_badges = opts[:user_badges] + @username = opts[:username] + @grant_count = opts[:grant_count] + end +end diff --git a/app/serializers/user_badge_serializer.rb b/app/serializers/user_badge_serializer.rb index cb6d2341b..9a5e6d9eb 100644 --- a/app/serializers/user_badge_serializer.rb +++ b/app/serializers/user_badge_serializer.rb @@ -1,9 +1,14 @@ class UserBadgeSerializer < ApplicationSerializer + + class UserSerializer < BasicUserSerializer + attributes :name, :moderator, :admin + end + attributes :id, :granted_at, :count, :post_id, :post_number has_one :badge - has_one :user, serializer: BasicUserSerializer, root: :users - has_one :granted_by, serializer: BasicUserSerializer, root: :users + has_one :user, serializer: UserSerializer, root: :users + has_one :granted_by, serializer: UserSerializer, root: :users has_one :topic, serializer: BasicTopicSerializer def include_count? diff --git a/app/serializers/user_badges_serializer.rb b/app/serializers/user_badges_serializer.rb new file mode 100644 index 000000000..178438b17 --- /dev/null +++ b/app/serializers/user_badges_serializer.rb @@ -0,0 +1,4 @@ +class UserBadgesSerializer < ApplicationSerializer + has_many :user_badges, embed: :objects + attributes :grant_count, :username +end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index fadc6ab72..14f6b2b43 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2746,6 +2746,10 @@ en: mark_watching: 'm, w Watch topic' badges: + earned_n_times: + one: "Earned this badge 1 time" + other: "Earned this badge %{count} times" + more_with_badge: "Others with this badge" title: Badges allow_title: "can be used as a title" multiple_grant: "can be awarded multiple times" diff --git a/spec/controllers/user_badges_controller_spec.rb b/spec/controllers/user_badges_controller_spec.rb index 3ad8840bc..0046f6140 100644 --- a/spec/controllers/user_badges_controller_spec.rb +++ b/spec/controllers/user_badges_controller_spec.rb @@ -12,9 +12,11 @@ describe UserBadgesController do xhr :get, :index, badge_id: badge.id expect(response.status).to eq(200) + parsed = JSON.parse(response.body) expect(parsed["topics"]).to eq(nil) - expect(parsed["user_badges"][0]["post_id"]).to eq(nil) + expect(parsed["badges"].length).to eq(1) + expect(parsed["user_badge_info"]["user_badges"][0]["post_id"]).to eq(nil) end end @@ -38,7 +40,7 @@ describe UserBadgesController do expect(response.status).to eq(200) parsed = JSON.parse(response.body) - expect(parsed["user_badges"].length).to eq(1) + expect(parsed["user_badge_info"]["user_badges"].length).to eq(1) end it 'includes counts when passed the aggregate argument' do