FEATURE: can invite/revoke groups on private messages
This commit is contained in:
parent
94df22564f
commit
8866169879
|
@ -3,7 +3,15 @@ import { autoUpdatingRelativeAge } from 'discourse/lib/formatter';
|
|||
export function actionDescriptionHtml(actionCode, createdAt, username) {
|
||||
const dt = new Date(createdAt);
|
||||
const when = autoUpdatingRelativeAge(dt, { format: 'medium-with-ago' });
|
||||
const who = username ? `<a class="mention" href="/users/${username}">@${username}</a>` : "";
|
||||
|
||||
var who = "";
|
||||
if (username) {
|
||||
if (actionCode === "invited_group" || actionCode === "removed_group") {
|
||||
who = `<a class="mention-group" href="/groups/${username}">@${username}</a>`;
|
||||
} else {
|
||||
who = `<a class="mention" href="/users/${username}">@${username}</a>`;
|
||||
}
|
||||
}
|
||||
return I18n.t(`action_codes.${actionCode}`, { who, when }).htmlSafe();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,10 +13,13 @@ export default TextField.extend({
|
|||
allowedUsers = this.get('allowedUsers') === 'true';
|
||||
|
||||
function excludedUsernames() {
|
||||
// hack works around some issues with allowAny eventing
|
||||
const usernames = self.get('single') ? [] : selected;
|
||||
|
||||
if (currentUser && self.get('excludeCurrentUser')) {
|
||||
return selected.concat([currentUser.get('username')]);
|
||||
return usernames.concat([currentUser.get('username')]);
|
||||
}
|
||||
return selected;
|
||||
return usernames;
|
||||
}
|
||||
|
||||
this.$().val(this.get('usernames')).autocomplete({
|
||||
|
|
|
@ -121,6 +121,8 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
successMessage: function() {
|
||||
if (this.get('model.inviteLink')) {
|
||||
return I18n.t('user.invited.generated_link_message', {inviteLink: this.get('model.inviteLink'), invitedEmail: this.get('emailOrUsername')});
|
||||
} else if (this.get('hasGroups')) {
|
||||
return I18n.t('topic.invite_private.success_group');
|
||||
} else if (this.get('isMessage')) {
|
||||
return I18n.t('topic.invite_private.success');
|
||||
} else if ( Discourse.Utilities.emailValid(this.get('emailOrUsername')) ) {
|
||||
|
@ -171,6 +173,22 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
|
||||
model.setProperties({ saving: true, error: false });
|
||||
|
||||
const onerror = function(e) {
|
||||
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
|
||||
self.set("errorMessage", e.jqXHR.responseJSON.errors[0]);
|
||||
} else {
|
||||
self.set("errorMessage", self.get('isMessage') ? I18n.t('topic.invite_private.error') : I18n.t('topic.invite_reply.error'));
|
||||
}
|
||||
model.setProperties({ saving: false, error: true });
|
||||
};
|
||||
|
||||
if (this.get('hasGroups')) {
|
||||
return this.get('model').createGroupInvite(this.get('emailOrUsername').trim()).then(result => {
|
||||
model.setProperties({ saving: false, finished: true });
|
||||
}).catch(onerror);
|
||||
|
||||
} else {
|
||||
|
||||
return this.get('model').createInvite(this.get('emailOrUsername').trim(), groupNames, this.get('customMessage')).then(result => {
|
||||
model.setProperties({ saving: false, finished: true });
|
||||
if (!this.get('invitingToTopic')) {
|
||||
|
@ -181,14 +199,8 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
} else if (this.get('isMessage') && result && result.user) {
|
||||
this.get('model.details.allowed_users').pushObject(Ember.Object.create(result.user));
|
||||
}
|
||||
}).catch(function(e) {
|
||||
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
|
||||
self.set("errorMessage", e.jqXHR.responseJSON.errors[0]);
|
||||
} else {
|
||||
self.set("errorMessage", self.get('isMessage') ? I18n.t('topic.invite_private.error') : I18n.t('topic.invite_reply.error'));
|
||||
}).catch(onerror);
|
||||
}
|
||||
model.setProperties({ saving: false, error: true });
|
||||
});
|
||||
},
|
||||
|
||||
generateInvitelink() {
|
||||
|
|
|
@ -256,6 +256,10 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
|||
return this.get('model.details').removeAllowedUser(user);
|
||||
},
|
||||
|
||||
removeAllowedGroup(group) {
|
||||
return this.get('model.details').removeAllowedGroup(group);
|
||||
},
|
||||
|
||||
deleteTopic() {
|
||||
this.deleteTopic();
|
||||
},
|
||||
|
|
|
@ -63,6 +63,18 @@ const TopicDetails = RestModel.extend({
|
|||
});
|
||||
},
|
||||
|
||||
removeAllowedGroup(group) {
|
||||
const groups = this.get('allowed_groups');
|
||||
const name = group.name;
|
||||
|
||||
return Discourse.ajax("/t/" + this.get('topic.id') + "/remove-allowed-group", {
|
||||
type: 'PUT',
|
||||
data: { name: name }
|
||||
}).then(() => {
|
||||
groups.removeObject(groups.findProperty('name', name));
|
||||
});
|
||||
},
|
||||
|
||||
removeAllowedUser(user) {
|
||||
const users = this.get('allowed_users');
|
||||
const username = user.get('username');
|
||||
|
|
|
@ -309,6 +309,13 @@ const Topic = RestModel.extend({
|
|||
});
|
||||
},
|
||||
|
||||
createGroupInvite(group) {
|
||||
return Discourse.ajax("/t/" + this.get('id') + "/invite-group", {
|
||||
type: 'POST',
|
||||
data: { group }
|
||||
});
|
||||
},
|
||||
|
||||
createInvite(user, group_names, custom_message) {
|
||||
return Discourse.ajax("/t/" + this.get('id') + "/invite", {
|
||||
type: 'POST',
|
||||
|
|
|
@ -11,9 +11,22 @@
|
|||
<label>{{inviteInstructions}}</label>
|
||||
{{#if allowExistingMembers}}
|
||||
{{#if isPrivateTopic}}
|
||||
{{user-selector single="true" allowAny=true excludeCurrentUser="true" usernames=emailOrUsername allowedUsers="true" topicId=topicId placeholderKey=placeholderKey}}
|
||||
{{user-selector single="true"
|
||||
allowAny=true
|
||||
excludeCurrentUser="true"
|
||||
usernames=emailOrUsername
|
||||
allowedUsers="true"
|
||||
topicId=topicId
|
||||
placeholderKey=placeholderKey}}
|
||||
{{else}}
|
||||
{{user-selector single="true" allowAny=true excludeCurrentUser="true" usernames=emailOrUsername placeholderKey=placeholderKey}}
|
||||
{{user-selector
|
||||
single="true"
|
||||
allowAny=true
|
||||
excludeCurrentUser="true"
|
||||
includeMentionableGroups="true"
|
||||
hasGroups=hasGroups
|
||||
usernames=emailOrUsername
|
||||
placeholderKey=placeholderKey}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{text-field value=emailOrUsername placeholderKey="topic.invite_reply.email_placeholder"}}
|
||||
|
|
|
@ -113,6 +113,7 @@
|
|||
toggleWiki="toggleWiki"
|
||||
toggleSummary="toggleSummary"
|
||||
removeAllowedUser="removeAllowedUser"
|
||||
removeAllowedGroup="removeAllowedGroup"
|
||||
showInvite="showInvite"
|
||||
topVisibleChanged="topVisibleChanged"
|
||||
currentPostChanged="currentPostChanged"
|
||||
|
|
|
@ -20,7 +20,9 @@ const icons = {
|
|||
'visible.disabled': 'eye-slash',
|
||||
'split_topic': 'sign-out',
|
||||
'invited_user': 'plus-circle',
|
||||
'invited_group': 'plus-circle',
|
||||
'removed_user': 'minus-circle',
|
||||
'removed_group': 'minus-circle',
|
||||
'public_topic': 'comment',
|
||||
'private_topic': 'envelope'
|
||||
};
|
||||
|
|
|
@ -3,12 +3,33 @@ import { createWidget } from 'discourse/widgets/widget';
|
|||
import { h } from 'virtual-dom';
|
||||
import { avatarFor } from 'discourse/widgets/post';
|
||||
|
||||
createWidget('pm-remove-group-link', {
|
||||
tagName: 'a.remove-invited',
|
||||
|
||||
html() {
|
||||
return iconNode('times');
|
||||
},
|
||||
|
||||
click() {
|
||||
bootbox.confirm(I18n.t("private_message_info.remove_allowed_group", {name: this.attrs.name}), (confirmed) => {
|
||||
if (confirmed) { this.sendWidgetAction('removeAllowedGroup', this.attrs); }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
createWidget('pm-map-user-group', {
|
||||
tagName: 'div.user.group',
|
||||
|
||||
html(attrs) {
|
||||
const link = h('a', { attributes: { href: Discourse.getURL(`/groups/${attrs.name}`) } }, attrs.name);
|
||||
return [iconNode('users'), ' ', link];
|
||||
const link = h('a', { attributes: { href: Discourse.getURL(`/groups/${attrs.group.name}`) } }, attrs.group.name);
|
||||
const result = [iconNode('users'), ' ', link];
|
||||
|
||||
if (attrs.canRemoveAllowedUsers) {
|
||||
result.push(' ');
|
||||
result.push(this.attach('pm-remove-group-link', attrs.group));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -51,12 +72,12 @@ export default createWidget('private-message-map', {
|
|||
const participants = [];
|
||||
|
||||
if (attrs.allowedGroups.length) {
|
||||
participants.push(attrs.allowedGroups.map(ag => this.attach('pm-map-user-group', ag)));
|
||||
participants.push(attrs.allowedGroups.map(ag => this.attach('pm-map-user-group', {group: ag, canRemoveAllowedUsers: attrs.canRemoveAllowedUsers})));
|
||||
}
|
||||
|
||||
if (attrs.allowedUsers.length) {
|
||||
participants.push(attrs.allowedUsers.map(ag => {
|
||||
return this.attach('pm-map-user', { user: ag, canRemoveAllowedUsers: attrs.canRemoveAllowedUsers });
|
||||
participants.push(attrs.allowedUsers.map(au => {
|
||||
return this.attach('pm-map-user', { user: au, canRemoveAllowedUsers: attrs.canRemoveAllowedUsers });
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -375,6 +375,34 @@ class TopicsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def remove_allowed_group
|
||||
params.require(:name)
|
||||
topic = Topic.find_by(id: params[:topic_id])
|
||||
guardian.ensure_can_remove_allowed_users!(topic)
|
||||
|
||||
if topic.remove_allowed_group(current_user, params[:name])
|
||||
render json: success_json
|
||||
else
|
||||
render json: failed_json, status: 422
|
||||
end
|
||||
end
|
||||
|
||||
def invite_group
|
||||
group = Group.find_by(name: params[:group])
|
||||
raise Discourse::NotFound unless group
|
||||
|
||||
topic = Topic.find_by(id: params[:topic_id])
|
||||
|
||||
if topic.private_message?
|
||||
guardian.ensure_can_send_private_message!(group)
|
||||
topic.invite_group(current_user, group)
|
||||
|
||||
render json: success_json
|
||||
else
|
||||
render json: failed_json, status: 422
|
||||
end
|
||||
end
|
||||
|
||||
def invite
|
||||
username_or_email = params[:user] ? fetch_username : fetch_email
|
||||
|
||||
|
|
|
@ -577,6 +577,19 @@ class Topic < ActiveRecord::Base
|
|||
changed_to_category(cat)
|
||||
end
|
||||
|
||||
def remove_allowed_group(removed_by, name)
|
||||
if group = Group.find_by(name: name)
|
||||
group_user = topic_allowed_groups.find_by(group_id: group.id)
|
||||
if group_user
|
||||
group_user.destroy
|
||||
add_small_action(removed_by, "removed_group", group.name)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def remove_allowed_user(removed_by, username)
|
||||
if user = User.find_by(username: username)
|
||||
topic_user = topic_allowed_users.find_by(user_id: user.id)
|
||||
|
@ -590,6 +603,19 @@ class Topic < ActiveRecord::Base
|
|||
false
|
||||
end
|
||||
|
||||
def invite_group(user, group)
|
||||
TopicAllowedGroup.create!(topic_id: id, group_id: group.id)
|
||||
|
||||
last_post = posts.order('post_number desc').where('not hidden AND posts.deleted_at IS NULL').first
|
||||
if last_post
|
||||
# ensure all the notifications are out
|
||||
PostAlerter.new.after_save_post(last_post)
|
||||
add_small_action(user, "invited_group", group.name)
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Invite a user to the topic by username or email. Returns success/failure
|
||||
def invite(invited_by, username_or_email, group_ids=nil, custom_message=nil)
|
||||
if private_message?
|
||||
|
|
|
@ -144,7 +144,9 @@ en:
|
|||
private_topic: "made this topic private %{when}"
|
||||
split_topic: "split this topic %{when}"
|
||||
invited_user: "invited %{who} %{when}"
|
||||
invited_group: "invited %{who} %{when}"
|
||||
removed_user: "removed %{who} %{when}"
|
||||
removed_group: "removed %{who} %{when}"
|
||||
autoclosed:
|
||||
enabled: 'closed %{when}'
|
||||
disabled: 'opened %{when}'
|
||||
|
@ -908,6 +910,7 @@ en:
|
|||
title: "Message"
|
||||
invite: "Invite Others..."
|
||||
remove_allowed_user: "Do you really want to remove {{name}} from this message?"
|
||||
remove_allowed_group: "Do you really want to remove {{name}} from this message?"
|
||||
|
||||
email: 'Email'
|
||||
username: 'Username'
|
||||
|
@ -1448,6 +1451,7 @@ en:
|
|||
email_or_username_placeholder: "email address or username"
|
||||
action: "Invite"
|
||||
success: "We've invited that user to participate in this message."
|
||||
success_group: "We've invited that group to participate in this message."
|
||||
error: "Sorry, there was an error inviting that user."
|
||||
group_name: "group name"
|
||||
|
||||
|
|
|
@ -547,6 +547,7 @@ Discourse::Application.routes.draw do
|
|||
put "t/:topic_id/make-banner" => "topics#make_banner", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/remove-banner" => "topics#remove_banner", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/remove-allowed-user" => "topics#remove_allowed_user", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/remove-allowed-group" => "topics#remove_allowed_group", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/recover" => "topics#recover", constraints: {topic_id: /\d+/}
|
||||
get "t/:topic_id/:post_number" => "topics#show", constraints: {topic_id: /\d+/, post_number: /\d+/}
|
||||
get "t/:topic_id/last" => "topics#show", post_number: 99999999, constraints: {topic_id: /\d+/}
|
||||
|
@ -557,6 +558,7 @@ Discourse::Application.routes.draw do
|
|||
get "t/:topic_id/posts" => "topics#posts", constraints: {topic_id: /\d+/}, format: :json
|
||||
post "t/:topic_id/timings" => "topics#timings", constraints: {topic_id: /\d+/}
|
||||
post "t/:topic_id/invite" => "topics#invite", constraints: {topic_id: /\d+/}
|
||||
post "t/:topic_id/invite-group" => "topics#invite_group", constraints: {topic_id: /\d+/}
|
||||
post "t/:topic_id/move-posts" => "topics#move_posts", constraints: {topic_id: /\d+/}
|
||||
post "t/:topic_id/merge-topic" => "topics#merge_topic", constraints: {topic_id: /\d+/}
|
||||
post "t/:topic_id/change-owner" => "topics#change_post_owners", constraints: {topic_id: /\d+/}
|
||||
|
|
|
@ -950,6 +950,35 @@ describe TopicsController do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'invite_group' do
|
||||
let :admins do
|
||||
Group[:admins]
|
||||
end
|
||||
|
||||
let! :admin do
|
||||
log_in :admin
|
||||
end
|
||||
|
||||
before do
|
||||
admins.alias_level = Group::ALIAS_LEVELS[:everyone]
|
||||
admins.save!
|
||||
end
|
||||
|
||||
it "disallows inviting a group to a topic" do
|
||||
topic = Fabricate(:topic)
|
||||
xhr :post, :invite_group, topic_id: topic.id, group: 'admins'
|
||||
expect(response.status).to eq(422)
|
||||
end
|
||||
|
||||
it "allows inviting a group to a PM" do
|
||||
topic = Fabricate(:private_message_topic)
|
||||
xhr :post, :invite_group, topic_id: topic.id, group: 'admins'
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(topic.allowed_groups.first.id).to eq(admins.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'invite' do
|
||||
|
||||
describe "group invites" do
|
||||
|
|
|
@ -372,6 +372,25 @@ describe Topic do
|
|||
context 'existing user' do
|
||||
let(:walter) { Fabricate(:walter_white) }
|
||||
|
||||
context 'by group name' do
|
||||
|
||||
it 'can add admin to allowed groups' do
|
||||
admins = Group[:admins]
|
||||
admins.alias_level = Group::ALIAS_LEVELS[:everyone]
|
||||
admins.save
|
||||
|
||||
expect(topic.invite_group(topic.user, admins)).to eq(true)
|
||||
|
||||
expect(topic.allowed_groups.include?(admins)).to eq(true)
|
||||
|
||||
expect(topic.remove_allowed_group(topic.user, 'admins')).to eq(true)
|
||||
topic.reload
|
||||
|
||||
expect(topic.allowed_groups.include?(admins)).to eq(false)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'by username' do
|
||||
|
||||
it 'adds and removes walter to the allowed users' do
|
||||
|
|
Loading…
Reference in New Issue