Merge pull request #4252 from techAPJ/invite-email-improvements
FEATURE: customize invite email message
This commit is contained in:
commit
5c3e36aec2
|
@ -6,6 +6,8 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
// If this isn't defined, it will proxy to the user model on the preferences
|
||||
// page which is wrong.
|
||||
emailOrUsername: null,
|
||||
hasCustomMessage: false,
|
||||
customMessage: null,
|
||||
inviteIcon: "envelope",
|
||||
|
||||
isAdmin: function(){
|
||||
|
@ -27,6 +29,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
}.property('isAdmin', 'emailOrUsername', 'invitingToTopic', 'isPrivateTopic', 'model.groupNames', 'model.saving'),
|
||||
|
||||
disabledCopyLink: function() {
|
||||
if (this.get('hasCustomMessage')) return true;
|
||||
if (this.get('model.saving')) return true;
|
||||
if (Ember.isEmpty(this.get('emailOrUsername'))) return true;
|
||||
const emailOrUsername = this.get('emailOrUsername').trim();
|
||||
|
@ -37,7 +40,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
// when inviting to private topic via email, group name must be specified
|
||||
if (this.get('isPrivateTopic') && Ember.isEmpty(this.get('model.groupNames')) && Discourse.Utilities.emailValid(emailOrUsername)) return true;
|
||||
return false;
|
||||
}.property('emailOrUsername', 'model.saving', 'isPrivateTopic', 'model.groupNames'),
|
||||
}.property('emailOrUsername', 'model.saving', 'isPrivateTopic', 'model.groupNames', 'hasCustomMessage'),
|
||||
|
||||
buttonTitle: function() {
|
||||
return this.get('model.saving') ? 'topic.inviting' : 'topic.invite_reply.action';
|
||||
|
@ -71,6 +74,11 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
return this.get('isAdmin') && (Discourse.Utilities.emailValid(this.get('emailOrUsername')) || this.get('isPrivateTopic') || !this.get('invitingToTopic')) && !Discourse.SiteSettings.enable_sso && Discourse.SiteSettings.enable_local_logins && !this.get('isMessage');
|
||||
}.property('isAdmin', 'emailOrUsername', 'isPrivateTopic', 'isMessage', 'invitingToTopic'),
|
||||
|
||||
// Show Custom Message textarea? (only shown when inviting new user to forum)
|
||||
showCustomMessage: function() {
|
||||
return this.get('model') === this.currentUser;
|
||||
}.property('model'),
|
||||
|
||||
// Instructional text for the modal.
|
||||
inviteInstructions: function() {
|
||||
if (Discourse.SiteSettings.enable_sso || !Discourse.SiteSettings.enable_local_logins) {
|
||||
|
@ -136,9 +144,15 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
'topic.invite_private.email_or_username_placeholder';
|
||||
}.property(),
|
||||
|
||||
customMessagePlaceholder: function() {
|
||||
return I18n.t('invite.custom_message_placeholder');
|
||||
}.property(),
|
||||
|
||||
// Reset the modal to allow a new user to be invited.
|
||||
reset() {
|
||||
this.set('emailOrUsername', null);
|
||||
this.set('hasCustomMessage', false);
|
||||
this.set('customMessage', null);
|
||||
this.get('model').setProperties({
|
||||
groupNames: null,
|
||||
error: false,
|
||||
|
@ -147,7 +161,6 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
inviteLink: null
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
||||
createInvite() {
|
||||
|
@ -162,7 +175,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
|
||||
model.setProperties({ saving: true, error: false });
|
||||
|
||||
return this.get('model').createInvite(this.get('emailOrUsername').trim(), groupNames).then(result => {
|
||||
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')) {
|
||||
Invite.findInvitedBy(this.currentUser, userInvitedController.get('filter')).then(invite_model => {
|
||||
|
@ -213,6 +226,15 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
}
|
||||
model.setProperties({ saving: false, error: true });
|
||||
});
|
||||
},
|
||||
|
||||
showCustomMessageBox() {
|
||||
this.toggleProperty('hasCustomMessage');
|
||||
if (this.get('hasCustomMessage')) {
|
||||
this.set('customMessage', I18n.t('invite.custom_message_template'));
|
||||
} else {
|
||||
this.set('customMessage', null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -321,10 +321,10 @@ const User = RestModel.extend({
|
|||
Discourse.SiteSettings['newuser_max_' + type + 's'] > 0;
|
||||
},
|
||||
|
||||
createInvite(email, group_names) {
|
||||
createInvite(email, group_names, custom_message) {
|
||||
return Discourse.ajax('/invites', {
|
||||
type: 'POST',
|
||||
data: { email, group_names }
|
||||
data: { email, group_names, custom_message }
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -22,6 +22,12 @@
|
|||
<label>{{{groupInstructions}}}</label>
|
||||
{{group-selector groupFinder=groupFinder groupNames=model.groupNames placeholderKey="topic.invite_private.group_name"}}
|
||||
{{/if}}
|
||||
|
||||
{{#if showCustomMessage}}
|
||||
<br><label><a {{action "showCustomMessageBox"}}>{{i18n 'invite.custom_message'}}</a></label>
|
||||
{{#if hasCustomMessage}}{{textarea value=customMessage placeholder=customMessagePlaceholder}}{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
|
|
@ -43,7 +43,7 @@ class InvitesController < ApplicationController
|
|||
end
|
||||
|
||||
begin
|
||||
if Invite.invite_by_email(params[:email], current_user, _topic=nil, group_ids)
|
||||
if Invite.invite_by_email(params[:email], current_user, _topic=nil, group_ids, params[:custom_message])
|
||||
render json: success_json
|
||||
else
|
||||
render json: failed_json, status: 422
|
||||
|
|
|
@ -9,7 +9,7 @@ module Jobs
|
|||
raise Discourse::InvalidParameters.new(:invite_id) unless args[:invite_id].present?
|
||||
|
||||
invite = Invite.find_by(id: args[:invite_id])
|
||||
message = InviteMailer.send_invite(invite)
|
||||
message = InviteMailer.send_invite(invite, args[:custom_message])
|
||||
Email::Sender.new(message, :invite).send
|
||||
end
|
||||
|
||||
|
|
|
@ -3,7 +3,11 @@ require_dependency 'email/message_builder'
|
|||
class InviteMailer < ActionMailer::Base
|
||||
include Email::BuildEmailHelper
|
||||
|
||||
def send_invite(invite)
|
||||
class UserNotificationRenderer < ActionView::Base
|
||||
include UserNotificationsHelper
|
||||
end
|
||||
|
||||
def send_invite(invite, custom_message=nil)
|
||||
# Find the first topic they were invited to
|
||||
first_topic = invite.topics.order(:created_at).first
|
||||
|
||||
|
@ -31,15 +35,29 @@ class InviteMailer < ActionMailer::Base
|
|||
site_description: SiteSetting.site_description,
|
||||
site_title: SiteSetting.title)
|
||||
else
|
||||
html = nil
|
||||
if custom_message.present? && custom_message =~ /{invite_link}/
|
||||
custom_message.gsub!("{invite_link}", "#{Discourse.base_url}/invites/#{invite.invite_key}")
|
||||
custom_message.gsub!("{site_title}", SiteSetting.title) if custom_message =~ /{site_title}/
|
||||
custom_message.gsub!("{site_description}", SiteSetting.site_description) if custom_message =~ /{site_description}/
|
||||
|
||||
html = UserNotificationRenderer.new(Rails.configuration.paths["app/views"]).render(
|
||||
template: 'email/invite',
|
||||
format: :html,
|
||||
locals: { message: PrettyText.cook(custom_message).html_safe,
|
||||
classes: 'custom-invite-email' }
|
||||
)
|
||||
end
|
||||
|
||||
build_email(invite.email,
|
||||
template: 'invite_forum_mailer',
|
||||
html_override: html,
|
||||
invitee_name: invitee_name,
|
||||
site_domain_name: Discourse.current_hostname,
|
||||
invite_link: "#{Discourse.base_url}/invites/#{invite.invite_key}",
|
||||
site_description: SiteSetting.site_description,
|
||||
site_title: SiteSetting.title)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def send_password_instructions(user)
|
||||
|
|
|
@ -72,8 +72,8 @@ class Invite < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def self.invite_by_email(email, invited_by, topic=nil, group_ids=nil)
|
||||
create_invite_by_email(email, invited_by, topic, group_ids, true)
|
||||
def self.invite_by_email(email, invited_by, topic=nil, group_ids=nil, custom_message=nil)
|
||||
create_invite_by_email(email, invited_by, topic, group_ids, true, custom_message)
|
||||
end
|
||||
|
||||
# generate invite link
|
||||
|
@ -85,7 +85,7 @@ class Invite < ActiveRecord::Base
|
|||
# Create an invite for a user, supplying an optional topic
|
||||
#
|
||||
# Return the previously existing invite if already exists. Returns nil if the invite can't be created.
|
||||
def self.create_invite_by_email(email, invited_by, topic=nil, group_ids=nil, send_email=true)
|
||||
def self.create_invite_by_email(email, invited_by, topic=nil, group_ids=nil, send_email=true, custom_message=nil)
|
||||
lower_email = Email.downcase(email)
|
||||
user = User.find_by(email: lower_email)
|
||||
|
||||
|
@ -126,7 +126,7 @@ class Invite < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
Jobs.enqueue(:invite_email, invite_id: invite.id) if send_email
|
||||
Jobs.enqueue(:invite_email, invite_id: invite.id, custom_message: custom_message) if send_email
|
||||
|
||||
invite.reload
|
||||
invite
|
||||
|
|
|
@ -3039,3 +3039,21 @@ en:
|
|||
top: "There are no more top topics."
|
||||
bookmarks: "There are no more bookmarked topics."
|
||||
search: "There are no more search results."
|
||||
|
||||
invite:
|
||||
custom_message: "Make your invite a little bit more personal by writing a custom message (optional)."
|
||||
custom_message_placeholder: "Enter your custom message, use {invite_link} for specifying invite link."
|
||||
custom_message_template: |
|
||||
Hello,
|
||||
|
||||
You've been invited you to join
|
||||
|
||||
> **{site_title}**
|
||||
>
|
||||
> {site_description}
|
||||
|
||||
If you're interested, click the link below:
|
||||
|
||||
{invite_link}
|
||||
|
||||
This invitation is from a trusted user, so you won't need to log in.
|
||||
|
|
|
@ -16,7 +16,7 @@ describe Jobs::InviteEmail do
|
|||
|
||||
it 'delegates to the test mailer' do
|
||||
Email::Sender.any_instance.expects(:send)
|
||||
InviteMailer.expects(:send_invite).with(invite).returns(mailer)
|
||||
InviteMailer.expects(:send_invite).with(invite, nil).returns(mailer)
|
||||
Jobs::InviteEmail.new.execute(invite_id: invite.id)
|
||||
end
|
||||
|
||||
|
@ -26,4 +26,3 @@ describe Jobs::InviteEmail do
|
|||
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -6,30 +6,88 @@ describe InviteMailer do
|
|||
|
||||
context "invite to site" do
|
||||
let(:invite) { Fabricate(:invite) }
|
||||
let(:invite_mail) { InviteMailer.send_invite(invite) }
|
||||
|
||||
it 'renders the invitee email' do
|
||||
expect(invite_mail.to).to eql([invite.email])
|
||||
context "default invite message" do
|
||||
let(:invite_mail) { InviteMailer.send_invite(invite) }
|
||||
|
||||
it 'renders the invitee email' do
|
||||
expect(invite_mail.to).to eql([invite.email])
|
||||
end
|
||||
|
||||
it 'renders the subject' do
|
||||
expect(invite_mail.subject).to be_present
|
||||
end
|
||||
|
||||
it 'renders site domain name in subject' do
|
||||
expect(invite_mail.subject).to match(Discourse.current_hostname)
|
||||
end
|
||||
|
||||
it 'renders the body' do
|
||||
expect(invite_mail.body).to be_present
|
||||
end
|
||||
|
||||
it 'renders the inviter email' do
|
||||
expect(invite_mail.from).to eql([SiteSetting.notification_email])
|
||||
end
|
||||
|
||||
it 'renders invite link' do
|
||||
expect(invite_mail.body.encoded).to match("#{Discourse.base_url}/invites/#{invite.invite_key}")
|
||||
end
|
||||
end
|
||||
|
||||
it 'renders the subject' do
|
||||
expect(invite_mail.subject).to be_present
|
||||
end
|
||||
context "custom invite message" do
|
||||
|
||||
it 'renders site domain name in subject' do
|
||||
expect(invite_mail.subject).to match(Discourse.current_hostname)
|
||||
end
|
||||
context "custom message includes invite link" do
|
||||
let(:custom_invite_mail) { InviteMailer.send_invite(invite, "Hello,\n\nYou've been invited you to join\n\n<a href=\"javascript:alert('HACK!')\">Click me.</a>\n\n> **{site_title}**\n>\n> {site_description}\n\nIf you're interested, click the link below:\n\n{invite_link}\n\nThis invitation is from a trusted user, so you won't need to log in.") }
|
||||
|
||||
it 'renders the body' do
|
||||
expect(invite_mail.body).to be_present
|
||||
end
|
||||
it 'renders the invitee email' do
|
||||
expect(custom_invite_mail.to).to eql([invite.email])
|
||||
end
|
||||
|
||||
it 'renders the inviter email' do
|
||||
expect(invite_mail.from).to eql([SiteSetting.notification_email])
|
||||
end
|
||||
it 'renders the subject' do
|
||||
expect(custom_invite_mail.subject).to be_present
|
||||
end
|
||||
|
||||
it 'renders invite link' do
|
||||
expect(invite_mail.body.encoded).to match("#{Discourse.base_url}/invites/#{invite.invite_key}")
|
||||
it 'renders site domain name in subject' do
|
||||
expect(custom_invite_mail.subject).to match(Discourse.current_hostname)
|
||||
end
|
||||
|
||||
it 'renders the html' do
|
||||
expect(custom_invite_mail.html_part).to be_present
|
||||
end
|
||||
|
||||
it 'renders custom_message' do
|
||||
expect(custom_invite_mail.html_part.to_s).to match("You've been invited you to join")
|
||||
end
|
||||
|
||||
it 'renders the inviter email' do
|
||||
expect(custom_invite_mail.from).to eql([SiteSetting.notification_email])
|
||||
end
|
||||
|
||||
it 'sanitizes HTML' do
|
||||
expect(custom_invite_mail.html_part.to_s).to_not match("HACK!")
|
||||
end
|
||||
|
||||
it 'renders invite link' do
|
||||
expect(custom_invite_mail.html_part.to_s).to match("#{Discourse.base_url}/invites/#{invite.invite_key}")
|
||||
end
|
||||
end
|
||||
|
||||
context "custom message does not include invite link" do
|
||||
let(:custom_invite_without_link) { InviteMailer.send_invite(invite, "Hello,\n\nYou've been invited you to join\n\n> **{site_title}**\n>\n> {site_description}") }
|
||||
|
||||
it 'renders default body' do
|
||||
expect(custom_invite_without_link.body).to be_present
|
||||
end
|
||||
|
||||
it 'does not render html' do
|
||||
expect(custom_invite_without_link.html_part).to eq(nil)
|
||||
end
|
||||
|
||||
it 'renders invite link' do
|
||||
expect(custom_invite_without_link.body.encoded).to match("#{Discourse.base_url}/invites/#{invite.invite_key}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue