diff --git a/app/assets/javascripts/discourse/controllers/invite-private.js.es6 b/app/assets/javascripts/discourse/controllers/invite-private.js.es6 deleted file mode 100644 index 94e1d6632de..00000000000 --- a/app/assets/javascripts/discourse/controllers/invite-private.js.es6 +++ /dev/null @@ -1,47 +0,0 @@ -import ModalFunctionality from 'discourse/mixins/modal-functionality'; -import ObjectController from 'discourse/controllers/object'; - -export default ObjectController.extend(ModalFunctionality, { - modalClass: 'invite', - - isAdmin: function(){ - return Discourse.User.currentProp("admin"); - }.property(), - - onShow: function(){ - this.set('controllers.modal.modalClass', 'invite-modal'); - this.set('emailOrUsername', ''); - }, - - disabled: function() { - if (this.get('saving')) return true; - return this.blank('emailOrUsername'); - }.property('emailOrUsername', 'saving'), - - buttonTitle: function() { - if (this.get('saving')) return I18n.t('topic.inviting'); - return I18n.t('topic.invite_private.action'); - }.property('saving'), - - actions: { - invite: function() { - if (this.get('disabled')) return; - - var self = this; - this.setProperties({saving: true, error: false}); - - // Invite the user to the private message - this.get('model').createInvite(this.get('emailOrUsername')).then(function(result) { - self.setProperties({saving: true, finished: true}); - - if(result && result.user) { - self.get('model.details.allowed_users').pushObject(result.user); - } - }).catch(function() { - self.setProperties({error: true, saving: false}); - }); - return false; - } - } - -}); diff --git a/app/assets/javascripts/discourse/controllers/invite.js.es6 b/app/assets/javascripts/discourse/controllers/invite.js.es6 index 6adc0f02081..ad595bb4c46 100644 --- a/app/assets/javascripts/discourse/controllers/invite.js.es6 +++ b/app/assets/javascripts/discourse/controllers/invite.js.es6 @@ -6,7 +6,7 @@ export default ObjectController.extend(ModalFunctionality, { // If this isn't defined, it will proxy to the user model on the preferences // page which is wrong. - email: null, + emailOrUsername: null, isAdmin: function(){ return Discourse.User.currentProp("admin"); @@ -19,12 +19,12 @@ export default ObjectController.extend(ModalFunctionality, { **/ disabled: function() { if (this.get('saving')) return true; - if (this.blank('email')) return true; - if (!Discourse.Utilities.emailValid(this.get('email'))) return true; + if (this.blank('emailOrUsername')) return true; + if ( !this.get('invitingToTopic') && !Discourse.Utilities.emailValid(this.get('emailOrUsername')) ) return true; if (this.get('model.details.can_invite_to')) return false; if (this.get('isPrivateTopic') && this.blank('groupNames')) return true; return false; - }.property('email', 'isPrivateTopic', 'groupNames', 'saving'), + }.property('emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'groupNames', 'saving'), /** The current text for the invite button @@ -53,18 +53,45 @@ export default ObjectController.extend(ModalFunctionality, { **/ isPrivateTopic: Em.computed.and('invitingToTopic', 'model.category.read_restricted'), + /** + Is Message? + + @property isMessage + **/ + isMessage: Em.computed.equal('model.archetype', 'private_message'), + + /** + Allow Existing Members? (username autocomplete) + + @property allowExistingMembers + **/ + allowExistingMembers: function() { + return this.get('invitingToTopic') && !this.get('isPrivateTopic'); + }.property('invitingToTopic', 'isPrivateTopic'), + + /** + Show Groups? (add invited user to private group) + + @property showGroups + **/ + showGroups: function() { + return this.get('isAdmin') && (Discourse.Utilities.emailValid(this.get('emailOrUsername')) || this.get('isPrivateTopic') || !this.get('invitingToTopic')); + }.property('isAdmin', 'emailOrUsername', 'isPrivateTopic', 'invitingToTopic'), + /** Instructional text for the modal. @property inviteInstructions **/ inviteInstructions: function() { - if (this.get('invitingToTopic')) { + if (this.get('isMessage')) { + return I18n.t('topic.invite_private.email_or_username'); + } else if (this.get('invitingToTopic')) { return I18n.t('topic.invite_reply.to_topic'); } else { return I18n.t('topic.invite_reply.to_forum'); } - }.property('invitingToTopic'), + }.property('isMessage', 'invitingToTopic'), /** Instructional text for the group selection. @@ -92,8 +119,25 @@ export default ObjectController.extend(ModalFunctionality, { @property successMessage **/ successMessage: function() { - return I18n.t('topic.invite_reply.success', { email: this.get('email') }); - }.property('email'), + if (this.get('isMessage')) { + return I18n.t('topic.invite_private.success'); + } else { + return I18n.t('topic.invite_reply.success', { emailOrUsername: this.get('emailOrUsername') }); + } + }.property('isMessage', 'emailOrUsername'), + + /** + The "error" text for when the invite fails. + + @property errorMessage + **/ + errorMessage: function() { + if (this.get('isMessage')) { + return I18n.t('topic.invite_private.error'); + } else { + return I18n.t('topic.invite_reply.error'); + } + }.property('isMessage'), /** Reset the modal to allow a new user to be invited. @@ -102,7 +146,7 @@ export default ObjectController.extend(ModalFunctionality, { **/ reset: function() { this.setProperties({ - email: null, + emailOrUsername: null, groupNames: null, error: false, saving: false, @@ -126,13 +170,15 @@ export default ObjectController.extend(ModalFunctionality, { var userInvitedController = this.get('controllers.user-invited'); this.setProperties({ saving: true, error: false }); - this.get('model').createInvite(this.get('email'), groupNames).then(function() { + this.get('model').createInvite(this.get('emailOrUsername'), groupNames).then(function(result) { self.setProperties({ saving: false, finished: true }); if (!self.get('invitingToTopic')) { Discourse.Invite.findInvitedBy(Discourse.User.current()).then(function (invite_model) { userInvitedController.set('model', invite_model); userInvitedController.set('totalInvites', invite_model.invites.length); }); + } else if (self.get('isMessage') && result && result.user) { + self.get('model.details.allowed_users').pushObject(result.user); } }).catch(function() { self.setProperties({ saving: false, error: true }); diff --git a/app/assets/javascripts/discourse/routes/topic.js.es6 b/app/assets/javascripts/discourse/routes/topic.js.es6 index d7948ce3a35..725c895ead2 100644 --- a/app/assets/javascripts/discourse/routes/topic.js.es6 +++ b/app/assets/javascripts/discourse/routes/topic.js.es6 @@ -69,16 +69,6 @@ const TopicRoute = Discourse.Route.extend(ShowFooter, { this.controllerFor('invite').reset(); }, - showPrivateInvite() { - showModal('invitePrivate', this.modelFor('topic')); - this.controllerFor('invitePrivate').setProperties({ - email: null, - error: false, - saving: false, - finished: false - }); - }, - showHistory(post) { showModal('history', post); this.controllerFor('history').refresh(post.get("id"), "latest"); diff --git a/app/assets/javascripts/discourse/templates/modal/invite.hbs b/app/assets/javascripts/discourse/templates/modal/invite.hbs index 3b74f7afab0..52a8767298b 100644 --- a/app/assets/javascripts/discourse/templates/modal/invite.hbs +++ b/app/assets/javascripts/discourse/templates/modal/invite.hbs @@ -2,7 +2,7 @@ {{#if error}}
{{username}} {{description}}
" private_message: "{{username}} {{description}}
" invited_to_private_message: "{{username}} {{description}}
" + invited_to_topic: "{{username}} {{description}}
" invitee_accepted: "{{username}} accepted your invitation
" moved_post: "{{username}} moved {{description}}
" linked: "{{username}} {{description}}
" @@ -1072,14 +1073,14 @@ en: invite_reply: title: 'Invite' - action: 'Email Invite' + action: 'Send Invite' help: 'send invitations to friends so they can reply to this topic with a single click' to_topic: "We'll send a brief email allowing your friend to immediately join and reply to this topic by clicking a link, no login required." to_forum: "We'll send a brief email allowing your friend to immediately join by clicking a link, no login required." email_placeholder: 'name@example.com' - success: "We mailed out an invitation to {{email}}. We'll notify you when the invitation is redeemed. Check the invitations tab on your user page to keep track of your invites." - error: "Sorry, we couldn't invite that person. Perhaps they are already a user? (Invites are rate limited)" + success: "We mailed out an invitation to {{emailOrUsername}}. We'll notify you when the invitation is redeemed. Check the invitations tab on your user page to keep track of your invites." + error: "Sorry, we couldn't invite that person. Perhaps they have already been invited? (Invites are rate limited)" login_reply: 'Log In to Reply' diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 45b611185bf..c366ac77d8f 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1158,6 +1158,7 @@ en: moved_post: "%{display_username} moved your post to %{link}" private_message: "%{display_username} sent you a message: %{link}" invited_to_private_message: "%{display_username} invited you to a message: %{link}" + invited_to_topic: "%{display_username} invited you to a topic: %{link}" invitee_accepted: "%{display_username} accepted your invitation" linked: "%{display_username} linked you in %{link}" granted_badge: "You earned %{link}" @@ -1773,6 +1774,13 @@ en: Please visit this link to view the topic: %{base_url}%{url} + user_invited_to_topic: + subject_template: "[%{site_name}] %{username} invited you to a topic '%{topic_title}'" + text_body_template: | + %{username} invited you to a topic '%{topic_title}' on %{site_name}: + + Please visit this link to view the topic: %{base_url}%{url} + user_replied: subject_template: "[%{site_name}] %{topic_title}" text_body_template: | diff --git a/spec/mailers/user_notifications_spec.rb b/spec/mailers/user_notifications_spec.rb index 18921cfb97b..100b5cfb172 100644 --- a/spec/mailers/user_notifications_spec.rb +++ b/spec/mailers/user_notifications_spec.rb @@ -326,4 +326,11 @@ describe UserNotifications do end end + describe "user invited to a topic" do + include_examples "notification email building" do + let(:notification_type) { :invited_to_topic } + include_examples "no reply by email" + end + end + end diff --git a/spec/models/user_email_observer_spec.rb b/spec/models/user_email_observer_spec.rb index 331d6eb636c..20c632e1293 100644 --- a/spec/models/user_email_observer_spec.rb +++ b/spec/models/user_email_observer_spec.rb @@ -98,4 +98,22 @@ describe UserEmailObserver do end + context 'user_invited_to_topic' do + + let(:user) { Fabricate(:user) } + let!(:notification) { Fabricate(:notification, user: user, notification_type: 13) } + + it "enqueues a job for the email" do + Jobs.expects(:enqueue_in).with(SiteSetting.email_time_window_mins.minutes, :user_email, type: :user_invited_to_topic, user_id: notification.user_id, notification_id: notification.id) + UserEmailObserver.send(:new).after_commit(notification) + end + + it "doesn't enqueue an email if the user has mention emails disabled" do + user.expects(:email_direct?).returns(false) + Jobs.expects(:enqueue_in).with(SiteSetting.email_time_window_mins.minutes, :user_email, has_entry(type: :user_invited_to_topic)).never + UserEmailObserver.send(:new).after_commit(notification) + end + + end + end