mirror of
https://github.com/discourse/discourse.git
synced 2026-07-29 15:54:48 -05:00
FEATURE: Allow users to select a badge with an image to appear on their
user card
This commit is contained in:
@@ -26,7 +26,7 @@ export default Ember.ObjectController.extend(BufferedContent, {
|
|||||||
'listable', 'auto_revoke',
|
'listable', 'auto_revoke',
|
||||||
'enabled', 'show_posts',
|
'enabled', 'show_posts',
|
||||||
'target_posts', 'name', 'description',
|
'target_posts', 'name', 'description',
|
||||||
'icon', 'query', 'badge_grouping_id',
|
'icon', 'image', 'query', 'badge_grouping_id',
|
||||||
'trigger', 'badge_type_id'],
|
'trigger', 'badge_type_id'],
|
||||||
self = this;
|
self = this;
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,12 @@
|
|||||||
<p class='help'>{{i18n admin.badges.icon_help}}</p>
|
<p class='help'>{{i18n admin.badges.icon_help}}</p>
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<label for="badge_type_id">{{i18n admin.badges.badge_type}}</label>
|
<label for="badge_type_id">{{i18n admin.badges.badge_type}}</label>
|
||||||
{{view Ember.Select name="badge_type_id"
|
{{view Ember.Select name="badge_type_id"
|
||||||
|
|||||||
@@ -3,7 +3,5 @@ export default Ember.Component.extend({
|
|||||||
classNameBindings: [':user-badge', 'badge.badgeTypeClassName'],
|
classNameBindings: [':user-badge', 'badge.badgeTypeClassName'],
|
||||||
title: Em.computed.alias('badge.displayDescription'),
|
title: Em.computed.alias('badge.displayDescription'),
|
||||||
attributeBindings: ['data-badge-name', 'title'],
|
attributeBindings: ['data-badge-name', 'title'],
|
||||||
'data-badge-name': Em.computed.alias('badge.name'),
|
'data-badge-name': Em.computed.alias('badge.name')
|
||||||
|
|
||||||
isIcon: Em.computed.match('badge.icon', /^fa-/)
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,63 +1,25 @@
|
|||||||
/**
|
import BadgeSelectController from "discourse/mixins/badge-select-controller";
|
||||||
Controller for selecting a badge to use as your title.
|
|
||||||
|
|
||||||
@class PreferencesBadgeTitleController
|
export default Ember.ArrayController.extend(BadgeSelectController, {
|
||||||
@extends Ember.ArrayController
|
|
||||||
@namespace Discourse
|
|
||||||
@module Discourse
|
|
||||||
**/
|
|
||||||
export default Ember.ArrayController.extend({
|
|
||||||
saving: false,
|
|
||||||
saved: false,
|
|
||||||
|
|
||||||
savingStatus: function() {
|
filteredList: function() {
|
||||||
if (this.get('saving')) {
|
return this.get('model').filterBy('badge.allow_title', true);
|
||||||
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;
|
|
||||||
}.property('model'),
|
}.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: {
|
actions: {
|
||||||
save: function() {
|
save: function() {
|
||||||
|
this.setProperties({ saved: false, saving: true });
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
Discourse.ajax(this.get('user.path') + "/preferences/badge_title", {
|
||||||
self.set('saved', false);
|
|
||||||
self.set('saving', true);
|
|
||||||
|
|
||||||
Discourse.ajax("/users/" + self.get('user.username_lower') + "/preferences/badge_title", {
|
|
||||||
type: "PUT",
|
type: "PUT",
|
||||||
data: {
|
data: { user_badge_id: self.get('selectedUserBadgeId') }
|
||||||
user_badge_id: self.get('selectedUserBadgeId')
|
|
||||||
}
|
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
self.set('saved', true);
|
self.setProperties({
|
||||||
self.set('saving', false);
|
saved: true,
|
||||||
self.set('user.title', self.get('selectedUserBadge.badge.name'));
|
saving: false,
|
||||||
|
"user.title": self.get('selectedUserBadge.badge.name')
|
||||||
|
});
|
||||||
}, function() {
|
}, function() {
|
||||||
bootbox.alert(I18n.t('generic_error'));
|
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);
|
user.setProperties(json.user);
|
||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ Discourse.Route.buildRoutes(function() {
|
|||||||
this.route('email');
|
this.route('email');
|
||||||
this.route('about', { path: '/about-me' });
|
this.route('about', { path: '/about-me' });
|
||||||
this.route('badgeTitle', { path: '/badge_title' });
|
this.route('badgeTitle', { path: '/badge_title' });
|
||||||
|
this.route('card-badge', { path: '/card-badge' });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.route('invited');
|
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}}
|
{{icon-or-image badge.icon}}
|
||||||
<i {{bind-attr class=":fa badge.icon"}}></i>
|
|
||||||
{{else}}
|
|
||||||
<img {{bind-attr src="badge.icon"}}>
|
|
||||||
{{/if}}
|
|
||||||
{{badge.displayName}}
|
{{badge.displayName}}
|
||||||
{{yield}}
|
{{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}}
|
{{#if hasUserFilters}}
|
||||||
<button class='btn' {{action "cancelFilter"}}>{{fa-icon "times"}}{{i18n topic.filters.cancel}}</button>
|
<button class='btn' {{action "cancelFilter"}}>{{fa-icon "times"}}{{i18n topic.filters.cancel}}</button>
|
||||||
{{/if}}
|
{{/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>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<p class='loading'>{{i18n loading}}</p>
|
<p class='loading'>{{i18n loading}}</p>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<label class="control-label">{{i18n user.title.title}}</label>
|
<label class="control-label">{{i18n user.title.title}}</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<span class="static">{{title}}</span>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@@ -157,6 +157,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="control-group pref-email-settings">
|
||||||
<label class="control-label">{{i18n user.email_settings}}</label>
|
<label class="control-label">{{i18n user.email_settings}}</label>
|
||||||
{{preference-checkbox labelKey="user.email_digests.title" checked=email_digests}}
|
{{preference-checkbox labelKey="user.email_digests.title" checked=email_digests}}
|
||||||
|
|||||||
@@ -145,4 +145,12 @@
|
|||||||
color: $danger;
|
color: $danger;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-badge {
|
||||||
|
img {
|
||||||
|
max-width: 100px;
|
||||||
|
}
|
||||||
|
float: right;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,23 @@ class UsersController < ApplicationController
|
|||||||
end
|
end
|
||||||
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
|
def user_preferences_redirect
|
||||||
redirect_to email_preferences_path(current_user.username_lower)
|
redirect_to email_preferences_path(current_user.username_lower)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ class UserProfile < ActiveRecord::Base
|
|||||||
before_save :cook
|
before_save :cook
|
||||||
after_save :trigger_badges
|
after_save :trigger_badges
|
||||||
|
|
||||||
|
belongs_to :card_image_badge, class_name: 'Badge'
|
||||||
|
|
||||||
BAKED_VERSION = 1
|
BAKED_VERSION = 1
|
||||||
|
|
||||||
def bio_excerpt
|
def bio_excerpt
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
class BadgeSerializer < ApplicationSerializer
|
class BadgeSerializer < ApplicationSerializer
|
||||||
attributes :id, :name, :description, :grant_count, :allow_title,
|
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
|
:system
|
||||||
has_one :badge_type
|
has_one :badge_type
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class UserSerializer < BasicUserSerializer
|
|||||||
has_one :invited_by, embed: :object, serializer: BasicUserSerializer
|
has_one :invited_by, embed: :object, serializer: BasicUserSerializer
|
||||||
has_many :custom_groups, embed: :object, serializer: BasicGroupSerializer
|
has_many :custom_groups, embed: :object, serializer: BasicGroupSerializer
|
||||||
has_many :featured_user_badges, embed: :ids, serializer: UserBadgeSerializer, root: :user_badges
|
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,
|
staff_attributes :number_of_deleted_posts,
|
||||||
:number_of_flagged_posts,
|
:number_of_flagged_posts,
|
||||||
@@ -80,7 +81,9 @@ class UserSerializer < BasicUserSerializer
|
|||||||
:disable_jump_reply,
|
:disable_jump_reply,
|
||||||
:gravatar_avatar_upload_id,
|
:gravatar_avatar_upload_id,
|
||||||
:custom_avatar_upload_id,
|
:custom_avatar_upload_id,
|
||||||
:has_title_badges
|
:has_title_badges,
|
||||||
|
:card_image_badge,
|
||||||
|
:card_image_badge_id
|
||||||
|
|
||||||
###
|
###
|
||||||
### ATTRIBUTES
|
### ATTRIBUTES
|
||||||
@@ -90,6 +93,11 @@ class UserSerializer < BasicUserSerializer
|
|||||||
object.id && object.id == scope.user.try(:id)
|
object.id && object.id == scope.user.try(:id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def card_badge
|
||||||
|
object.user_profile.card_image_badge
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def bio_raw
|
def bio_raw
|
||||||
object.user_profile.bio_raw
|
object.user_profile.bio_raw
|
||||||
end
|
end
|
||||||
@@ -110,6 +118,22 @@ class UserSerializer < BasicUserSerializer
|
|||||||
website.present?
|
website.present?
|
||||||
end
|
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
|
def profile_background
|
||||||
object.user_profile.profile_background
|
object.user_profile.profile_background
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -418,6 +418,9 @@ en:
|
|||||||
created: "Joined"
|
created: "Joined"
|
||||||
log_out: "Log Out"
|
log_out: "Log Out"
|
||||||
location: "Location"
|
location: "Location"
|
||||||
|
card_badge:
|
||||||
|
title: "User Card Badge"
|
||||||
|
|
||||||
website: "Web Site"
|
website: "Web Site"
|
||||||
email_settings: "Email"
|
email_settings: "Email"
|
||||||
email_digests:
|
email_digests:
|
||||||
@@ -2106,6 +2109,7 @@ en:
|
|||||||
listable: Show badge on the public badges page
|
listable: Show badge on the public badges page
|
||||||
enabled: Enable badge
|
enabled: Enable badge
|
||||||
icon: Icon
|
icon: Icon
|
||||||
|
image: Image
|
||||||
icon_help: "Use either a Font Awesome class or URL to an image"
|
icon_help: "Use either a Font Awesome class or URL to an image"
|
||||||
query: Badge Query (SQL)
|
query: Badge Query (SQL)
|
||||||
target_posts: Query targets posts
|
target_posts: Query targets posts
|
||||||
@@ -2206,7 +2210,7 @@ en:
|
|||||||
one: "1 granted"
|
one: "1 granted"
|
||||||
other: "%{count} granted"
|
other: "%{count} granted"
|
||||||
select_badge_for_title: Select a badge to use as your title
|
select_badge_for_title: Select a badge to use as your title
|
||||||
no_title: "<no title>"
|
none: "<none>"
|
||||||
badge_grouping:
|
badge_grouping:
|
||||||
getting_started:
|
getting_started:
|
||||||
name: 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}
|
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}
|
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}
|
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}
|
get "users/:username/invited" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||||
post "users/action/send_activation_email" => "users#send_activation_email"
|
post "users/action/send_activation_email" => "users#send_activation_email"
|
||||||
get "users/:username/activity" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
|
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
|
||||||
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
|
describe "badge_title" do
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
let(:badge) { Fabricate(:badge) }
|
let(:badge) { Fabricate(:badge) }
|
||||||
|
|||||||
Reference in New Issue
Block a user