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

View file

@ -9,6 +9,13 @@
**/
Discourse.InvitePrivateController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
modalClass: 'invite',
onShow: function(){
this.set('controllers.modal.modalClass', 'invite-modal');
this.set('emailOrUsername', '');
},
disabled: function() {
if (this.get('saving')) return true;
return this.blank('emailOrUsername');
@ -27,10 +34,14 @@ Discourse.InvitePrivateController = Discourse.ObjectController.extend(Discourse.
this.set('saving', true);
this.set('error', false);
// 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
invitePrivateController.set('saving', false);
invitePrivateController.set('finished', true);
if(result && result.user) {
invitePrivateController.get('content.allowed_users').pushObject(result.user);
}
}, function() {
// Failure
invitePrivateController.set('error', true);

View file

@ -429,6 +429,10 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
if (onPostRendered) {
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() {
return this.get('starred') ? 'favorite.help.unstar' : 'favorite.help.star';
}).property('starred'),
@ -274,6 +285,7 @@ Discourse.Topic = Discourse.Model.extend({
lastPost = post;
});
topic.set('allowed_users', Em.A(result.allowed_users));
topic.set('loaded', true);
}

View file

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

View file

@ -10,7 +10,7 @@
{{i18n topic.invite_private.success}}
{{else}}
<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}}
</div>
<div class="modal-footer">

View file

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

View file

@ -8,14 +8,5 @@
**/
Discourse.InvitePrivateView = Discourse.ModalBodyView.extend({
templateName: 'modal/invite_private',
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;
}
title: Em.String.i18n('topic.invite_private.title')
});

View file

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

View file

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

View file

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

View file

@ -135,13 +135,30 @@ class TopicsController < ApplicationController
render nothing: true
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
params.require(:user)
topic = Topic.where(id: params[:topic_id]).first
guardian.ensure_can_invite_to!(topic)
if topic.invite(current_user, params[:user])
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
render json: failed_json, status: 422
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
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
def invite(invited_by, username_or_email)
if private_message?

View file

@ -23,7 +23,7 @@ class TopicViewSerializer < ApplicationSerializer
end
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
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/unmute' => 'topics#unmute', 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/: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
end
def can_remove_allowed_users?(topic)
is_staff?
end
# Support for ensure_{blah}! methods.
def method_missing(method, *args, &block)
if method.to_s =~ /^ensure_(.*)\!$/

View file

@ -410,9 +410,13 @@ describe Topic 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.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
it 'creates a notification' do