fix up find as you type for the invite into PM function

allow mods to remove users from a PM
This commit is contained in:
Sam
2013-06-18 17:17:01 +10:00
parent 2eb1cc220c
commit 80c42753e1
17 changed files with 119 additions and 26 deletions

View File

@ -46,6 +46,10 @@ $.fn.autocomplete = function(options) {
if (options.transformComplete) { if (options.transformComplete) {
transformed = options.transformComplete(item); transformed = options.transformComplete(item);
} }
if (options.single){
// dump what we have in single mode, just in case
inputSelectedItems = [];
}
var d = $("<div class='item'><span>" + (transformed || item) + "<a href='#'><i class='icon-remove'></i></a></span></div>"); var d = $("<div class='item'><span>" + (transformed || item) + "<a href='#'><i class='icon-remove'></i></a></span></div>");
var prev = me.parent().find('.item:last'); var prev = me.parent().find('.item:last');
if (prev.length === 0) { if (prev.length === 0) {
@ -57,12 +61,16 @@ $.fn.autocomplete = function(options) {
if (options.onChangeItems) { if (options.onChangeItems) {
options.onChangeItems(inputSelectedItems); options.onChangeItems(inputSelectedItems);
} }
return d.find('a').click(function() {
d.find('a').click(function() {
closeAutocomplete(); closeAutocomplete();
inputSelectedItems.splice($.inArray(item), 1); inputSelectedItems.splice($.inArray(item), 1);
$(this).parent().parent().remove(); $(this).parent().parent().remove();
if (options.single) {
me.show();
}
if (options.onChangeItems) { if (options.onChangeItems) {
return options.onChangeItems(inputSelectedItems); options.onChangeItems(inputSelectedItems);
} }
}); });
}; };
@ -71,6 +79,9 @@ $.fn.autocomplete = function(options) {
if (term) { if (term) {
if (isInput) { if (isInput) {
me.val(""); me.val("");
if(options.single){
me.hide();
}
addInputSelectedItem(term); addInputSelectedItem(term);
} else { } else {
if (options.transformComplete) { if (options.transformComplete) {
@ -90,7 +101,11 @@ $.fn.autocomplete = function(options) {
var height = this.height(); var height = this.height();
wrap = this.wrap("<div class='ac-wrap clearfix" + (disabled ? " disabled": "") + "'/>").parent(); wrap = this.wrap("<div class='ac-wrap clearfix" + (disabled ? " disabled": "") + "'/>").parent();
wrap.width(width); wrap.width(width);
this.width(150); if(options.single) {
this.css("width","100%");
} else {
this.width(150);
}
this.attr('name', this.attr('name') + "-renamed"); this.attr('name', this.attr('name') + "-renamed");
var vals = this.val().split(","); var vals = this.val().split(",");
_.each(vals,function(x) { _.each(vals,function(x) {
@ -98,7 +113,7 @@ $.fn.autocomplete = function(options) {
if (options.reverseTransform) { if (options.reverseTransform) {
x = options.reverseTransform(x); x = options.reverseTransform(x);
} }
return addInputSelectedItem(x); addInputSelectedItem(x);
} }
}); });
this.val(""); this.val("");
@ -185,10 +200,22 @@ $.fn.autocomplete = function(options) {
if (oldClose) { if (oldClose) {
oldClose(); oldClose();
} }
return closeAutocomplete(); closeAutocomplete();
}); });
$(this).keypress(function(e) { $(this).keypress(function(e) {
if(options.allowAny){
if(inputSelectedItems.length === 0) {
inputSelectedItems.push("");
}
inputSelectedItems.pop();
inputSelectedItems.push(me.val());
if (options.onChangeItems) {
options.onChangeItems(inputSelectedItems);
}
}
if (!options.key) return; if (!options.key) return;
// keep hunting backwards till you hit a // keep hunting backwards till you hit a
@ -264,7 +291,7 @@ $.fn.autocomplete = function(options) {
// We're cancelling it, really. // We're cancelling it, really.
return true; return true;
} }
closeAutocomplete(); e.stopImmediatePropagation();
return false; return false;
case 38: case 38:
selectedOption = selectedOption - 1; selectedOption = selectedOption - 1;

View File

@ -9,6 +9,13 @@
**/ **/
Discourse.InvitePrivateController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, { Discourse.InvitePrivateController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
modalClass: 'invite',
onShow: function(){
this.set('controllers.modal.modalClass', 'invite-modal');
this.set('emailOrUsername', '');
},
disabled: function() { disabled: function() {
if (this.get('saving')) return true; if (this.get('saving')) return true;
return this.blank('emailOrUsername'); return this.blank('emailOrUsername');
@ -27,10 +34,14 @@ Discourse.InvitePrivateController = Discourse.ObjectController.extend(Discourse.
this.set('saving', true); this.set('saving', true);
this.set('error', false); this.set('error', false);
// Invite the user to the private message // Invite the user to the private message
this.get('content').inviteUser(this.get('emailOrUsername')).then(function() { this.get('content').inviteUser(this.get('emailOrUsername')).then(function(result) {
// Success // Success
invitePrivateController.set('saving', false); invitePrivateController.set('saving', false);
invitePrivateController.set('finished', true); invitePrivateController.set('finished', true);
if(result && result.user) {
invitePrivateController.get('content.allowed_users').pushObject(result.user);
}
}, function() { }, function() {
// Failure // Failure
invitePrivateController.set('error', true); invitePrivateController.set('error', true);
@ -39,4 +50,4 @@ Discourse.InvitePrivateController = Discourse.ObjectController.extend(Discourse.
return false; return false;
} }
}); });

View File

@ -429,6 +429,10 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
if (onPostRendered) { if (onPostRendered) {
onPostRendered(post); onPostRendered(post);
} }
},
removeAllowedUser: function(username) {
this.get('model').removeAllowedUser(username);
} }
}); });

View File

@ -150,6 +150,17 @@ Discourse.Topic = Discourse.Model.extend({
}); });
}, },
removeAllowedUser: function(username) {
var allowedUsers = this.get('allowed_users');
return Discourse.ajax("/t/" + this.get('id') + "/remove-allowed-user", {
type: 'PUT',
data: { username: username }
}).then(function(){
allowedUsers.removeObject(allowedUsers.find(function(item){ return item.username === username; }));
});
},
favoriteTooltipKey: (function() { favoriteTooltipKey: (function() {
return this.get('starred') ? 'favorite.help.unstar' : 'favorite.help.star'; return this.get('starred') ? 'favorite.help.unstar' : 'favorite.help.star';
}).property('starred'), }).property('starred'),
@ -274,6 +285,7 @@ Discourse.Topic = Discourse.Model.extend({
lastPost = post; lastPost = post;
}); });
topic.set('allowed_users', Em.A(result.allowed_users));
topic.set('loaded', true); topic.set('loaded', true);
} }

