FEATURE: Allow users to select a badge with an image to appear on their
user card
This commit is contained in:
parent
d2fb2bc4cd
commit
71f211f0b3
|
@ -26,7 +26,7 @@ export default Ember.ObjectController.extend(BufferedContent, {
|
|||
'listable', 'auto_revoke',
|
||||
'enabled', 'show_posts',
|
||||
'target_posts', 'name', 'description',
|
||||
'icon', 'query', 'badge_grouping_id',
|
||||
'icon', 'image', 'query', 'badge_grouping_id',
|
||||
'trigger', 'badge_type_id'],
|
||||
self = this;
|
||||
|
||||
|
|
|
@ -18,6 +18,12 @@
|
|||
<p class='help'>{{i18n admin.badges.icon_help}}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="name">{{i18n admin.badges.image}}</label>
|
||||
{{input type="text" name="name" value=buffered.image}}
|
||||
<p class='help'>{{i18n admin.badges.icon_help}}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="badge_type_id">{{i18n admin.badges.badge_type}}</label>
|
||||
{{view Ember.Select name="badge_type_id"
|
||||
|
|
|
@ -3,7 +3,5 @@ export default Ember.Component.extend({
|
|||
classNameBindings: [':user-badge', 'badge.badgeTypeClassName'],
|
||||
title: Em.computed.alias('badge.displayDescription'),
|
||||
attributeBindings: ['data-badge-name', 'title'],
|
||||
'data-badge-name': Em.computed.alias('badge.name'),
|
||||
|
||||
isIcon: Em.computed.match('badge.icon', /^fa-/)
|
||||
'data-badge-name': Em.computed.alias('badge.name')
|
||||
});
|
||||
|
|
|
@ -1,63 +1,25 @@
|
|||
/**
|
||||
Controller for selecting a badge to use as your title.
|
||||
import BadgeSelectController from "discourse/mixins/badge-select-controller";
|
||||
|
||||
@class PreferencesBadgeTitleController
|
||||
@extends Ember.ArrayController
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default Ember.ArrayController.extend({
|
||||
saving: false,
|
||||
saved: false,
|
||||
export default Ember.ArrayController.extend(BadgeSelectController, {
|
||||
|
||||
savingStatus: function() {
|
||||
if (this.get('saving')) {
|
||||
return I18n.t('saving');
|
||||
} else {
|
||||
return I18n.t('save');
|
||||
}
|
||||
}.property('saving'),
|
||||
|
||||
selectableUserBadges: function() {
|
||||
var items = this.get('model').filterBy('badge.allow_title', true);
|
||||
items = _.uniq(items, false, function(e) { return e.get('badge.name'); });
|
||||
items.unshiftObject(Em.Object.create({
|
||||
badge: Discourse.Badge.create({name: I18n.t('badges.no_title')})
|
||||
}));
|
||||
return items;
|
||||
filteredList: function() {
|
||||
return this.get('model').filterBy('badge.allow_title', true);
|
||||
}.property('model'),
|
||||
|
||||
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: Discourse.computed.propertyEqual('user.title', 'selectedUserBadge.badge.name'),
|
||||
|
||||
disableSave: Em.computed.or('saving', 'titleNotChanged'),
|
||||
|
||||
actions: {
|
||||
save: function() {
|
||||
this.setProperties({ saved: false, saving: true });
|
||||
|
||||
var self = this;
|
||||
|
||||
self.set('saved', false);
|
||||
self.set('saving', true);
|
||||
|
||||
Discourse.ajax("/users/" + self.get('user.username_lower') + "/preferences/badge_title", {
|
||||
Discourse.ajax(this.get('user.path') + "/preferences/badge_title", {
|
||||
type: "PUT",
|
||||
data: {
|
||||
user_badge_id: self.get('selectedUserBadgeId')
|
||||
}
|
||||
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'));
|
||||
self.setProperties({
|
||||
saved: true,
|
||||
saving: false,
|
||||
"user.title": self.get('selectedUserBadge.badge.name')
|
||||
});
|
||||
}, function() {
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
});
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import BadgeSelectController from "discourse/mixins/badge-select-controller";
|
||||
|
||||
export default Ember.ArrayController.extend(BadgeSelectController, {
|
||||
filteredList: function() {
|
||||
return this.get('model').filter(function(b) {
|
||||
return !Em.empty(b.get('badge.image'));
|
||||
});
|
||||
}.property('model'),
|
||||
|
||||
actions: {
|
||||
save: function() {
|
||||
this.setProperties({ saved: false, saving: true });
|
||||
|
||||
var self = this;
|
||||
Discourse.ajax(this.get('user.path') + "/preferences/card-badge", {
|
||||
type: "PUT",
|
||||
data: { user_badge_id: self.get('selectedUserBadgeId') }
|
||||
}).then(function() {
|
||||
self.setProperties({
|
||||
saved: true,
|
||||
saving: false,
|
||||
"user.card_image_badge": self.get('selectedUserBadge.badge.image')
|
||||
});
|
||||
}).catch(function() {
|
||||
self.set('saving', false);
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
export default Ember.Handlebars.makeBoundHelper(function(str) {
|
||||
if (Em.empty(str)) { return ""; }
|
||||
|
||||
if (str.indexOf('fa-') === 0) {
|
||||
return new Handlebars.SafeString("<i class='fa " + str + "'></i>");
|
||||
} else {
|
||||
return new Handlebars.SafeString("<img src='" + str + "'>");
|
||||
}
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
export default Ember.Mixin.create({
|
||||
saving: false,
|
||||
saved: false,
|
||||
|
||||
selectableUserBadges: function() {
|
||||
var items = this.get('filteredList');
|
||||
items = _.uniq(items, false, function(e) { return e.get('badge.name'); });
|
||||
items.unshiftObject(Em.Object.create({
|
||||
badge: Discourse.Badge.create({name: I18n.t('badges.none')})
|
||||
}));
|
||||
return items;
|
||||
}.property('filteredList'),
|
||||
|
||||
savingStatus: function() {
|
||||
if (this.get('saving')) {
|
||||
return I18n.t('saving');
|
||||
} else {
|
||||
return I18n.t('save');
|
||||
}
|
||||
}.property('saving'),
|
||||
|
||||
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'),
|
||||
|
||||
disableSave: Em.computed.alias('saving')
|
||||
});
|
||||
|
|
@ -338,6 +338,10 @@ Discourse.User = Discourse.Model.extend({
|
|||
});
|
||||
}
|
||||
|
||||
if (json.user.card_badge) {
|
||||
json.user.card_badge = Discourse.Badge.create(json.user.card_badge);
|
||||
}
|
||||
|
||||
user.setProperties(json.user);
|
||||
return user;
|
||||
});
|
||||
|
|
|
@ -84,6 +84,7 @@ Discourse.Route.buildRoutes(function() {
|
|||
this.route('email');
|
||||
this.route('about', { path: '/about-me' });
|
||||
this.route('badgeTitle', { path: '/badge_title' });
|
||||
this.route('card-badge', { path: '/card-badge' });
|
||||
});
|
||||
|
||||
this.route('invited');
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
export default Discourse.RestrictedUserRoute.extend({
|
||||
model: function() {
|
||||
return this.modelFor('user');
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
this.render({ into: 'user', outlet: 'userOutlet' });
|
||||
},
|
||||
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties({ model: model, newBio: model.get('bio_raw') });
|
||||
},
|
||||
|
||||
// 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' });
|
||||
},
|
||||
|
||||
actions: {
|
||||
changeAbout: function() {
|
||||
var route = this;
|
||||
var controller = route.controllerFor('preferences/about');
|
||||
|
||||
controller.setProperties({ saving: true });
|
||||
return controller.get('model').save().then(function() {
|
||||
controller.set('saving', false);
|
||||
route.transitionTo('user.index');
|
||||
}, function() {
|
||||
// model failed to save
|
||||
controller.set('saving', false);
|
||||
alert(I18n.t('generic_error'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
export default 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.get('selectableUserBadges.length') > 0) {
|
||||
controller.set('selectedUserBadgeId', controller.get('selectableUserBadges')[0].get('id'));
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
export default Discourse.RestrictedUserRoute.extend({
|
||||
model: function() {
|
||||
return Discourse.UserBadge.findByUsername(this.modelFor('user').get('username'));
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
return this.render({ 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.image') === controller.get('user.card_image_badge')) {
|
||||
controller.set('selectedUserBadgeId', userBadge.get('id'));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
export default Discourse.RestrictedUserRoute.extend({
|
||||
model: function() {
|
||||
return this.modelFor('user');
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
this.render({ into: 'user', outlet: 'userOutlet' });
|
||||
},
|
||||
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties({ model: model, newEmail: model.get('email') });
|
||||
},
|
||||
|
||||
// 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' });
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export default Discourse.RestrictedUserRoute.extend({
|
||||
renderTemplate: function() {
|
||||
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
|
||||
}
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
export default Discourse.RestrictedUserRoute.extend({
|
||||
model: function() {
|
||||
return this.modelFor('user');
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
return this.render({ 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, user) {
|
||||
controller.setProperties({ model: user, newUsername: user.get('username') });
|
||||
}
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
export default Discourse.RestrictedUserRoute.extend({
|
||||
model: function() {
|
||||
return this.modelFor('user');
|
||||
},
|
||||
|
||||
setupController: function(controller, user) {
|
||||
controller.setProperties({ model: user, newNameInput: user.get('name') });
|
||||
this.controllerFor('user').set('indexStream', false);
|
||||
},
|
||||
|
||||
actions: {
|
||||
showAvatarSelector: function() {
|
||||
Discourse.Route.showModal(this, 'avatar-selector');
|
||||
// all the properties needed for displaying the avatar selector modal
|
||||
var controller = this.controllerFor('avatar-selector');
|
||||
var user = this.modelFor('user');
|
||||
var props = user.getProperties(
|
||||
'username', 'email',
|
||||
'uploaded_avatar_id',
|
||||
'system_avatar_upload_id',
|
||||
'gravatar_avatar_upload_id',
|
||||
'custom_avatar_upload_id'
|
||||
);
|
||||
|
||||
switch(props.uploaded_avatar_id){
|
||||
case props.system_avatar_upload_id:
|
||||
props.selected = "system";
|
||||
break;
|
||||
case props.gravatar_avatar_upload_id:
|
||||
props.selected = "gravatar";
|
||||
break;
|
||||
default:
|
||||
props.selected = "uploaded";
|
||||
}
|
||||
|
||||
controller.setProperties(props);
|
||||
},
|
||||
|
||||
saveAvatarSelection: function() {
|
||||
var user = this.modelFor('user');
|
||||
var avatarSelector = this.controllerFor('avatar-selector');
|
||||
|
||||
|
||||
// sends the information to the server if it has changed
|
||||
if (avatarSelector.get('selectedUploadId') !== user.get('uploaded_avatar_id')) {
|
||||
user.pickAvatar(avatarSelector.get('selectedUploadId'));
|
||||
}
|
||||
|
||||
// saves the data back
|
||||
user.setProperties(avatarSelector.getProperties(
|
||||
'system_avatar_upload_id',
|
||||
'gravatar_avatar_upload_id',
|
||||
'custom_avatar_upload_id'
|
||||
));
|
||||
avatarSelector.send('closeModal');
|
||||
},
|
||||
|
||||
}
|
||||
});
|
|
@ -1,215 +0,0 @@
|
|||
/**
|
||||
The common route stuff for a user's preference
|
||||
|
||||
@class PreferencesRoute
|
||||
@extends Discourse.RestrictedUserRoute
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.PreferencesRoute = Discourse.RestrictedUserRoute.extend({
|
||||
model: function() {
|
||||
return this.modelFor('user');
|
||||
},
|
||||
|
||||
setupController: function(controller, user) {
|
||||
controller.setProperties({ model: user, newNameInput: user.get('name') });
|
||||
this.controllerFor('user').set('indexStream', false);
|
||||
},
|
||||
|
||||
actions: {
|
||||
showAvatarSelector: function() {
|
||||
Discourse.Route.showModal(this, 'avatar-selector');
|
||||
// all the properties needed for displaying the avatar selector modal
|
||||
var controller = this.controllerFor('avatar-selector');
|
||||
var user = this.modelFor('user');
|
||||
var props = user.getProperties(
|
||||
'username', 'email',
|
||||
'uploaded_avatar_id',
|
||||
'system_avatar_upload_id',
|
||||
'gravatar_avatar_upload_id',
|
||||
'custom_avatar_upload_id'
|
||||
);
|
||||
|
||||
switch(props.uploaded_avatar_id){
|
||||
case props.system_avatar_upload_id:
|
||||
props.selected = "system";
|
||||
break;
|
||||
case props.gravatar_avatar_upload_id:
|
||||
props.selected = "gravatar";
|
||||
break;
|
||||
default:
|
||||
props.selected = "uploaded";
|
||||
}
|
||||
|
||||
controller.setProperties(props);
|
||||
},
|
||||
|
||||
saveAvatarSelection: function() {
|
||||
var user = this.modelFor('user');
|
||||
var avatarSelector = this.controllerFor('avatar-selector');
|
||||
|
||||
|
||||
// sends the information to the server if it has changed
|
||||
if (avatarSelector.get('selectedUploadId') !== user.get('uploaded_avatar_id')) {
|
||||
user.pickAvatar(avatarSelector.get('selectedUploadId'));
|
||||
}
|
||||
|
||||
// saves the data back
|
||||
user.setProperties(avatarSelector.getProperties(
|
||||
'system_avatar_upload_id',
|
||||
'gravatar_avatar_upload_id',
|
||||
'custom_avatar_upload_id'
|
||||
));
|
||||
avatarSelector.send('closeModal');
|
||||
},
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
Discourse.PreferencesIndexRoute = Discourse.RestrictedUserRoute.extend({
|
||||
renderTemplate: function() {
|
||||
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
The route for editing a user's "About Me" bio.
|
||||
|
||||
@class PreferencesAboutRoute
|
||||
@extends Discourse.RestrictedUserRoute
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.PreferencesAboutRoute = Discourse.RestrictedUserRoute.extend({
|
||||
model: function() {
|
||||
return this.modelFor('user');
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
this.render({ into: 'user', outlet: 'userOutlet' });
|
||||
},
|
||||
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties({ model: model, newBio: model.get('bio_raw') });
|
||||
},
|
||||
|
||||
// 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' });
|
||||
},
|
||||
|
||||
actions: {
|
||||
changeAbout: function() {
|
||||
var route = this;
|
||||
var controller = route.controllerFor('preferences/about');
|
||||
|
||||
controller.setProperties({ saving: true });
|
||||
return controller.get('model').save().then(function() {
|
||||
controller.set('saving', false);
|
||||
route.transitionTo('user.index');
|
||||
}, function() {
|
||||
// model failed to save
|
||||
controller.set('saving', false);
|
||||
alert(I18n.t('generic_error'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
The route for editing a user's email
|
||||
|
||||
@class PreferencesEmailRoute
|
||||
@extends Discourse.RestrictedUserRoute
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.PreferencesEmailRoute = Discourse.RestrictedUserRoute.extend({
|
||||
model: function() {
|
||||
return this.modelFor('user');
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
this.render({ into: 'user', outlet: 'userOutlet' });
|
||||
},
|
||||
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties({ model: model, newEmail: model.get('email') });
|
||||
},
|
||||
|
||||
// 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' });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
The route for updating a user's username
|
||||
|
||||
@class PreferencesUsernameRoute
|
||||
@extends Discourse.RestrictedUserRoute
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.PreferencesUsernameRoute = Discourse.RestrictedUserRoute.extend({
|
||||
model: function() {
|
||||
return this.modelFor('user');
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
return this.render({ 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, user) {
|
||||
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.get('selectableUserBadges.length') > 0) {
|
||||
controller.set('selectedUserBadgeId', controller.get('selectableUserBadges')[0].get('id'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -1,7 +1,3 @@
|
|||
{{#if isIcon}}
|
||||
<i {{bind-attr class=":fa badge.icon"}}></i>
|
||||
{{else}}
|
||||
<img {{bind-attr src="badge.icon"}}>
|
||||
{{/if}}
|
||||
{{icon-or-image badge.icon}}
|
||||
{{badge.displayName}}
|
||||
{{yield}}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<section class='user-content'>
|
||||
<form class="form-horizontal">
|
||||
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<h3>{{i18n user.card_badge.title}}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="control-label"></label>
|
||||
<div class="controls">
|
||||
{{combo-box valueAttribute="id" value=selectedUserBadgeId nameProperty="badge.displayName" content=selectableUserBadges}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<button class="btn btn-primary" {{bind-attr disabled=disableSave}} {{action "save"}}>{{savingStatus}}</button>
|
||||
{{#if saved}}{{i18n saved}}{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</section>
|
|
@ -55,6 +55,12 @@
|
|||
{{#if hasUserFilters}}
|
||||
<button class='btn' {{action "cancelFilter"}}>{{fa-icon "times"}}{{i18n topic.filters.cancel}}</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if user.card_badge}}
|
||||
{{#link-to 'badges.show' user.card_badge class="card-badge"}}
|
||||
{{icon-or-image user.card_badge.image}}
|
||||
{{/link-to}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<p class='loading'>{{i18n loading}}</p>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<label class="control-label">{{i18n user.title.title}}</label>
|
||||
<div class="controls">
|
||||
<span class="static">{{title}}</span>
|
||||
{{#link-to "preferences.badgeTitle" class="btn btn-small pad-left no-text"}}<i class="fa fa-pencil"></i>{{/link-to}}
|
||||
{{#link-to "preferences.badgeTitle" class="btn btn-small pad-left no-text"}}{{fa-icon "pencil"}}{{/link-to}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
@ -157,6 +157,16 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group pref-card-badge">
|
||||
<label class="control-label">{{i18n user.card_badge.title}}</label>
|
||||
<div class="controls">
|
||||
{{#if card_image_badge}}
|
||||
{{icon-or-image card_image_badge}}
|
||||
{{/if}}
|
||||
{{#link-to "preferences.card-badge" class="btn btn-small pad-left no-text"}}{{fa-icon "pencil"}}{{/link-to}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group pref-email-settings">
|
||||
<label class="control-label">{{i18n user.email_settings}}</label>
|
||||
{{preference-checkbox labelKey="user.email_digests.title" checked=email_digests}}
|
||||
|
|
|
@ -145,4 +145,12 @@
|
|||
color: $danger;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.card-badge {
|
||||
img {
|
||||
max-width: 100px;
|
||||
}
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,23 @@ class UsersController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def card_badge
|
||||
end
|
||||
|
||||
def update_card_badge
|
||||
user = fetch_user_from_params
|
||||
guardian.ensure_can_edit!(user)
|
||||
|
||||
user_badge = UserBadge.find_by(id: params[:user_badge_id].to_i)
|
||||
if user_badge && user_badge.user == user && user_badge.badge.image.present?
|
||||
user.user_profile.update_column(:card_image_badge_id, user_badge.badge.id)
|
||||
else
|
||||
user.user_profile.update_column(:card_image_badge_id, nil)
|
||||
end
|
||||
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
def user_preferences_redirect
|
||||
redirect_to email_preferences_path(current_user.username_lower)
|
||||
end
|
||||
|
|
|
@ -6,6 +6,8 @@ class UserProfile < ActiveRecord::Base
|
|||
before_save :cook
|
||||
after_save :trigger_badges
|
||||
|
||||
belongs_to :card_image_badge, class_name: 'Badge'
|
||||
|
||||
BAKED_VERSION = 1
|
||||
|
||||
def bio_excerpt
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class BadgeSerializer < ApplicationSerializer
|
||||
attributes :id, :name, :description, :grant_count, :allow_title,
|
||||
:multiple_grant, :icon, :listable, :enabled, :badge_grouping_id,
|
||||
:multiple_grant, :icon, :image, :listable, :enabled, :badge_grouping_id,
|
||||
:system
|
||||
has_one :badge_type
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ class UserSerializer < BasicUserSerializer
|
|||
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
|
||||
has_one :card_badge, embed: :object, serializer: BadgeSerializer
|
||||
|
||||
staff_attributes :number_of_deleted_posts,
|
||||
:number_of_flagged_posts,
|
||||
|
@ -80,7 +81,9 @@ class UserSerializer < BasicUserSerializer
|
|||
:disable_jump_reply,
|
||||
:gravatar_avatar_upload_id,
|
||||
:custom_avatar_upload_id,
|
||||
:has_title_badges
|
||||
:has_title_badges,
|
||||
:card_image_badge,
|
||||
:card_image_badge_id
|
||||
|
||||
###
|
||||
### ATTRIBUTES
|
||||
|
@ -90,6 +93,11 @@ class UserSerializer < BasicUserSerializer
|
|||
object.id && object.id == scope.user.try(:id)
|
||||
end
|
||||
|
||||
def card_badge
|
||||
object.user_profile.card_image_badge
|
||||
end
|
||||
|
||||
|
||||
def bio_raw
|
||||
object.user_profile.bio_raw
|
||||
end
|
||||
|
@ -110,6 +118,22 @@ class UserSerializer < BasicUserSerializer
|
|||
website.present?
|
||||
end
|
||||
|
||||
def card_image_badge_id
|
||||
object.user_profile.card_image_badge.try(:id)
|
||||
end
|
||||
|
||||
def include_card_image_badge_id?
|
||||
card_image_badge_id.present?
|
||||
end
|
||||
|
||||
def card_image_badge
|
||||
object.user_profile.card_image_badge.try(:image)
|
||||
end
|
||||
|
||||
def include_card_image_badge?
|
||||
card_image_badge.present?
|
||||
end
|
||||
|
||||
def profile_background
|
||||
object.user_profile.profile_background
|
||||
end
|
||||
|
|
|
@ -418,6 +418,9 @@ en:
|
|||
created: "Joined"
|
||||
log_out: "Log Out"
|
||||
location: "Location"
|
||||
card_badge:
|
||||
title: "User Card Badge"
|
||||
|
||||
website: "Web Site"
|
||||
email_settings: "Email"
|
||||
email_digests:
|
||||
|
@ -2106,6 +2109,7 @@ en:
|
|||
listable: Show badge on the public badges page
|
||||
enabled: Enable badge
|
||||
icon: Icon
|
||||
image: Image
|
||||
icon_help: "Use either a Font Awesome class or URL to an image"
|
||||
query: Badge Query (SQL)
|
||||
target_posts: Query targets posts
|
||||
|
@ -2206,7 +2210,7 @@ en:
|
|||
one: "1 granted"
|
||||
other: "%{count} granted"
|
||||
select_badge_for_title: Select a badge to use as your title
|
||||
no_title: "<no title>"
|
||||
none: "<none>"
|
||||
badge_grouping:
|
||||
getting_started:
|
||||
name: Getting Started
|
||||
|
|
|
@ -236,6 +236,10 @@ Discourse::Application.routes.draw do
|
|||
post "users/:username/preferences/user_image" => "users#upload_user_image", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
delete "users/:username/preferences/user_image" => "users#destroy_user_image", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
put "users/:username/preferences/avatar/pick" => "users#pick_avatar", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "users/:username/preferences/card-badge" => "users#card_badge", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
put "users/:username/preferences/card-badge" => "users#update_card_badge", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
|
||||
|
||||
get "users/:username/invited" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
post "users/action/send_activation_email" => "users#send_activation_email"
|
||||
get "users/:username/activity" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
class AddImageToBadges < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :badges, :image, :string, limit: 255
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
class AddCardImageToUserProfiles < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :user_profiles, :card_image_badge_id, :integer
|
||||
end
|
||||
end
|
|
@ -971,6 +971,26 @@ describe UsersController do
|
|||
end
|
||||
end
|
||||
|
||||
describe "badge_card" do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:badge) { Fabricate(:badge) }
|
||||
let(:user_badge) { BadgeGranter.grant(badge, user) }
|
||||
|
||||
it "sets the user's card image to the badge" do
|
||||
log_in_user user
|
||||
xhr :put, :update_card_badge, user_badge_id: user_badge.id, username: user.username
|
||||
user.user_profile.reload.card_image_badge_id.should be_blank
|
||||
badge.update_attributes image: "wat.com/wat.jpg"
|
||||
|
||||
xhr :put, :update_card_badge, user_badge_id: user_badge.id, username: user.username
|
||||
user.user_profile.reload.card_image_badge_id.should == badge.id
|
||||
|
||||
# Can set to nothing
|
||||
xhr :put, :update_card_badge, username: user.username
|
||||
user.user_profile.reload.card_image_badge_id.should be_blank
|
||||
end
|
||||
end
|
||||
|
||||
describe "badge_title" do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:badge) { Fabricate(:badge) }
|
||||
|
|
Loading…
Reference in New Issue