FEATURE: First class messages to groups, you can select a group as a target of a message

This commit is contained in:
Sam 2015-12-02 15:49:43 +11:00
parent fbffe28772
commit 9899e8d4a5
11 changed files with 93 additions and 15 deletions

View File

@ -6,7 +6,9 @@ export default TextField.extend({
_initializeAutocomplete: function() { _initializeAutocomplete: function() {
var self = this, var self = this,
selected = [], selected = [],
groups = [],
currentUser = this.currentUser, currentUser = this.currentUser,
includeMentionableGroups = this.get('includeMentionableGroups') === 'true',
includeGroups = this.get('includeGroups') === 'true', includeGroups = this.get('includeGroups') === 'true',
allowedUsers = this.get('allowedUsers') === 'true'; allowedUsers = this.get('allowedUsers') === 'true';
@ -24,18 +26,22 @@ export default TextField.extend({
allowAny: this.get('allowAny'), allowAny: this.get('allowAny'),
dataSource: function(term) { dataSource: function(term) {
return userSearch({ var results = userSearch({
term: term.replace(/[^a-zA-Z0-9_\-\.]/, ''), term: term.replace(/[^a-zA-Z0-9_\-\.]/, ''),
topicId: self.get('topicId'), topicId: self.get('topicId'),
exclude: excludedUsernames(), exclude: excludedUsernames(),
includeGroups, includeGroups,
allowedUsers allowedUsers,
includeMentionableGroups
}); });
return results;
}, },
transformComplete: function(v) { transformComplete: function(v) {
if (v.username) { if (v.username || v.name) {
return v.username; if (!v.username) { groups.push(v.name); }
return v.username || v.name;
} else { } else {
var excludes = excludedUsernames(); var excludes = excludedUsernames();
return v.usernames.filter(function(item){ return v.usernames.filter(function(item){
@ -45,10 +51,14 @@ export default TextField.extend({
}, },
onChangeItems: function(items) { onChangeItems: function(items) {
var hasGroups = false;
items = items.map(function(i) { items = items.map(function(i) {
if (groups.indexOf(i) > -1) { hasGroups = true; }
return i.username ? i.username : i; return i.username ? i.username : i;
}); });
self.set('usernames', items.join(",")); self.set('usernames', items.join(","));
self.set('hasGroups', hasGroups);
selected = items; selected = items;
}, },

View File

@ -75,9 +75,10 @@ export default Ember.Controller.extend({
if (!Discourse.User.currentProp('staff')) { return false; } if (!Discourse.User.currentProp('staff')) { return false; }
var usernames = this.get('model.targetUsernames'); var usernames = this.get('model.targetUsernames');
var hasTargetGroups = this.get('model.hasTargetGroups');
// We need exactly one user to issue a warning // We need exactly one user to issue a warning
if (Ember.isEmpty(usernames) || usernames.split(',').length !== 1) { if (Ember.isEmpty(usernames) || usernames.split(',').length !== 1 || hasTargetGroups) {
return false; return false;
} }
return this.get('model.creatingPrivateMessage'); return this.get('model.creatingPrivateMessage');

View File

@ -6,7 +6,7 @@ var cache = {},
currentTerm, currentTerm,
oldSearch; oldSearch;
function performSearch(term, topicId, includeGroups, allowedUsers, resultsFn) { function performSearch(term, topicId, includeGroups, includeMentionableGroups, allowedUsers, resultsFn) {
var cached = cache[term]; var cached = cache[term];
if (cached) { if (cached) {
resultsFn(cached); resultsFn(cached);
@ -18,6 +18,7 @@ function performSearch(term, topicId, includeGroups, allowedUsers, resultsFn) {
data: { term: term, data: { term: term,
topic_id: topicId, topic_id: topicId,
include_groups: includeGroups, include_groups: includeGroups,
include_mentionable_groups: includeMentionableGroups,
topic_allowed_users: allowedUsers } topic_allowed_users: allowedUsers }
}); });
@ -76,6 +77,7 @@ function organizeResults(r, options) {
export default function userSearch(options) { export default function userSearch(options) {
var term = options.term || "", var term = options.term || "",
includeGroups = options.includeGroups, includeGroups = options.includeGroups,
includeMentionableGroups = options.includeMentionableGroups,
allowedUsers = options.allowedUsers, allowedUsers = options.allowedUsers,
topicId = options.topicId; topicId = options.topicId;
@ -103,7 +105,7 @@ export default function userSearch(options) {
resolve(CANCELLED_STATUS); resolve(CANCELLED_STATUS);
}, 5000); }, 5000);
debouncedSearch(term, topicId, includeGroups, allowedUsers, function(r) { debouncedSearch(term, topicId, includeGroups, includeMentionableGroups, allowedUsers, function(r) {
clearTimeout(clearPromise); clearTimeout(clearPromise);
resolve(organizeResults(r, options)); resolve(organizeResults(r, options));
}); });

View File

@ -1,8 +1,8 @@
<h3>{{fa-icon "envelope"}} {{i18n 'private_message_info.title'}}</h3> <h3>{{fa-icon 'envelope'}} {{i18n 'private_message_info.title'}}</h3>
<div class='participants clearfix'> <div class='participants clearfix'>
{{#each details.allowed_groups as |ag|}} {{#each details.allowed_groups as |ag|}}
<div class='user group'> <div class='user group'>
#{{unbound ag.name}} {{fa-icon 'users'}} {{#link-to "group.index" ag.name}}{{unbound ag.name}}{{/link-to}}
</div> </div>
{{/each}} {{/each}}
{{#each details.allowed_users as |au|}} {{#each details.allowed_users as |au|}}

View File

@ -41,11 +41,13 @@
{{user-selector topicId=controller.controllers.topic.model.id {{user-selector topicId=controller.controllers.topic.model.id
excludeCurrentUser="true" excludeCurrentUser="true"
id="private-message-users" id="private-message-users"
includeGroups="true" includeMentionableGroups="true"
class="span8" class="span8"
placeholderKey="composer.users_placeholder" placeholderKey="composer.users_placeholder"
tabindex="1" tabindex="1"
usernames=model.targetUsernames}} usernames=model.targetUsernames
hasGroups=model.hasTargetGroups
}}
{{#if showWarning}} {{#if showWarning}}
<div class='add-warning'> <div class='add-warning'>
<label> <label>

View File

@ -507,6 +507,14 @@ class PostsController < ApplicationController
result[:user_agent] = request.user_agent result[:user_agent] = request.user_agent
result[:referrer] = request.env["HTTP_REFERER"] result[:referrer] = request.env["HTTP_REFERER"]
if usernames = result[:target_usernames]
usernames = usernames.split(",")
groups = Group.mentionable(current_user).where('name in (?)', usernames).pluck('name')
usernames -= groups
result[:target_usernames] = usernames.join(",")
result[:target_group_names] = groups.join(",")
end
result result
end end

View File

@ -548,6 +548,14 @@ class UsersController < ApplicationController
end end
end end
if params[:include_mentionable_groups] == "true" && current_user
to_render[:groups] = Group.mentionable(current_user)
.where("name ILIKE :term_like", term_like: "#{term}%")
.map do |m|
{name: m.name, usernames: []}
end
end
render json: to_render render json: to_render
end end

View File

@ -201,7 +201,7 @@ class Group < ActiveRecord::Base
end end
def self.search_group(name) def self.search_group(name)
Group.where(visible: true).where("name ILIKE :term_like", term_like: "#{name.downcase}%") Group.where(visible: true).where("name ILIKE :term_like", term_like: "#{name}%")
end end
def self.lookup_group(name) def self.lookup_group(name)

View File

@ -150,19 +150,32 @@ class TopicCreator
def add_users(topic, usernames) def add_users(topic, usernames)
return unless usernames return unless usernames
User.where(username: usernames.split(',').flatten).each do |user|
names = usernames.split(',').flatten
len = 0
User.where(username: names).each do |user|
check_can_send_permission!(topic, user) check_can_send_permission!(topic, user)
@added_users << user @added_users << user
topic.topic_allowed_users.build(user_id: user.id) topic.topic_allowed_users.build(user_id: user.id)
len += 1
end end
rollback_with!(topic, :target_user_not_found) unless len == names.length
end end
def add_groups(topic, groups) def add_groups(topic, groups)
return unless groups return unless groups
Group.where(name: groups.split(',')).each do |group| names = groups.split(',')
len = 0
Group.where(name: names).each do |group|
check_can_send_permission!(topic, group) check_can_send_permission!(topic, group)
topic.topic_allowed_groups.build(group_id: group.id) topic.topic_allowed_groups.build(group_id: group.id)
len += 1
end end
rollback_with!(topic, :target_group_not_found) unless len == names.length
end end
def check_can_send_permission!(topic, obj) def check_can_send_permission!(topic, obj)

View File

@ -544,7 +544,7 @@ describe PostCreator do
target_group_names: group.name) target_group_names: group.name)
end end
it 'acts correctly' do it 'can post to a group correctly' do
expect(post.topic.archetype).to eq(Archetype.private_message) expect(post.topic.archetype).to eq(Archetype.private_message)
expect(post.topic.topic_allowed_users.count).to eq(1) expect(post.topic.topic_allowed_users.count).to eq(1)
expect(post.topic.topic_allowed_groups.count).to eq(1) expect(post.topic.topic_allowed_groups.count).to eq(1)

View File

@ -583,6 +583,40 @@ describe PostsController do
expect(parsed['cooked']).to be_present expect(parsed['cooked']).to be_present
end end
it "can send a message to a group" do
group = Group.create(name: 'test_group', alias_level: Group::ALIAS_LEVELS[:nobody])
user1 = Fabricate(:user)
group.add(user1)
xhr :post, :create, {
raw: 'I can haz a test',
title: 'I loves my test',
target_usernames: group.name,
archetype: Archetype.private_message
}
expect(response).not_to be_success
# allow pm to this group
group.update_columns(alias_level: Group::ALIAS_LEVELS[:everyone])
xhr :post, :create, {
raw: 'I can haz a test',
title: 'I loves my test',
target_usernames: group.name,
archetype: Archetype.private_message
}
expect(response).to be_success
parsed = ::JSON.parse(response.body)
post = Post.find(parsed['id'])
expect(post.topic.topic_allowed_users.length).to eq(1)
expect(post.topic.topic_allowed_groups.length).to eq(1)
end
it "returns the nested post with a param" do it "returns the nested post with a param" do
xhr :post, :create, {raw: 'this is the test content', xhr :post, :create, {raw: 'this is the test content',
title: 'this is the test title for the topic', title: 'this is the test title for the topic',