View File

@ -56,6 +56,9 @@ Discourse.Route.reopenClass({
if (model) { if (model) {
controller.set('model', model); controller.set('model', model);
} }
if(controller && controller.onShow) {
controller.onShow();
}
controller.set('flashMessage', null); controller.set('flashMessage', null);
} }
} }

View File

@ -10,7 +10,7 @@
{{i18n topic.invite_private.success}} {{i18n topic.invite_private.success}}
{{else}} {{else}}
<label>{{i18n topic.invite_private.email_or_username}}</label> <label>{{i18n topic.invite_private.email_or_username}}</label>
{{textField value=emailOrUsername placeholderKey="topic.invite_private.email_or_username_placeholder"}} {{userSelector single=true allowAny=true usernames=emailOrUsername placeholderKey="topic.invite_private.email_or_username_placeholder"}}
{{/if}} {{/if}}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@ -20,4 +20,4 @@
<button class='btn btn-primary' {{bindAttr disabled="disabled"}} {{action invite}}>{{buttonTitle}}</button> <button class='btn btn-primary' {{bindAttr disabled="disabled"}} {{action invite}}>{{buttonTitle}}</button>
{{/if}} {{/if}}
</div> </div>

View File

@ -13,6 +13,9 @@
<a href='/users/{{lower username}}'> <a href='/users/{{lower username}}'>
{{unbound username}} {{unbound username}}
</a> </a>
{{#if controller.model.can_remove_allowed_users}}
<a class='remove-invited' {{action removeAllowedUser username}}>x</a>
{{/if}}
</div> </div>
{{/each}} {{/each}}
</div> </div>

View File

@ -8,14 +8,5 @@
**/ **/
Discourse.InvitePrivateView = Discourse.ModalBodyView.extend({ Discourse.InvitePrivateView = Discourse.ModalBodyView.extend({
templateName: 'modal/invite_private', templateName: 'modal/invite_private',
title: Em.String.i18n('topic.invite_private.title'), title: Em.String.i18n('topic.invite_private.title')
keyUp: function(e) {
// Add the invitee if they hit enter
if (e.keyCode === 13) { this.get('controller').invite(); }
return false;
}
}); });

View File

@ -1,6 +1,7 @@
Discourse.UserSelector = Discourse.TextField.extend({ Discourse.UserSelector = Discourse.TextField.extend({
didInsertElement: function(){ didInsertElement: function(){
var userSelectorView = this; var userSelectorView = this;
var selected = []; var selected = [];
var transformTemplate = Handlebars.compile("{{avatar this imageSize=\"tiny\"}} {{this.username}}"); var transformTemplate = Handlebars.compile("{{avatar this imageSize=\"tiny\"}} {{this.username}}");
@ -9,7 +10,8 @@ Discourse.UserSelector = Discourse.TextField.extend({
template: Discourse.UserSelector.templateFunction(), template: Discourse.UserSelector.templateFunction(),
disabled: this.get('disabled'), disabled: this.get('disabled'),
single: this.get('single'),
allowAny: this.get('allowAny'),
dataSource: function(term) { dataSource: function(term) {
var exclude = selected; var exclude = selected;
if (userSelectorView.get('excludeCurrentUser')){ if (userSelectorView.get('excludeCurrentUser')){

View File

@ -71,7 +71,7 @@
.autocomplete { .autocomplete {
z-index: 9999; z-index: 999999;
position: absolute; position: absolute;
width: 200px; width: 200px;
background-color: $white; background-color: $white;
@ -354,7 +354,7 @@ div.ac-wrap.disabled {
div.ac-wrap { div.ac-wrap {
background-color: $white; background-color: $white;
border: 1px solid #cccccc; border: 1px solid #cccccc;
padding: 5px 10px 0; padding: 5px 10px;
@include border-radius-all(3px); @include border-radius-all(3px);
div.item { div.item {
float: left; float: left;

View File

@ -222,3 +222,10 @@
.modal-tab { .modal-tab {
position: absolute; position: absolute;
} }
.invite-modal {
overflow: visible;
.ember-text-field {
width: 550px;
}
}

View File

@ -135,13 +135,30 @@ class TopicsController < ApplicationController
render nothing: true render nothing: true
end end
def remove_allowed_user
params.require(:username)
topic = Topic.where(id: params[:topic_id]).first
guardian.ensure_can_remove_allowed_users!(topic)
if topic.remove_allowed_user(params[:username])
render json: success_json
else
render json: failed_json, status: 422
end
end
def invite def invite
params.require(:user) params.require(:user)
topic = Topic.where(id: params[:topic_id]).first topic = Topic.where(id: params[:topic_id]).first
guardian.ensure_can_invite_to!(topic) guardian.ensure_can_invite_to!(topic)
if topic.invite(current_user, params[:user]) if topic.invite(current_user, params[:user])
render json: success_json user = User.find_by_username_or_email(params[:user]).first
if user
render_json_dump BasicUserSerializer.new(user, scope: guardian, root: 'user')
else
render json: success_json
end
else else
render json: failed_json, status: 422 render json: failed_json, status: 422
end end

View File

@ -370,6 +370,13 @@ class Topic < ActiveRecord::Base
[featured_user1_id, featured_user2_id, featured_user3_id, featured_user4_id].uniq.compact [featured_user1_id, featured_user2_id, featured_user3_id, featured_user4_id].uniq.compact
end end
def remove_allowed_user(username)
user = User.where(username: username).first
if user
topic_allowed_users.where(user_id: user.id).first.destroy
end
end
# Invite a user to the topic by username or email. Returns success/failure # Invite a user to the topic by username or email. Returns success/failure
def invite(invited_by, username_or_email) def invite(invited_by, username_or_email)
if private_message? if private_message?

View File

@ -23,7 +23,7 @@ class TopicViewSerializer < ApplicationSerializer
end end
def self.guardian_attributes def self.guardian_attributes
[:can_moderate, :can_edit, :can_delete, :can_invite_to, :can_move_posts] [:can_moderate, :can_edit, :can_delete, :can_invite_to, :can_move_posts, :can_remove_allowed_users]
end end
attributes *topic_attributes attributes *topic_attributes

View File

@ -211,6 +211,7 @@ Discourse::Application.routes.draw do
put 't/:topic_id/mute' => 'topics#mute', constraints: {topic_id: /\d+/} put 't/:topic_id/mute' => 'topics#mute', constraints: {topic_id: /\d+/}
put 't/:topic_id/unmute' => 'topics#unmute', constraints: {topic_id: /\d+/} put 't/:topic_id/unmute' => 'topics#unmute', constraints: {topic_id: /\d+/}
put 't/:topic_id/autoclose' => 'topics#autoclose', constraints: {topic_id: /\d+/} put 't/:topic_id/autoclose' => 'topics#autoclose', constraints: {topic_id: /\d+/}
put 't/:topic_id/remove-allowed-user' => 'topics#remove_allowed_user', constraints: {topic_id: /\d+/}
get 't/:topic_id/:post_number' => 'topics#show', constraints: {topic_id: /\d+/, post_number: /\d+/} get 't/:topic_id/:post_number' => 'topics#show', constraints: {topic_id: /\d+/, post_number: /\d+/}
get 't/:slug/:topic_id.rss' => 'topics#feed', format: :rss, constraints: {topic_id: /\d+/} get 't/:slug/:topic_id.rss' => 'topics#feed', format: :rss, constraints: {topic_id: /\d+/}

View File

@ -195,6 +195,10 @@ class Guardian
is_staff? && user.created_at >= 7.days.ago is_staff? && user.created_at >= 7.days.ago
end end
def can_remove_allowed_users?(topic)
is_staff?
end
# Support for ensure_{blah}! methods. # Support for ensure_{blah}! methods.
def method_missing(method, *args, &block) def method_missing(method, *args, &block)
if method.to_s =~ /^ensure_(.*)\!$/ if method.to_s =~ /^ensure_(.*)\!$/

View File

@ -410,9 +410,13 @@ describe Topic do
context 'by username' do context 'by username' do
it 'adds walter to the allowed users' do it 'adds and removes walter to the allowed users' do
topic.invite(topic.user, walter.username).should be_true topic.invite(topic.user, walter.username).should be_true
topic.allowed_users.include?(walter).should be_true topic.allowed_users.include?(walter).should be_true
topic.remove_allowed_user(walter.username).should be_true
topic.reload
topic.allowed_users.include?(walter).should be_false
end end
it 'creates a notification' do it 'creates a notification' do