FIX: Many bugs with admin badges interface
* Editing a badge's title would show it as changed in the side even if you didn't hit save * Clicking a badge would not scroll to the top * If there was an error saving a badge there was a missing i18n key * URLs were using queryParams instead of paths * User `label` tags for checkboxes for larger click targets * Saved! text would persist when viewing another badge * After creating a new badge it would show nothing * Validation errors were not being properly released to the client * Query errors were surrounded by an extra array
This commit is contained in:
@@ -1,37 +0,0 @@
|
||||
import ObjectController from 'discourse/controllers/object';
|
||||
|
||||
/**
|
||||
This is the itemController for `Discourse.AdminBadgesController`. Its main purpose
|
||||
is to indicate which badge was selected.
|
||||
|
||||
@class AdminBadgeController
|
||||
@extends ObjectController
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
|
||||
export default ObjectController.extend({
|
||||
/**
|
||||
Whether this badge has been selected.
|
||||
|
||||
@property selected
|
||||
@type {Boolean}
|
||||
**/
|
||||
selected: Discourse.computed.propertyEqual('model.name', 'parentController.selectedItem.name'),
|
||||
|
||||
/**
|
||||
Show the displayName only if it is different from the name.
|
||||
|
||||
@property showDisplayName
|
||||
@type {Boolean}
|
||||
**/
|
||||
showDisplayName: Discourse.computed.propertyNotEqual('selectedItem.name', 'selectedItem.displayName'),
|
||||
|
||||
/**
|
||||
Don't allow editing if this is a system badge.
|
||||
|
||||
@property readOnly
|
||||
@type {Boolean}
|
||||
**/
|
||||
readOnly: Ember.computed.alias('model.system')
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
import BufferedContent from 'discourse/mixins/buffered-content';
|
||||
|
||||
export default Ember.ObjectController.extend(BufferedContent, {
|
||||
needs: ['admin-badges'],
|
||||
saving: false,
|
||||
savingStatus: '',
|
||||
|
||||
badgeTypes: Em.computed.alias('controllers.admin-badges.badgeTypes'),
|
||||
badgeGroupings: Em.computed.alias('controllers.admin-badges.badgeGroupings'),
|
||||
badgeTriggers: Em.computed.alias('controllers.admin-badges.badgeTriggers'),
|
||||
protectedSystemFields: Em.computed.alias('controllers.admin-badges.protectedSystemFields'),
|
||||
|
||||
readOnly: Ember.computed.alias('buffered.system'),
|
||||
showDisplayName: Discourse.computed.propertyNotEqual('name', 'displayName'),
|
||||
canEditDescription: Em.computed.none('buffered.translatedDescription'),
|
||||
|
||||
_resetSaving: function() {
|
||||
this.set('saving', false);
|
||||
this.set('savingStatus', '');
|
||||
}.observes('model.id'),
|
||||
|
||||
actions: {
|
||||
save: function() {
|
||||
if (!this.get('saving')) {
|
||||
var fields = ['allow_title', 'multiple_grant',
|
||||
'listable', 'auto_revoke',
|
||||
'enabled', 'show_posts',
|
||||
'target_posts', 'name', 'description',
|
||||
'icon', 'query', 'badge_grouping_id',
|
||||
'trigger', 'badge_type_id'],
|
||||
self = this;
|
||||
|
||||
if (this.get('buffered.system')){
|
||||
var protectedFields = this.get('protectedSystemFields');
|
||||
fields = _.filter(fields, function(f){
|
||||
return !_.include(protectedFields,f);
|
||||
});
|
||||
}
|
||||
|
||||
this.set('saving', true);
|
||||
this.set('savingStatus', I18n.t('saving'));
|
||||
|
||||
var boolFields = ['allow_title', 'multiple_grant',
|
||||
'listable', 'auto_revoke',
|
||||
'enabled', 'show_posts',
|
||||
'target_posts' ];
|
||||
|
||||
var data = {},
|
||||
buffered = this.get('buffered');
|
||||
fields.forEach(function(field){
|
||||
var d = buffered.get(field);
|
||||
if (_.include(boolFields, field)) { d = !!d; }
|
||||
data[field] = d;
|
||||
});
|
||||
|
||||
var newBadge = !this.get('id'),
|
||||
model = this.get('model');
|
||||
this.get('model').save(data).then(function() {
|
||||
if (newBadge) {
|
||||
self.get('controllers.admin-badges').pushObject(model);
|
||||
self.transitionToRoute('adminBadges.show', model.get('id'));
|
||||
} else {
|
||||
self.commitBuffer();
|
||||
self.set('savingStatus', I18n.t('saved'));
|
||||
}
|
||||
|
||||
}).catch(function(error) {
|
||||
self.set('savingStatus', I18n.t('failed'));
|
||||
self.send('saveError', error);
|
||||
}).finally(function() {
|
||||
self.set('saving', false);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
var self = this,
|
||||
adminBadgesController = this.get('controllers.admin-badges'),
|
||||
model = this.get('model');
|
||||
|
||||
if (!model.get('id')) {
|
||||
self.transitionToRoute('adminBadges.index');
|
||||
return;
|
||||
}
|
||||
|
||||
return bootbox.confirm(I18n.t("admin.badges.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
|
||||
if (result) {
|
||||
model.destroy().then(function() {
|
||||
adminBadgesController.removeObject(model);
|
||||
self.transitionToRoute('adminBadges.index');
|
||||
}).catch(function() {
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,165 +1 @@
|
||||
/**
|
||||
This controller supports the interface for dealing with badges.
|
||||
|
||||
@class AdminBadgesController
|
||||
@extends Ember.ArrayController
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default Ember.ArrayController.extend({
|
||||
needs: ['modal'],
|
||||
itemController: 'admin-badge',
|
||||
queryParams: ['badgeId'],
|
||||
badgeId: Em.computed.alias('selectedId'),
|
||||
|
||||
/**
|
||||
ID of the currently selected badge.
|
||||
|
||||
@property selectedId
|
||||
@type {Integer}
|
||||
**/
|
||||
selectedId: null,
|
||||
|
||||
/**
|
||||
Badge that is currently selected.
|
||||
|
||||
@property selectedItem
|
||||
@type {Discourse.Badge}
|
||||
**/
|
||||
selectedItem: function() {
|
||||
if (this.get('selectedId') === undefined || this.get('selectedId') === "undefined") {
|
||||
// New Badge
|
||||
return this.get('newBadge');
|
||||
} else {
|
||||
// Existing Badge
|
||||
var selectedId = parseInt(this.get('selectedId'));
|
||||
return this.get('model').filter(function(badge) {
|
||||
return parseInt(badge.get('id')) === selectedId;
|
||||
})[0];
|
||||
}
|
||||
}.property('selectedId', 'newBadge'),
|
||||
|
||||
/**
|
||||
Unsaved badge, if one exists.
|
||||
|
||||
@property newBadge
|
||||
@type {Discourse.Badge}
|
||||
**/
|
||||
newBadge: function() {
|
||||
return this.get('model').filter(function(badge) {
|
||||
return badge.get('id') === undefined;
|
||||
})[0];
|
||||
}.property('model.@each.id'),
|
||||
|
||||
/**
|
||||
Whether a new unsaved badge exists.
|
||||
|
||||
@property newBadgeExists
|
||||
@type {Discourse.Badge}
|
||||
**/
|
||||
newBadgeExists: Em.computed.notEmpty('newBadge'),
|
||||
|
||||
/**
|
||||
We don't allow setting a description if a translation for the given badge
|
||||
name exists.
|
||||
|
||||
@property canEditDescription
|
||||
@type {Boolean}
|
||||
**/
|
||||
canEditDescription: Em.computed.none('selectedItem.translatedDescription'),
|
||||
|
||||
/**
|
||||
Disable saving if the currently selected item is being saved.
|
||||
|
||||
@property disableSave
|
||||
@type {Boolean}
|
||||
**/
|
||||
disableSave: Em.computed.alias('selectedItem.saving'),
|
||||
|
||||
actions: {
|
||||
|
||||
/**
|
||||
Create a new badge and select it.
|
||||
|
||||
@method newBadge
|
||||
**/
|
||||
createNewBadge: function() {
|
||||
var badge = Discourse.Badge.create({
|
||||
name: I18n.t('admin.badges.new_badge')
|
||||
});
|
||||
this.pushObject(badge);
|
||||
this.send('selectBadge', badge);
|
||||
},
|
||||
|
||||
/**
|
||||
Select a particular badge.
|
||||
|
||||
@method selectBadge
|
||||
@param {Discourse.Badge} badge The badge to be selected
|
||||
**/
|
||||
selectBadge: function(badge) {
|
||||
this.set('selectedId', badge.get('id'));
|
||||
},
|
||||
|
||||
/**
|
||||
Save the selected badge.
|
||||
|
||||
@method save
|
||||
**/
|
||||
save: function() {
|
||||
if (!this.get('disableSave')) {
|
||||
var fields = ['allow_title', 'multiple_grant',
|
||||
'listable', 'auto_revoke',
|
||||
'enabled', 'show_posts',
|
||||
'target_posts', 'name', 'description',
|
||||
'icon', 'query', 'badge_grouping_id',
|
||||
'trigger', 'badge_type_id'],
|
||||
self = this;
|
||||
|
||||
if (this.get('selectedItem.system')){
|
||||
var protectedFields = this.get('protectedSystemFields');
|
||||
fields = _.filter(fields, function(f){
|
||||
return !_.include(protectedFields,f);
|
||||
});
|
||||
}
|
||||
|
||||
this.get('selectedItem').save(fields).catch(function(error) {
|
||||
// this shows the admin-badge-preview modal with the error
|
||||
// kinda weird, but it consolidates the display logic for badge errors
|
||||
self.send('saveError', error);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
Confirm before destroying the selected badge.
|
||||
|
||||
@method destroy
|
||||
**/
|
||||
destroy: function() {
|
||||
// Delete immediately if the selected badge is new.
|
||||
if (!this.get('selectedItem.id')) {
|
||||
this.get('model').removeObject(this.get('selectedItem'));
|
||||
this.set('selectedId', null);
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return bootbox.confirm(I18n.t("admin.badges.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
|
||||
if (result) {
|
||||
var selected = self.get('selectedItem');
|
||||
selected.destroy().then(function() {
|
||||
// Success.
|
||||
self.set('selectedId', null);
|
||||
self.get('model').removeObject(selected);
|
||||
}, function() {
|
||||
// Failure.
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
export default Ember.ArrayController.extend();
|
||||
|
||||
Reference in New Issue
Block a user