diff --git a/app/assets/javascripts/discourse/components/group-card-contents.js.es6 b/app/assets/javascripts/discourse/components/group-card-contents.js.es6 index 50ea8b9ab22..1838b32352a 100644 --- a/app/assets/javascripts/discourse/components/group-card-contents.js.es6 +++ b/app/assets/javascripts/discourse/components/group-card-contents.js.es6 @@ -5,6 +5,7 @@ import { default as discourseComputed } from "discourse-common/utils/decorators" import CardContentsBase from "discourse/mixins/card-contents-base"; import CleansUp from "discourse/mixins/cleans-up"; import { groupPath } from "discourse/lib/url"; +import { Promise } from "rsvp"; const maxMembersToDisplay = 10; @@ -55,8 +56,9 @@ export default Component.extend(CardContentsBase, CleansUp, { if (!group.flair_url && !group.flair_bg_color) { group.set("flair_url", "fa-users"); } - group.set("limit", maxMembersToDisplay); - return group.findMembers(); + return group.members.length < maxMembersToDisplay + ? group.findMembers({ limit: maxMembersToDisplay }, true) + : Promise.resolve(); }) .catch(() => this._close()) .finally(() => this.set("loading", null)); diff --git a/app/assets/javascripts/discourse/components/group-members-input.js.es6 b/app/assets/javascripts/discourse/components/group-members-input.js.es6 deleted file mode 100644 index aedb43f2a6b..00000000000 --- a/app/assets/javascripts/discourse/components/group-members-input.js.es6 +++ /dev/null @@ -1,91 +0,0 @@ -import discourseComputed from "discourse-common/utils/decorators"; -import { isEmpty } from "@ember/utils"; -import { lte } from "@ember/object/computed"; -import Component from "@ember/component"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import { propertyEqual } from "discourse/lib/computed"; - -export default Component.extend({ - classNames: ["group-members-input"], - addButton: true, - - @discourseComputed("model.limit", "model.offset", "model.user_count") - currentPage(limit, offset, userCount) { - if (userCount === 0) { - return 0; - } - - return Math.floor(offset / limit) + 1; - }, - - @discourseComputed("model.limit", "model.user_count") - totalPages(limit, userCount) { - if (userCount === 0) { - return 0; - } - return Math.ceil(userCount / limit); - }, - - @discourseComputed("model.usernames") - disableAddButton(usernames) { - return !usernames || !(usernames.length > 0); - }, - - showingFirst: lte("currentPage", 1), - showingLast: propertyEqual("currentPage", "totalPages"), - - actions: { - next() { - if (this.showingLast) { - return; - } - - const group = this.model; - const offset = Math.min( - group.get("offset") + group.get("limit"), - group.get("user_count") - ); - group.set("offset", offset); - - return group.findMembers(); - }, - - previous() { - if (this.showingFirst) { - return; - } - - const group = this.model; - const offset = Math.max(group.get("offset") - group.get("limit"), 0); - group.set("offset", offset); - - return group.findMembers(); - }, - - addMembers() { - if (isEmpty(this.get("model.usernames"))) { - return; - } - this.model.addMembers(this.get("model.usernames")).catch(popupAjaxError); - this.set("model.usernames", null); - }, - - removeMember(member) { - const message = I18n.t("groups.manage.delete_member_confirm", { - username: member.get("username"), - group: this.get("model.name") - }); - - return bootbox.confirm( - message, - I18n.t("no_value"), - I18n.t("yes_value"), - confirm => { - if (confirm) { - this.model.removeMember(member); - } - } - ); - } - } -}); diff --git a/app/assets/javascripts/discourse/components/groups-form-profile-fields.js.es6 b/app/assets/javascripts/discourse/components/groups-form-profile-fields.js.es6 index 9074ef2d637..fda6dd8ec62 100644 --- a/app/assets/javascripts/discourse/components/groups-form-profile-fields.js.es6 +++ b/app/assets/javascripts/discourse/components/groups-form-profile-fields.js.es6 @@ -6,6 +6,7 @@ import { observes } from "discourse-common/utils/decorators"; import Group from "discourse/models/group"; +import { popupAjaxError } from "discourse/lib/ajax-error"; import discourseDebounce from "discourse/lib/debounce"; import EmberObject from "@ember/object"; @@ -68,32 +69,34 @@ export default Component.extend({ name = this.nameInput; if (isEmpty(name)) return; - Group.checkName(name).then(response => { - const validationName = "uniqueNameValidation"; + Group.checkName(name) + .then(response => { + const validationName = "uniqueNameValidation"; - if (response.available) { - this.set( - validationName, - EmberObject.create({ - ok: true, - reason: I18n.t("admin.groups.new.name.available") - }) - ); + if (response.available) { + this.set( + validationName, + EmberObject.create({ + ok: true, + reason: I18n.t("admin.groups.new.name.available") + }) + ); - this.set("disableSave", false); - this.set("model.name", this.nameInput); - } else { - let reason; - - if (response.errors) { - reason = response.errors.join(" "); + this.set("disableSave", false); + this.set("model.name", this.nameInput); } else { - reason = I18n.t("admin.groups.new.name.not_available"); - } + let reason; - this.set(validationName, this._failedInputValidation(reason)); - } - }); + if (response.errors) { + reason = response.errors.join(" "); + } else { + reason = I18n.t("admin.groups.new.name.not_available"); + } + + this.set(validationName, this._failedInputValidation(reason)); + } + }) + .catch(popupAjaxError); }, 500), _failedInputValidation(reason) { diff --git a/app/assets/javascripts/discourse/controllers/group-index.js.es6 b/app/assets/javascripts/discourse/controllers/group-index.js.es6 index 094c1365979..887d5dbfcf5 100644 --- a/app/assets/javascripts/discourse/controllers/group-index.js.es6 +++ b/app/assets/javascripts/discourse/controllers/group-index.js.es6 @@ -1,27 +1,25 @@ +import Controller, { inject } from "@ember/controller"; import { alias } from "@ember/object/computed"; -import { inject } from "@ember/controller"; -import Controller from "@ember/controller"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import Group from "discourse/models/group"; import { default as discourseComputed, observes } from "discourse-common/utils/decorators"; +import { popupAjaxError } from "discourse/lib/ajax-error"; import discourseDebounce from "discourse/lib/debounce"; -import User from "discourse/models/user"; export default Controller.extend({ + application: inject(), + queryParams: ["order", "desc", "filter"], + order: "", desc: null, - loading: false, - limit: null, - offset: null, - isOwner: alias("model.is_group_owner"), - showActions: false, filter: null, filterInput: null, - application: inject(), + + loading: false, + isOwner: alias("model.is_group_owner"), + showActions: false, @observes("filterInput") _setFilter: discourseDebounce(function() { @@ -29,19 +27,33 @@ export default Controller.extend({ }, 500), @observes("order", "desc", "filter") - refreshMembers() { - this.set("loading", true); - const model = this.model; + _filtersChanged() { + this.findMembers(true); + }, - if (model && model.can_see_members) { - model.findMembers(this.memberParams).finally(() => { - this.set( - "application.showFooter", - model.members.length >= model.user_count - ); - this.set("loading", false); - }); + findMembers(refresh) { + if (this.loading) { + return; } + + const model = this.model; + if (!model) { + return; + } + + if (!refresh && model.members.length >= model.user_count) { + this.set("application.showFooter", true); + return; + } + + this.set("loading", true); + model.findMembers(this.memberParams, refresh).finally(() => { + this.set( + "application.showFooter", + model.members.length >= model.user_count + ); + this.set("loading", false); + }); }, @discourseComputed("order", "desc", "filter") @@ -49,7 +61,7 @@ export default Controller.extend({ return { order, desc, filter }; }, - @discourseComputed("model.members") + @discourseComputed("model.members.[]") hasMembers(members) { return members && members.length > 0; }, @@ -69,6 +81,10 @@ export default Controller.extend({ }, actions: { + loadMore() { + this.findMembers(); + }, + toggleActions() { this.toggleProperty("showActions"); }, @@ -93,38 +109,6 @@ export default Controller.extend({ .then(() => this.set("usernames", [])) .catch(popupAjaxError); } - }, - - loadMore() { - if (this.loading) { - return; - } - if (this.get("model.members.length") >= this.get("model.user_count")) { - this.set("application.showFooter", true); - return; - } - - this.set("loading", true); - - Group.loadMembers( - this.get("model.name"), - this.get("model.members.length"), - this.limit, - { order: this.order, desc: this.desc } - ).then(result => { - this.get("model.members").addObjects( - result.members.map(member => User.create(member)) - ); - this.setProperties({ - loading: false, - user_count: result.meta.total, - limit: result.meta.limit, - offset: Math.min( - result.meta.offset + result.meta.limit, - result.meta.total - ) - }); - }); } } }); diff --git a/app/assets/javascripts/discourse/controllers/group-requests.js.es6 b/app/assets/javascripts/discourse/controllers/group-requests.js.es6 index ba8c7c971b4..013c44f6d88 100644 --- a/app/assets/javascripts/discourse/controllers/group-requests.js.es6 +++ b/app/assets/javascripts/discourse/controllers/group-requests.js.es6 @@ -1,25 +1,23 @@ -import { inject } from "@ember/controller"; -import Controller from "@ember/controller"; -import { ajax } from "discourse/lib/ajax"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import Group from "discourse/models/group"; +import Controller, { inject } from "@ember/controller"; import { default as discourseComputed, observes } from "discourse-common/utils/decorators"; +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; import discourseDebounce from "discourse/lib/debounce"; -import User from "discourse/models/user"; export default Controller.extend({ + application: inject(), + queryParams: ["order", "desc", "filter"], + order: "", desc: null, - loading: false, - limit: null, - offset: null, filter: null, filterInput: null, - application: inject(), + + loading: false, @observes("filterInput") _setFilter: discourseDebounce(function() { @@ -27,51 +25,41 @@ export default Controller.extend({ }, 500), @observes("order", "desc", "filter") - refreshRequesters(force) { - if (this.loading || !this.model) { + _filtersChanged() { + this.findRequesters(true); + }, + + findRequesters(refresh) { + if (this.loading) { return; } - if ( - !force && - this.count && - this.get("model.requesters.length") >= this.count - ) { + const model = this.model; + if (!model) { + return; + } + + if (!refresh && model.members.length >= model.user_count) { this.set("application.showFooter", true); return; } this.set("loading", true); - this.set("application.showFooter", false); - - Group.loadMembers( - this.get("model.name"), - force ? 0 : this.get("model.requesters.length"), - this.limit, - { - order: this.order, - desc: this.desc, - filter: this.filter, - requesters: true - } - ).then(result => { - const requesters = (!force && this.get("model.requesters")) || []; - requesters.addObjects(result.members.map(m => User.create(m))); - this.set("model.requesters", requesters); - - this.setProperties({ - loading: false, - count: result.meta.total, - limit: result.meta.limit, - offset: Math.min( - result.meta.offset + result.meta.limit, - result.meta.total - ) - }); + model.findRequesters(this.memberParams, refresh).finally(() => { + this.set( + "application.showFooter", + model.requesters.length >= model.user_count + ); + this.set("loading", false); }); }, - @discourseComputed("model.requesters") + @discourseComputed("order", "desc", "filter") + memberParams(order, desc, filter) { + return { order, desc, filter }; + }, + + @discourseComputed("model.requesters.[]") hasRequesters(requesters) { return requesters && requesters.length > 0; }, @@ -94,7 +82,7 @@ export default Controller.extend({ actions: { loadMore() { - this.refreshRequesters(); + this.findRequesters(); }, acceptRequest(user) { diff --git a/app/assets/javascripts/discourse/controllers/group.js.es6 b/app/assets/javascripts/discourse/controllers/group.js.es6 index bd79392b857..b6e9a7ab8f4 100644 --- a/app/assets/javascripts/discourse/controllers/group.js.es6 +++ b/app/assets/javascripts/discourse/controllers/group.js.es6 @@ -21,10 +21,17 @@ export default Controller.extend({ @discourseComputed( "showMessages", "model.user_count", + "model.request_count", "canManageGroup", "model.allow_membership_requests" ) - tabs(showMessages, userCount, canManageGroup, allowMembershipRequests) { + tabs( + showMessages, + userCount, + requestCount, + canManageGroup, + allowMembershipRequests + ) { const membersTab = Tab.create({ name: "members", route: "group.index", @@ -41,7 +48,8 @@ export default Controller.extend({ Tab.create({ name: "requests", i18nKey: "requests.title", - icon: "user-plus" + icon: "user-plus", + count: requestCount }) ); } diff --git a/app/assets/javascripts/discourse/models/group.js.es6 b/app/assets/javascripts/discourse/models/group.js.es6 index 4d6455e891c..78221111340 100644 --- a/app/assets/javascripts/discourse/models/group.js.es6 +++ b/app/assets/javascripts/discourse/models/group.js.es6 @@ -1,31 +1,31 @@ +import EmberObject from "@ember/object"; +import { equal } from "@ember/object/computed"; import { isEmpty } from "@ember/utils"; -import { notEmpty, equal } from "@ember/object/computed"; -import { ajax } from "discourse/lib/ajax"; import { default as discourseComputed, observes } from "discourse-common/utils/decorators"; +import { ajax } from "discourse/lib/ajax"; +import Category from "discourse/models/category"; import GroupHistory from "discourse/models/group-history"; import RestModel from "discourse/models/rest"; -import Category from "discourse/models/category"; -import User from "discourse/models/user"; import Topic from "discourse/models/topic"; -import { popupAjaxError } from "discourse/lib/ajax-error"; -import EmberObject from "@ember/object"; +import User from "discourse/models/user"; const Group = RestModel.extend({ - limit: 50, - offset: 0, user_count: 0, + limit: null, + offset: null, + + request_count: 0, + requestersLimit: null, + requestersOffset: null, init() { this._super(...arguments); - - this.set("owners", []); + this.setProperties({ members: [], requesters: [] }); }, - hasOwners: notEmpty("owners"), - @discourseComputed("automatic_membership_email_domains") emailDomains(value) { return isEmpty(value) ? "" : value; @@ -36,50 +36,76 @@ const Group = RestModel.extend({ return automatic ? "automatic" : "custom"; }, - @discourseComputed("user_count") - userCountDisplay(userCount) { - // don't display zero its ugly - if (userCount > 0) { - return userCount; + findMembers(params, refresh) { + if (isEmpty(this.name) || !this.can_see_members) { + return Ember.RSVP.Promise.reject(); } + + if (refresh) { + this.setProperties({ limit: null, offset: null }); + } + + params = Object.assign( + { offset: (this.offset || 0) + (this.limit || 0) }, + params + ); + + return Group.loadMembers(this.name, params).then(result => { + const ownerIds = new Set(); + result.owners.forEach(owner => ownerIds.add(owner.id)); + + const members = refresh ? [] : this.members; + members.pushObjects( + result.members.map(member => { + member.owner = ownerIds.has(member.id); + return User.create(member); + }) + ); + + this.setProperties({ + members, + user_count: result.meta.total, + limit: result.meta.limit, + offset: result.meta.offset + }); + }); }, - findMembers(params) { + findRequesters(params, refresh) { if (isEmpty(this.name) || !this.can_see_members) { - return; + return Ember.RSVP.Promise.reject(); } - const offset = Math.min(this.user_count, Math.max(this.offset, 0)); + if (refresh) { + this.setProperties({ requestersOffset: null, requestersLimit: null }); + } - return Group.loadMembers(this.name, offset, this.limit, params).then( - result => { - const ownerIds = {}; - result.owners.forEach(owner => (ownerIds[owner.id] = true)); - - this.setProperties({ - user_count: result.meta.total, - limit: result.meta.limit, - offset: result.meta.offset, - members: result.members.map(member => { - if (ownerIds[member.id]) { - member.owner = true; - } - return User.create(member); - }), - owners: result.owners.map(owner => User.create(owner)) - }); - } + params = Object.assign( + { + offset: (this.requestersOffset || 0) + (this.requestersLimit || 0), + requesters: true + }, + params ); + + return Group.loadMembers(this.name, params).then(result => { + const requesters = refresh ? [] : this.requesters; + requesters.pushObjects(result.members.map(m => User.create(m))); + + this.setProperties({ + requesters, + request_count: result.meta.total, + requestersLimit: result.meta.limit, + requestersOffset: result.meta.offset + }); + }); }, removeOwner(member) { return ajax(`/admin/groups/${this.id}/owners.json`, { type: "DELETE", data: { user_id: member.id } - }).then(() => { - // reload member list - this.findMembers(); - }); + }).then(() => this.findMembers()); }, removeMember(member, params) { @@ -272,16 +298,8 @@ Group.reopenClass({ ); }, - loadMembers(name, offset, limit, params) { - return ajax(`/groups/${name}/members.json`, { - data: Object.assign( - { - limit: limit || 50, - offset: offset || 0 - }, - params || {} - ) - }); + loadMembers(name, opts) { + return ajax(`/groups/${name}/members.json`, { data: opts }); }, mentionable(name) { @@ -293,9 +311,7 @@ Group.reopenClass({ }, checkName(name) { - return ajax("/groups/check-name", { - data: { group_name: name } - }).catch(popupAjaxError); + return ajax("/groups/check-name", { data: { group_name: name } }); } }); diff --git a/app/assets/javascripts/discourse/routes/group-index.js.es6 b/app/assets/javascripts/discourse/routes/group-index.js.es6 index a8f630df0ff..a3aed428165 100644 --- a/app/assets/javascripts/discourse/routes/group-index.js.es6 +++ b/app/assets/javascripts/discourse/routes/group-index.js.es6 @@ -19,7 +19,7 @@ export default DiscourseRoute.extend({ filterInput: this._params.filter }); - controller.refreshMembers(); + controller.findMembers(true); }, actions: { diff --git a/app/assets/javascripts/discourse/routes/group-requests.js.es6 b/app/assets/javascripts/discourse/routes/group-requests.js.es6 index b299bb01bff..548529cab0c 100644 --- a/app/assets/javascripts/discourse/routes/group-requests.js.es6 +++ b/app/assets/javascripts/discourse/routes/group-requests.js.es6 @@ -18,6 +18,6 @@ export default DiscourseRoute.extend({ filterInput: this._params.filter }); - controller.refreshRequesters(true); + controller.findRequesters(true); } }); diff --git a/app/assets/javascripts/discourse/templates/components/group-members-input.hbs b/app/assets/javascripts/discourse/templates/components/group-members-input.hbs deleted file mode 100644 index 73c5820a85c..00000000000 --- a/app/assets/javascripts/discourse/templates/components/group-members-input.hbs +++ /dev/null @@ -1,30 +0,0 @@ - - -{{#if model.members}} -