From 5bb3a1a05a1ff35e322ccd4f7e39954456d674ea Mon Sep 17 00:00:00 2001 From: Vikhyat Korrapati Date: Fri, 18 Apr 2014 04:03:13 +0530 Subject: [PATCH 1/6] Show badges filter in user profiles only when the user has badges. --- .../javascripts/discourse/controllers/user_controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/user_controller.js b/app/assets/javascripts/discourse/controllers/user_controller.js index 1054198763b..bc7fb0f3207 100644 --- a/app/assets/javascripts/discourse/controllers/user_controller.js +++ b/app/assets/javascripts/discourse/controllers/user_controller.js @@ -19,8 +19,8 @@ Discourse.UserController = Discourse.ObjectController.extend({ }.property('viewingSelf'), showBadges: function() { - return Discourse.SiteSettings.enable_badges; - }.property(), + return Discourse.SiteSettings.enable_badges && (this.get('content.badge_count') > 0); + }.property('content.badge_count'), privateMessageView: function() { return (this.get('userActionType') === Discourse.UserAction.TYPES.messages_sent) || From 4eb7579d04cfd8e30dd9b5acb70a4c40193b6c17 Mon Sep 17 00:00:00 2001 From: Vikhyat Korrapati Date: Fri, 18 Apr 2014 04:04:24 +0530 Subject: [PATCH 2/6] Notification copy. --- config/locales/client.en.yml | 2 +- config/locales/server.en.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index c493fe74b7e..02dd9a36932 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -598,7 +598,7 @@ en: moved_post: " {{username}} moved {{link}}" total_flagged: "total flagged posts" linked: " {{username}} {{link}}" - granted_badge: " {{link}}" + granted_badge: " You were granted {{link}}" upload_selector: title: "Add an image" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index ac2c6bd5611..fcad786f630 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -888,7 +888,7 @@ en: invited_to_private_message: "%{display_username} invited you to a private message: %{link}" invitee_accepted: "%{display_username} accepted your invitation" linked: "%{display_username} linked you in %{link}" - granted_badge: "You were granted the badge %{link}" + granted_badge: "You were granted %{link}" search: within_post: "#%{post_number} by %{username}: %{excerpt}" From 95b430df72687dbb3d881b19897e22ed21f3cd09 Mon Sep 17 00:00:00 2001 From: Vikhyat Korrapati Date: Fri, 18 Apr 2014 08:24:48 +0530 Subject: [PATCH 3/6] Link poster expansion "+x more" to user badges page. --- .../discourse/templates/poster_expansion.handlebars | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/templates/poster_expansion.handlebars b/app/assets/javascripts/discourse/templates/poster_expansion.handlebars index 874935fda54..9759a5d1105 100644 --- a/app/assets/javascripts/discourse/templates/poster_expansion.handlebars +++ b/app/assets/javascripts/discourse/templates/poster_expansion.handlebars @@ -13,7 +13,9 @@ {{user-badge badge=badge}} {{/each}} {{#if showMoreBadges}} - {{i18n badges.more_badges count=moreBadgesCount}} + {{#link-to 'user.badges' user class="btn more-user-badges"}} + {{i18n badges.more_badges count=moreBadgesCount}} + {{/link-to}} {{/if}} {{/if}} From 8113e8d89783b50cb9285974230ea5e2615cbbaf Mon Sep 17 00:00:00 2001 From: Vikhyat Korrapati Date: Fri, 18 Apr 2014 08:40:53 +0530 Subject: [PATCH 4/6] Basic UI for selecting gold/silver badges as titles. --- .../preferences_badge_title_controller.js | 64 +++++++++++++++++++ .../controllers/preferences_controller.js | 11 ++++ .../discourse/routes/application_routes.js | 1 + .../discourse/routes/preferences_routes.js | 42 +++++++++++- .../templates/user/badge-title.js.handlebars | 25 ++++++++ .../templates/user/preferences.js.handlebars | 10 +++ app/controllers/users_controller.rb | 15 +++++ app/services/badge_granter.rb | 11 +++- config/locales/client.en.yml | 1 + config/routes.rb | 2 + spec/services/badge_granter_spec.rb | 4 +- 11 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 app/assets/javascripts/discourse/controllers/preferences_badge_title_controller.js create mode 100644 app/assets/javascripts/discourse/templates/user/badge-title.js.handlebars diff --git a/app/assets/javascripts/discourse/controllers/preferences_badge_title_controller.js b/app/assets/javascripts/discourse/controllers/preferences_badge_title_controller.js new file mode 100644 index 00000000000..85352626d87 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/preferences_badge_title_controller.js @@ -0,0 +1,64 @@ +/** + Controller for selecting a badge to use as your title. + + @class PreferencesBadgeTitleController + @extends Ember.ArrayController + @namespace Discourse + @module Discourse +**/ +Discourse.PreferencesBadgeTitleController = Ember.ArrayController.extend({ + saving: false, + saved: false, + + savingStatus: function() { + if (this.get('saving')) { + return I18n.t('saving'); + } else { + return I18n.t('save'); + } + }.property('saving'), + + selectableUserBadges: Em.computed.filter('model', function(userBadge) { + var badgeType = userBadge.get('badge.badge_type.name'); + return (badgeType === "Gold" || badgeType === "Silver"); + }), + + selectedUserBadge: function() { + var selectedUserBadgeId = parseInt(this.get('selectedUserBadgeId')); + var selectedUserBadge = null; + this.get('selectableUserBadges').forEach(function(userBadge) { + if (userBadge.get('id') === selectedUserBadgeId) { + selectedUserBadge = userBadge; + } + }); + return selectedUserBadge; + }.property('selectedUserBadgeId'), + + titleNotChanged: function() { + return this.get('user.title') === this.get('selectedUserBadge.badge.name'); + }.property('selectedUserBadge', 'user.title'), + + disableSave: Em.computed.or('saving', 'titleNotChanged'), + + actions: { + save: function() { + var self = this; + + self.set('saved', false); + self.set('saving', true); + + Discourse.ajax("/users/" + self.get('user.username_lower') + "/preferences/badge_title", { + type: "PUT", + data: { + user_badge_id: self.get('selectedUserBadgeId') + } + }).then(function() { + self.set('saved', true); + self.set('saving', false); + self.set('user.title', self.get('selectedUserBadge.badge.name')); + }, function() { + bootbox.alert(I18n.t('generic_error')); + }); + } + } +}); diff --git a/app/assets/javascripts/discourse/controllers/preferences_controller.js b/app/assets/javascripts/discourse/controllers/preferences_controller.js index c5f6c143054..1467b87ba97 100644 --- a/app/assets/javascripts/discourse/controllers/preferences_controller.js +++ b/app/assets/javascripts/discourse/controllers/preferences_controller.js @@ -38,6 +38,17 @@ Discourse.PreferencesController = Discourse.ObjectController.extend({ return Discourse.SiteSettings.enable_names; }.property(), + canSelectTitle: function() { + if (!Discourse.SiteSettings.enable_badges || this.get('model.badge_count') === 0) { + return false; + } + + // If the first featured badge isn't gold or silver we know the user won't have + // _any_ gold or silver badges. + var badgeType = this.get('model.featured_user_badges')[0].get('badge.badge_type.name'); + return (badgeType === "Gold" || badgeType === "Silver"); + }.property('model.badge_count', 'model.featured_user_badges.@each.badge.badge_type.name'), + availableLocales: function() { return Discourse.SiteSettings.available_locales.split('|').map( function(s) { return {name: s, value: s}; diff --git a/app/assets/javascripts/discourse/routes/application_routes.js b/app/assets/javascripts/discourse/routes/application_routes.js index 9e46bfb9b75..e14386703af 100644 --- a/app/assets/javascripts/discourse/routes/application_routes.js +++ b/app/assets/javascripts/discourse/routes/application_routes.js @@ -81,6 +81,7 @@ Discourse.Route.buildRoutes(function() { this.route('username'); this.route('email'); this.route('about', { path: '/about-me' }); + this.route('badgeTitle', { path: '/badge_title' }); }); this.route('invited'); diff --git a/app/assets/javascripts/discourse/routes/preferences_routes.js b/app/assets/javascripts/discourse/routes/preferences_routes.js index fbf0d63b2a7..ef59b99d418 100644 --- a/app/assets/javascripts/discourse/routes/preferences_routes.js +++ b/app/assets/javascripts/discourse/routes/preferences_routes.js @@ -44,7 +44,7 @@ Discourse.PreferencesRoute = Discourse.RestrictedUserRoute.extend({ user.set('avatar_template', avatarSelector.get('avatarTemplate')); avatarSelector.send('closeModal'); }, - + showProfileBackgroundFileSelector: function() { $("#profile-background-input").click(); }, @@ -161,3 +161,43 @@ Discourse.PreferencesUsernameRoute = Discourse.RestrictedUserRoute.extend({ controller.setProperties({ model: user, newUsername: user.get('username') }); } }); + +/** + The route for updating a user's title to one of their badges + + @class PreferencesBadgeTitleRoute + @extends Discourse.RestrictedUserRoute + @namespace Discourse + @module Discourse +**/ +Discourse.PreferencesBadgeTitleRoute = Discourse.RestrictedUserRoute.extend({ + model: function() { + return Discourse.UserBadge.findByUsername(this.modelFor('user').get('username')); + }, + + renderTemplate: function() { + return this.render('user/badge-title', { into: 'user', outlet: 'userOutlet' }); + }, + + // A bit odd, but if we leave to /preferences we need to re-render that outlet + deactivate: function() { + this._super(); + this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' }); + }, + + setupController: function(controller, model) { + controller.set('model', model); + controller.set('user', this.modelFor('user')); + + model.forEach(function(userBadge) { + if (userBadge.get('badge.name') === controller.get('user.title')) { + controller.set('selectedUserBadgeId', userBadge.get('id')); + } + }); + if (!controller.get('selectedUserBadgeId')) { + controller.set('selectedUserBadgeId', controller.get('selectableUserBadges')[0].get('id')); + } + } +}); + + diff --git a/app/assets/javascripts/discourse/templates/user/badge-title.js.handlebars b/app/assets/javascripts/discourse/templates/user/badge-title.js.handlebars new file mode 100644 index 00000000000..6bf807b8de1 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/user/badge-title.js.handlebars @@ -0,0 +1,25 @@ +
+
+ +
+
+

{{i18n badges.select_badge_for_title}}

+
+
+ +
+ +
+ {{combobox valueAttribute="id" value=selectedUserBadgeId nameProperty="badge.name" content=selectableUserBadges}} +
+
+ +
+
+ + {{#if saved}}{{i18n saved}}{{/if}} +
+
+ +
+
diff --git a/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars b/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars index be9d804b6cd..846b486473a 100644 --- a/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars +++ b/app/assets/javascripts/discourse/templates/user/preferences.js.handlebars @@ -37,6 +37,16 @@ {{/if}} + {{#if canSelectTitle}} +
+ +
+ {{title}} + {{#link-to "preferences.badgeTitle" class="btn pad-left"}}{{/link-to}} +
+
+ {{/if}} +
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 24a3d9443ae..9aca2800fc9 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -61,6 +61,21 @@ class UsersController < ApplicationController render nothing: true end + def badge_title + params.require(:user_badge_id) + + user = fetch_user_from_params + guardian.ensure_can_edit!(user) + + user_badge = UserBadge.find(params[:user_badge_id]) + if user_badge.user == user && ["Gold", "Silver"].include?(user_badge.badge.badge_type.name) + user.title = user_badge.badge.name + user.save! + end + + render nothing: true + end + def preferences render nothing: true end diff --git a/app/services/badge_granter.rb b/app/services/badge_granter.rb index 53b3d1fb965..8b4de1a54f7 100644 --- a/app/services/badge_granter.rb +++ b/app/services/badge_granter.rb @@ -40,8 +40,15 @@ class BadgeGranter if options[:revoked_by] StaffActionLogger.new(options[:revoked_by]).log_badge_revoke(user_badge) end - # Revoke badge -- This is inefficient, but not very easy to optimize unless - # the data hash is converted into a hstore. + + # If the user's title is the same as the badge name, remove their title. + if user_badge.user.title == user_badge.badge.name + user_badge.user.title = nil + user_badge.user.save! + end + + # Delete notification -- This is inefficient, but not very easy to optimize + # unless the data hash is converted into a hstore. notification = user_badge.user.notifications.where(notification_type: Notification.types[:granted_badge]).where("data LIKE ?", "%" + user_badge.badge_id.to_s + "%").select {|n| n.data_hash["badge_id"] == user_badge.badge_id }.first notification && notification.destroy end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 02dd9a36932..b4f8745c881 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1779,6 +1779,7 @@ en: awarded: one: "1 awarded" other: "%{count} awarded" + select_badge_for_title: Select a badge to use as your title example_badge: name: Example Badge description: This is a generic example badge. diff --git a/config/routes.rb b/config/routes.rb index 137e2434795..4d6a44ff475 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -184,6 +184,8 @@ Discourse::Application.routes.draw do get "users/:username/preferences/email" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT} put "users/:username/preferences/email" => "users#change_email", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/preferences/about-me" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT} + get "users/:username/preferences/badge_title" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT} + put "users/:username/preferences/badge_title" => "users#badge_title", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/preferences/username" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT} put "users/:username/preferences/username" => "users#username", constraints: {username: USERNAME_ROUTE_FORMAT} get "users/:username/avatar(/:size)" => "users#avatar", constraints: {username: USERNAME_ROUTE_FORMAT} # LEGACY ROUTE diff --git a/spec/services/badge_granter_spec.rb b/spec/services/badge_granter_spec.rb index 476bc6a4b4a..dcdbd7008c3 100644 --- a/spec/services/badge_granter_spec.rb +++ b/spec/services/badge_granter_spec.rb @@ -53,13 +53,15 @@ describe BadgeGranter do let(:admin) { Fabricate(:admin) } let!(:user_badge) { BadgeGranter.grant(badge, user) } - it 'revokes the badge, deletes the notification and decrements grant_count' do + it 'revokes the badge and does necessary cleanup' do + user.title = badge.name; user.save! badge.reload.grant_count.should eq(1) StaffActionLogger.any_instance.expects(:log_badge_revoke).with(user_badge) BadgeGranter.revoke(user_badge, revoked_by: admin) UserBadge.where(user: user, badge: badge).first.should_not be_present badge.reload.grant_count.should eq(0) user.notifications.where(notification_type: Notification.types[:granted_badge]).should be_empty + user.reload.title.should == nil end end From e6eb16ee73c99d4afc45baf1271c8a6f41948c08 Mon Sep 17 00:00:00 2001 From: Vikhyat Korrapati Date: Fri, 18 Apr 2014 09:10:18 +0530 Subject: [PATCH 5/6] Fix infinite loading spinner for badges that have not been granted yet. --- app/assets/javascripts/discourse/routes/badges_show_route.js | 1 + .../javascripts/discourse/templates/badges/show.js.handlebars | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/routes/badges_show_route.js b/app/assets/javascripts/discourse/routes/badges_show_route.js index d0769325040..88e022cb187 100644 --- a/app/assets/javascripts/discourse/routes/badges_show_route.js +++ b/app/assets/javascripts/discourse/routes/badges_show_route.js @@ -18,6 +18,7 @@ Discourse.BadgesShowRoute = Ember.Route.extend({ setupController: function(controller, model) { Discourse.UserBadge.findByBadgeId(model.get('id')).then(function(userBadges) { controller.set('userBadges', userBadges); + controller.set('userBadgesLoaded', true); }); controller.set('model', model); } diff --git a/app/assets/javascripts/discourse/templates/badges/show.js.handlebars b/app/assets/javascripts/discourse/templates/badges/show.js.handlebars index 7f0b70167f8..22681594ed4 100644 --- a/app/assets/javascripts/discourse/templates/badges/show.js.handlebars +++ b/app/assets/javascripts/discourse/templates/badges/show.js.handlebars @@ -22,6 +22,8 @@ {{/link-to}} {{/each}} {{else}} -
{{i18n loading}}
+ {{#unless userBadgesLoaded}} +
{{i18n loading}}
+ {{/unless}} {{/if}}
From 27b62df48bbe4053dccc2da0d2c9004393c0e0ae Mon Sep 17 00:00:00 2001 From: Vikhyat Korrapati Date: Fri, 18 Apr 2014 09:19:10 +0530 Subject: [PATCH 6/6] Change "awarded" to "granted". --- .../discourse/templates/badges/index.js.handlebars | 2 +- .../discourse/templates/badges/show.js.handlebars | 2 +- app/assets/stylesheets/desktop/user-badges.scss | 1 + config/locales/client.en.yml | 6 +++--- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/discourse/templates/badges/index.js.handlebars b/app/assets/javascripts/discourse/templates/badges/index.js.handlebars index 39af8480a40..2317de8a9b0 100644 --- a/app/assets/javascripts/discourse/templates/badges/index.js.handlebars +++ b/app/assets/javascripts/discourse/templates/badges/index.js.handlebars @@ -6,7 +6,7 @@ {{user-badge badge=this}} {{description}} - {{i18n badges.awarded count=grant_count}} + {{i18n badges.granted count=grant_count}} {{/each}} diff --git a/app/assets/javascripts/discourse/templates/badges/show.js.handlebars b/app/assets/javascripts/discourse/templates/badges/show.js.handlebars index 22681594ed4..3c0a0d84f4e 100644 --- a/app/assets/javascripts/discourse/templates/badges/show.js.handlebars +++ b/app/assets/javascripts/discourse/templates/badges/show.js.handlebars @@ -9,7 +9,7 @@ {{user-badge badge=this}} {{description}} - {{i18n badges.awarded count=grant_count}} + {{i18n badges.granted count=grant_count}} diff --git a/app/assets/stylesheets/desktop/user-badges.scss b/app/assets/stylesheets/desktop/user-badges.scss index 2be02acb1fb..632ccf6502c 100644 --- a/app/assets/stylesheets/desktop/user-badges.scss +++ b/app/assets/stylesheets/desktop/user-badges.scss @@ -63,6 +63,7 @@ table.badges-listing { td.grant-count { font-size: 0.8em; color: $secondary_text_color; + text-align: right; } td.badge, td.grant-count { diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index b4f8745c881..11ee529292f 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1776,9 +1776,9 @@ en: more_badges: one: "+1 More" other: "+%{count} More" - awarded: - one: "1 awarded" - other: "%{count} awarded" + granted: + one: "1 granted" + other: "%{count} granted" select_badge_for_title: Select a badge to use as your title example_badge: name: Example Badge