Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mail messages #1733

Merged
merged 10 commits into from
Dec 22, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .meteor/packages
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ rocketchat:lib
rocketchat:authorization
rocketchat:autolinker
rocketchat:channel-settings
rocketchat:channel-settings-mail-messages
rocketchat:colors
rocketchat:custom-oauth
rocketchat:emojione
Expand Down
1 change: 1 addition & 0 deletions .meteor/versions
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Meteor.startup ->
RocketChat.ChannelSettings.addOption
id: 'mail-messages'
template: 'channelSettingsMailMessages'
validation: ->
return RocketChat.authz.hasAllPermission('mail-messages')

RocketChat.callbacks.add 'roomExit', (mainNode) ->
instance = Blaze.getView($('.messages-box')?[0])?.templateInstance()
instance?.resetSelection(false)
, RocketChat.callbacks.priority.MEDIUM, 'room-exit-mail-messages'
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.flex-tab {
.mail-message {
form {
margin-top: 20px;

.input-line.double-col {
margin-bottom: 20px;

label {
line-height: 15px;
}

div {
line-height: 15px;
i.octicon {
font-size: 13px;
opacity: 0.4;
vertical-align: top;
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Template.channelSettingsMailMessages.events
'click button.mail-messages': (e, t) ->
Session.set 'channelSettingsMailMessages', Session.get('openedRoom')
RocketChat.TabBar.setTemplate('mailMessagesInstructions')
view = Blaze.getView($('.messages-box')[0])
view?.templateInstance?().resetSelection?(true)

Template.channelSettingsMailMessages.onCreated ->
view = Blaze.getView($('.messages-box')[0])
view?.templateInstance?().resetSelection?(false)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template name="channelSettingsMailMessages">
<li>
<label>{{_ "Mail_Messages"}}</label>
<div>
<button type="button" class="button primary mail-messages">{{_ "Choose_messages"}}</button>
</div>
</li>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
Template.mailMessagesInstructions.helpers
name: ->
return Meteor.user().name
email: ->
return Meteor.user().emails?[0]?.address
roomName: ->
return ChatRoom.findOne(Session.get('openedRoom'))?.name
erroredEmails: ->
return Template.instance()?.erroredEmails.get().join(', ')

Template.mailMessagesInstructions.events
'click .cancel': (e, t) ->
t.reset()

'click .send': (e, t) ->
t.$('.error').hide()
$btn = t.$('button.send')
oldBtnValue = $btn.html()
$btn.html(TAPi18n.__('Sending'))

selectedMessages = $('.messages-box .message.selected')

error = false
if selectedMessages.length is 0
t.$('.error-select').show()
error = true

if t.$('input[name=to]').val().trim()
rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/
emails = t.$('input[name=to]').val().trim().split(',')
erroredEmails = []
for email in emails
unless rfcMailPatternWithName.test email.trim()
erroredEmails.push email.trim()

t.erroredEmails.set erroredEmails
if erroredEmails.length > 0
t.$('.error-invalid-emails').show()
error = true
else
t.$('.error-missing-to').show()
error = true

if error
$btn.html(oldBtnValue)
else
data =
rid: Session.get('openedRoom')
to: t.$('input[name=to]').val().trim()
subject: t.$('input[name=subject]').val().trim()
messages: selectedMessages.map((i, message) -> return message.id).toArray()
language: localStorage.getItem('userLanguage')

Meteor.call 'mailMessages', data, (err, result) ->
$btn.html(oldBtnValue)
if err?
return toastr.error(err.reason or err.message)

toastr.success(TAPi18n.__('Your_email_has_been_queued_for_sending'))
t.reset()

'click .select-all': (e, t) ->
t.$('.error-select').hide()

view = Blaze.getView($('.messages-box')[0])
view?.templateInstance?().selectedMessages = _.pluck(ChatMessage.find({rid: Session.get('openedRoom')})?.fetch(), '_id')
$(".messages-box .message").addClass('selected')

Template.mailMessagesInstructions.onCreated ->
@erroredEmails = new ReactiveVar []

@reset = ->
RocketChat.TabBar.setTemplate('channelSettings')
view = Blaze.getView($('.messages-box')[0])
view?.templateInstance?().resetSelection?(false)

@autorun =>
if Session.get('channelSettingsMailMessages') isnt Session.get('openedRoom')
this.reset()
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<template name="mailMessagesInstructions">
<div class="content">
<div class="list-view mail-message">
<div class="status">
<h2>{{_ "Mail_Messages"}}</h2>
</div>
<p>{{_ "Mail_Messages_Instructions"}}</p>
<form>
<fieldset>
<div class="input-line double-col">
<label>{{_ "From"}}</label>
<div>{{name}}</div>
<div>{{email}}</div>
</div>
<div class="input-line double-col">
<label>{{_ "To"}}</label>
<div>
<input type="text" name="to" value="" />
</div>
</div>
<div class="input-line double-col">
<label>{{_ "Subject"}}</label>
<div>
<input type="text" name="subject" value="{{_ "Mail_Messages_Subject" roomName}}" />
</div>
</div>
</fieldset>
</form>
<div class="error error-missing-to alert alert-danger" style="display: none">
{{_ "Mail_Message_Missing_to"}}
</div>
<div class="error error-invalid-emails alert alert-danger" style="display: none">
{{_ "Mail_Message_Invalid_emails" erroredEmails}}
</div>
<div class="error error-select alert alert-danger" style="display: none">
{{{_ "Mail_Message_No_messages_selected_select_all"}}}
</div>
<p style="margin-top: 30px">
<button type="button" class="button secondary cancel">{{_ "Cancel"}}</button>
<button type="button" class="button primary send">{{_ "Send"}}</button>
</p>
</div>
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"Body" : "Body",
"Cancel" : "Cancel",
"Choose_messages" : "Choose messages",
"From" : "From",
"Mail_Message_Missing_to" : "You must provide one or more To e-mail addresses, separated by commas.",
"Mail_Message_No_messages_selected_select_all" : "You haven't selected any messages. Would you like to <a href='#' class='select-all'>select all</a> visible messages?",
"Mail_Messages" : "Mail Messages",
"Mail_Messages_Instructions" : "Choose which messages you want to send via e-mail by clicking the messages",
"Mail_Messages_Subject" : "Here's a selected portion of %s messages",
"Mail_Message_Invalid_emails" : "You have provided one or more invalid e-mails: %s",
"Send" : "Send",
"Sending" : "Sending...",
"Subject" : "Subject",
"To" : "To",
"Your_email_has_been_queued_for_sending" : "Your email has been queued for sending"
}
51 changes: 51 additions & 0 deletions packages/rocketchat-channel-settings-mail-messages/package.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
Package.describe({
name: 'rocketchat:channel-settings-mail-messages',
version: '0.0.1',
summary: 'Channel Settings - Mail Messages',
git: ''
});

Package.onUse(function(api) {
api.versionsFrom('1.0');

api.use([
'coffeescript',
'templating',
'reactive-var',
'[email protected]',
'rocketchat:[email protected]',
'rocketchat:channel-settings',
'momentjs:moment'
]);

api.addFiles([
'client/lib/startup.coffee',
'client/stylesheets/mail-messages.less',
'client/views/channelSettingsMailMessages.html',
'client/views/channelSettingsMailMessages.coffee',
'client/views/mailMessagesInstructions.html',
'client/views/mailMessagesInstructions.coffee'
], 'client');


api.addFiles([
'server/lib/startup.coffee',
'server/methods/mailMessages.coffee'
], 'server');

// TAPi18n
var _ = Npm.require('underscore');
var fs = Npm.require('fs');
tapi18nFiles = _.compact(_.map(fs.readdirSync('packages/rocketchat-channel-settings-mail-messages/i18n'), function(filename) {
if (fs.statSync('packages/rocketchat-channel-settings-mail-messages/i18n/' + filename).size > 16) {
return 'i18n/' + filename;
}
}));
api.use('tap:[email protected]');
api.imply('tap:i18n');
api.addFiles(tapi18nFiles);
});

Package.onTest(function(api) {

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Meteor.startup ->
permission = { _id: 'mail-messages', roles : [ 'admin' ] }
RocketChat.models.Permissions.upsert( permission._id, { $setOnInsert : permission })
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Meteor.methods
'mailMessages': (data) ->
if not Meteor.userId()
throw new Meteor.Error('invalid-user', "[methods] mailMessages -> Invalid user")

check(data, Match.ObjectIncluding({ rid: String, to: String, subject: String, messages: [ String ], language: String }))

room = Meteor.call 'canAccessRoom', data.rid, Meteor.userId()
unless room
throw new Meteor.Error('invalid-room', "[methods] mailMessages -> Invalid room")

unless RocketChat.authz.hasPermission(Meteor.userId(), 'mail-messages')
throw new Meteor.Error 'not-authorized'

rfcMailPatternWithName = /^(?:.*<)?([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)(?:>?)$/
emails = data.to.trim().split(',')
for email in emails
unless rfcMailPatternWithName.test email.trim()
throw new Meteor.Error('invalid-email', "[methods] mailMessages -> Invalid e-mail")

user = Meteor.user()
name = user.name
email = user.emails?[0]?.address

if data.language isnt 'en'
localeFn = Meteor.call 'loadLocale', data.language
if localeFn
Function(localeFn)()

html = ""
RocketChat.models.Messages.findByRoomIdAndMessageIds(data.rid, data.messages, { sort: { ts: 1 } }).forEach (message) ->
dateTime = moment(message.ts).locale(data.language).format('L LT')
html += "<p style='margin-bottom: 5px'><b>#{message.u.username}</b> <span style='color: #aaa; font-size: 12px'>#{dateTime}</span><br />" + RocketChat.Message.parse(message, data.language) + "</p>"

Meteor.defer ->
Email.send
to: emails
from: RocketChat.settings.get('From_Email')
replyTo: email
subject: data.subject
html: html

console.log 'Sending email to ' + emails.join(', ')


return true
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
RocketChat.ChannelSettings = new class
options = new ReactiveVar {}

###
# Adds an option in Channel Settings
# @config (object)
# id: option id (required)
# template (string): template name to render (required)
# validation (function): if option should be displayed
###
addOption = (config) ->
unless config?.id
throw new Meteor.Error "ChannelSettings-addOption-error", "Option id was not informed."

Tracker.nonreactive ->
opts = options.get()
opts[config.id] = config
options.set opts

getOptions = ->
allOptions = _.toArray options.get()
allowedOptions = _.compact _.map allOptions, (option) ->
if not option.validation? or option.validation()
return option

return _.sortBy allowedOptions, 'order'

addOption: addOption
getOptions: getOptions
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
.flex-tab {
.channel-settings {
margin-top: 60px;
padding: 20px;
ul {
li {
margin-bottom: 20px;
}
}

form {
label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}

div span {
font-size: 14px;
i.octicon {
font-size: 12px;
vertical-align: middle;
margin-left: 3px;
}
}
}

Expand All @@ -19,15 +33,3 @@
}
}
}

.input-line.double-col {
div {
line-height: 15px;
padding: 10px 20px 10px 0;
i.octicon {
font-size: 13px;
opacity: 0.4;
vertical-align: top;
}
}
}
Loading