diff --git a/app/assets/javascripts/admin/controllers/admin_group_controller.js b/app/assets/javascripts/admin/controllers/admin_group_controller.js new file mode 100644 index 00000000000..4b5f5bfb004 --- /dev/null +++ b/app/assets/javascripts/admin/controllers/admin_group_controller.js @@ -0,0 +1,77 @@ +Discourse.AdminGroupController = Em.ObjectController.extend({ + needs: ['adminGroups'], + members: null, + disableSave: false, + + aliasLevelOptions: function() { + return [ + { name: I18n.t("groups.alias_levels.nobody"), value: 0}, + { name: I18n.t("groups.alias_levels.mods_and_admins"), value: 2}, + { name: I18n.t("groups.alias_levels.members_mods_and_admins"), value: 3}, + { name: I18n.t("groups.alias_levels.everyone"), value: 99} + ]; + }.property(), + + usernames: function(key, value) { + var members = this.get('members'); + if (arguments.length > 1) { + this.set('_usernames', value); + } else { + var usernames; + if(members) { + usernames = members.map(function(user) { + return user.get('username'); + }).join(','); + } + this.set('_usernames', usernames); + } + return this.get('_usernames'); + }.property('members.@each.username'), + + actions: { + save: function() { + var self = this, + group = this.get('model'); + + self.set('disableSave', true); + + var promise; + if (group.get('id')) { + promise = group.saveWithUsernames(this.get('usernames')); + } else { + promise = group.createWithUsernames(this.get('usernames')).then(function() { + var groupsController = self.get('controllers.adminGroups'); + groupsController.addObject(group); + }); + } + promise.then(function() { + self.send('showGroup', group); + }, function(e) { + var message = $.parseJSON(e.responseText).errors; + bootbox.alert(message); + }).finally(function() { + self.set('disableSave', false); + }); + }, + + destroy: function() { + var group = this.get('model'), + groupsController = this.get('controllers.adminGroups'), + self = this; + + bootbox.confirm(I18n.t("admin.groups.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) { + if (result) { + self.set('disableSave', true); + group.destroy().then(function() { + groupsController.get('model').removeObject(group); + self.transitionToRoute('adminGroups.index'); + }, function() { + bootbox.alert(I18n.t("admin.groups.delete_failed")); + }).finally(function() { + self.set('disableSave', false); + }); + } + }); + } + } +}); diff --git a/app/assets/javascripts/admin/controllers/admin_groups_controller.js b/app/assets/javascripts/admin/controllers/admin_groups_controller.js index ea5f6509383..563b4f17bc5 100644 --- a/app/assets/javascripts/admin/controllers/admin_groups_controller.js +++ b/app/assets/javascripts/admin/controllers/admin_groups_controller.js @@ -1,49 +1,23 @@ -Discourse.AdminGroupsController = Ember.Controller.extend({ - itemController: 'adminGroup', +Discourse.AdminGroupsController = Ember.ArrayController.extend({ + sortProperties: ['name'], + + refreshingAutoGroups: false, actions: { - edit: function(group){ - this.get('model').select(group); - group.loadUsers(); - }, - refreshAutoGroups: function(){ - var self = this; + var self = this, + groups = this.get('model'); self.set('refreshingAutoGroups', true); - Discourse.ajax('/admin/groups/refresh_automatic_groups', {type: 'POST'}).then(function() { - return Discourse.Group.findAll().then(function(groups) { - self.set('model', groups); - self.set('refreshingAutoGroups', false); - }); - }); - }, - - newGroup: function(){ - var group = Discourse.Group.create({ loadedUsers: true, visible: true }), - model = this.get("model"); - model.addObject(group); - model.select(group); - }, - - save: function(group){ - if(!group.get("id")){ - group.create(); - } else { - group.save(); - } - }, - - destroy: function(group){ - var self = this; - return bootbox.confirm(I18n.t("admin.groups.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) { - if (result) { - group.destroy().then(function(deleted) { - if (deleted) { - self.get("model").removeObject(group); - } + this.transitionToRoute('adminGroups.index').then(function() { + Discourse.ajax('/admin/groups/refresh_automatic_groups', {type: 'POST'}).then(function() { + return Discourse.Group.findAll().then(function(newGroups) { + groups.clear(); + groups.addObjects(newGroups); + }).finally(function() { + self.set('refreshingAutoGroups', false); }); - } + }); }); } } diff --git a/app/assets/javascripts/admin/routes/admin_group_route.js b/app/assets/javascripts/admin/routes/admin_group_route.js new file mode 100644 index 00000000000..d4a8fd3cef7 --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin_group_route.js @@ -0,0 +1,23 @@ +Discourse.AdminGroupRoute = Em.Route.extend({ + + model: function(params) { + var groups = this.modelFor('adminGroups'), + group = groups.findProperty('name', params.name); + + if (!group) { return this.transitionTo('adminGroups.index'); } + return group; + }, + + afterModel: function(model) { + var self = this; + return model.findMembers().then(function(members) { + self.set('_members', members); + }); + }, + + setupController: function(controller, model) { + controller.set('model', model); + controller.set('members', this.get('_members')); + } +}); + diff --git a/app/assets/javascripts/admin/routes/admin_groups_route.js b/app/assets/javascripts/admin/routes/admin_groups_route.js new file mode 100644 index 00000000000..e66299a50fb --- /dev/null +++ b/app/assets/javascripts/admin/routes/admin_groups_route.js @@ -0,0 +1,31 @@ +/** + Handles routes for admin groups + + @class AdminGroupsRoute + @extends Discourse.Route + @namespace Discourse + @module Discourse +**/ +Discourse.AdminGroupsRoute = Discourse.Route.extend({ + model: function() { + return Discourse.Group.findAll(); + }, + + actions: { + showGroup: function(g) { + // This hack is needed because the autocomplete plugin does not + // refresh properly when the underlying data changes. TODO should + // be to update the plugin so it works properly and remove this hack. + var self = this; + this.transitionTo('adminGroups.index').then(function() { + self.transitionTo('adminGroup', g); + }); + }, + + newGroup: function(){ + var group = Discourse.Group.create({ visible: true }); + this.send('showGroup', group); + } + } +}); + diff --git a/app/assets/javascripts/admin/routes/admin_groups_routes.js b/app/assets/javascripts/admin/routes/admin_groups_routes.js deleted file mode 100644 index 6db39d35a68..00000000000 --- a/app/assets/javascripts/admin/routes/admin_groups_routes.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - Handles routes for admin groups - - @class AdminGroupsRoute - @extends Discourse.Route - @namespace Discourse - @module Discourse -**/ -Discourse.AdminGroupsRoute = Discourse.Route.extend({ - - model: function() { - return Discourse.Group.findAll(); - }, - - setupController: function(controller, model) { - controller.set('model', model); - controller.set('aliasLevelOptions', Discourse.Group.aliasLevelOptions()); - } - -}); - diff --git a/app/assets/javascripts/admin/routes/admin_routes.js b/app/assets/javascripts/admin/routes/admin_routes.js index 7aceb61d33b..bc2a431a200 100644 --- a/app/assets/javascripts/admin/routes/admin_routes.js +++ b/app/assets/javascripts/admin/routes/admin_routes.js @@ -43,7 +43,9 @@ Discourse.Route.buildRoutes(function() { this.route('screenedUrls', { path: '/screened_urls' }); }); - this.route('groups'); + this.resource('adminGroups', { path: '/groups'}, function() { + this.resource('adminGroup', { path: '/:name' }); + }); this.resource('adminUsers', { path: '/users' }, function() { this.resource('adminUser', { path: '/:username' }, function() { diff --git a/app/assets/javascripts/admin/templates/admin.js.handlebars b/app/assets/javascripts/admin/templates/admin.js.handlebars index 088de2b2b23..84b4ca72c63 100644 --- a/app/assets/javascripts/admin/templates/admin.js.handlebars +++ b/app/assets/javascripts/admin/templates/admin.js.handlebars @@ -14,7 +14,7 @@
  • {{#link-to 'admin.badges'}}{{i18n admin.badges.title}}{{/link-to}}
  • {{/if}} {{#if currentUser.admin}} -
  • {{#link-to 'admin.groups'}}{{i18n admin.groups.title}}{{/link-to}}
  • +
  • {{#link-to 'adminGroups.index'}}{{i18n admin.groups.title}}{{/link-to}}
  • {{/if}}
  • {{#link-to 'adminEmail'}}{{i18n admin.email.title}}{{/link-to}}
  • {{#link-to 'adminFlags'}}{{i18n admin.flags.title}}{{/link-to}}
  • diff --git a/app/assets/javascripts/admin/templates/group.js.handlebars b/app/assets/javascripts/admin/templates/group.js.handlebars new file mode 100644 index 00000000000..1988e84ce70 --- /dev/null +++ b/app/assets/javascripts/admin/templates/group.js.handlebars @@ -0,0 +1,29 @@ +{{#if automatic}} +

    {{name}}

    +{{else}} + {{textField value=name placeholderKey="admin.groups.name_placeholder"}} +{{/if}} + +
    + +
    + {{userSelector usernames=usernames id="group-users" placeholderKey="admin.groups.selector_placeholder" tabindex="1" disabled=automatic}} +
    +
    +
    +
    + {{input type="checkbox" checked=visible}} {{i18n groups.visible}} +
    +
    +
    + +
    + {{combobox valueAttribute="value" value=alias_level content=aliasLevelOptions}} +
    +
    +
    + + {{#unless automatic}} + {{i18n admin.customize.delete}} + {{/unless}} +
    diff --git a/app/assets/javascripts/admin/templates/groups.js.handlebars b/app/assets/javascripts/admin/templates/groups.js.handlebars index 925e532c06d..fcab6629385 100644 --- a/app/assets/javascripts/admin/templates/groups.js.handlebars +++ b/app/assets/javascripts/admin/templates/groups.js.handlebars @@ -2,9 +2,9 @@

    {{i18n admin.groups.edit}}

    @@ -15,44 +15,6 @@
    - {{#if model.active}} - {{#if model.active.loadedUsers}} - {{#with model.active}} - {{#if automatic}} -

    {{name}}

    - {{else}} - {{textField value=name placeholderKey="admin.groups.name_placeholder"}} - {{/if}} - -
    - -
    - {{userSelector usernames=usernames id="group-users" placeholderKey="admin.groups.selector_placeholder" tabindex="1" disabledBinding="automatic"}} -
    -
    -
    -
    - {{input type="checkbox" checked=visible}} {{i18n groups.visible}} -
    -
    -
    - -
    - {{combobox valueAttribute="value" value=alias_level content=controller.aliasLevelOptions}} -
    -
    -
    - - {{#unless automatic}} - {{i18n admin.customize.delete}} - {{/unless}} -
    - {{/with}} - {{else}} -
    {{i18n loading}}
    - {{/if}} - {{else}} - {{i18n admin.groups.about}} - {{/if}} + {{outlet}}
    diff --git a/app/assets/javascripts/admin/templates/groups_index.js.handlebars b/app/assets/javascripts/admin/templates/groups_index.js.handlebars new file mode 100644 index 00000000000..5c29aa24aea --- /dev/null +++ b/app/assets/javascripts/admin/templates/groups_index.js.handlebars @@ -0,0 +1 @@ +{{i18n admin.groups.about}} diff --git a/app/assets/javascripts/discourse/lib/autocomplete.js b/app/assets/javascripts/discourse/lib/autocomplete.js index fdb424c0e86..e3cf8627ead 100644 --- a/app/assets/javascripts/discourse/lib/autocomplete.js +++ b/app/assets/javascripts/discourse/lib/autocomplete.js @@ -270,7 +270,7 @@ $.fn.autocomplete = function(options) { } }); - return $(this).keydown(function(e) { + $(this).keydown(function(e) { var c, caretPosition, i, initial, next, prev, prevIsGood, stopFound, term, total, userToComplete; if(options.allowAny){ @@ -405,4 +405,6 @@ $.fn.autocomplete = function(options) { } } }); + + return this; }; diff --git a/app/assets/javascripts/discourse/models/group.js b/app/assets/javascripts/discourse/models/group.js index 3502c92fea0..3ef5edc94df 100644 --- a/app/assets/javascripts/discourse/models/group.js +++ b/app/assets/javascripts/discourse/models/group.js @@ -6,22 +6,7 @@ @namespace Discourse @module Discourse **/ -var ALIAS_LEVELS = { - nobody: 0, - only_admins: 1, - mods_and_admins: 2, - members_mods_and_admins: 3, - everyone: 99 - }, - aliasLevelOptions = [ - { name: I18n.t("groups.alias_levels.nobody"), value: ALIAS_LEVELS.nobody}, - { name: I18n.t("groups.alias_levels.mods_and_admins"), value: ALIAS_LEVELS.mods_and_admins}, - { name: I18n.t("groups.alias_levels.members_mods_and_admins"), value: ALIAS_LEVELS.members_mods_and_admins}, - { name: I18n.t("groups.alias_levels.everyone"), value: ALIAS_LEVELS.everyone} - ]; - Discourse.Group = Discourse.Model.extend({ - loadedUsers: false, userCountDisplay: function(){ var c = this.get('user_count'); @@ -31,56 +16,17 @@ Discourse.Group = Discourse.Model.extend({ } }.property('user_count'), - // TODO: Refactor so adminGroups doesn't store the groups inside itself either. findMembers: function() { + if (Em.isEmpty(this.get('name'))) { return Ember.RSVP.resolve([]); } + return Discourse.ajax('/groups/' + this.get('name') + '/members').then(function(result) { return result.map(function(u) { return Discourse.User.create(u) }); }); }, - loadUsers: function() { - var id = this.get('id'); - if(id && !this.get('loadedUsers')) { - var self = this; - return this.findMembers().then(function(users) { - self.set('users', users); - self.set('loadedUsers', true); - return self; - }); - } - return Ember.RSVP.resolve(this); - }, - - usernames: function(key, value) { - var users = this.get('users'); - if (arguments.length > 1) { - this.set('_usernames', value); - } else { - var usernames = ""; - if(users) { - usernames = users.map(function(user) { - return user.get('username'); - }).join(','); - } - this.set('_usernames', usernames); - } - return this.get('_usernames'); - }.property('users.@each.username'), - destroy: function(){ - if(!this.id) return; - - var self = this; - this.set('disableSave', true); - - return Discourse.ajax("/admin/groups/" + this.get('id'), {type: "DELETE"}) - .then(function(){ - return true; - }, function() { - self.set('disableSave', false); - bootbox.alert(I18n.t("admin.groups.delete_failed")); - return false; - }); + if(!this.get('id')) return; + return Discourse.ajax("/admin/groups/" + this.get('id'), {type: "DELETE"}); }, asJSON: function() { @@ -91,36 +37,22 @@ Discourse.Group = Discourse.Model.extend({ usernames: this.get('usernames') } }; }, - create: function(){ - var self = this; - self.set('disableSave', true); + createWithUsernames: function(usernames){ + var self = this, + json = this.asJSON(); + json.group.usernames = usernames; - return Discourse.ajax("/admin/groups", {type: "POST", data: this.asJSON()}).then(function(resp) { - self.set('disableSave', false); - self.set('id', resp.id); - }, function (error) { - self.set('disableSave', false); - if (error && error.responseText) { - bootbox.alert($.parseJSON(error.responseText).errors); - } - else { - bootbox.alert(I18n.t('generic_error')); - } + return Discourse.ajax("/admin/groups", {type: "POST", data: json}).then(function(resp) { + self.set('id', resp.basic_group.id); }); }, - save: function(){ - var self = this; - self.set('disableSave', true); - + saveWithUsernames: function(usernames){ + var json = this.asJSON(); + json.group.usernames = usernames; return Discourse.ajax("/admin/groups/" + this.get('id'), { type: "PUT", - data: this.asJSON() - }).then(function(){ - self.set('disableSave', false); - }, function(e){ - var message = $.parseJSON(e.responseText).errors; - bootbox.alert(message); + data: json }); }, @@ -142,11 +74,7 @@ Discourse.Group = Discourse.Model.extend({ Discourse.Group.reopenClass({ findAll: function(){ return Discourse.ajax("/admin/groups.json").then(function(groups){ - var list = Discourse.SelectableArray.create(); - _.each(groups,function(group){ - list.addObject(Discourse.Group.create(group)); - }); - return list; + return groups.map(function(g) { return Discourse.Group.create(g); }); }); }, @@ -160,9 +88,5 @@ Discourse.Group.reopenClass({ return Discourse.ajax("/groups/" + name + ".json").then(function(g) { return Discourse.Group.create(g.basic_group); }); - }, - - aliasLevelOptions: function() { - return aliasLevelOptions; } }); diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index fcf92d29496..8803314d001 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -4,6 +4,10 @@ class Admin::GroupsController < Admin::AdminController render_serialized(groups, BasicGroupSerializer) end + def show + render nothing: true + end + def refresh_automatic_groups Group.refresh_automatic_groups! render json: success_json @@ -31,7 +35,7 @@ class Admin::GroupsController < Admin::AdminController def create group = Group.new - group.name = params[:group][:name].strip + group.name = (params[:group][:name] || '').strip group.usernames = params[:group][:usernames] if params[:group][:usernames] group.visible = params[:group][:visible] == "true" if group.save diff --git a/app/models/group.rb b/app/models/group.rb index 646e667c383..2741b30dfcb 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -8,6 +8,7 @@ class Group < ActiveRecord::Base after_save :destroy_deletions validate :name_format_validator + validates_uniqueness_of :name AUTO_GROUPS = { :everyone => 0,