refactoring email templates

This commit is contained in:
Sacha Greif 2014-08-03 11:50:10 +09:00
parent 6cc5bedb4d
commit 78f440e8cd
16 changed files with 193 additions and 134 deletions

View file

@ -6,7 +6,7 @@ Template[getTemplate('notification_item')].helpers({
return this.properties;
},
notificationHTML: function(){
return getNotificationContents(this).html;
return buildSiteNotification(this);
}
});

View file

@ -29,70 +29,26 @@ Notifications.allow({
, remove: canEditById
});
getNotificationContents = function(notification, context){
// the same notifications can be displayed in multiple contexts: on-site in the sidebar, sent by email, etc.
buildSiteNotification = function(notification){
var event = notification.event,
p = notification.properties,
context = typeof context === 'undefined' ? 'sidebar' : context,
userToNotify = Meteor.users.findOne(notification.userId);
userToNotify = Meteor.users.findOne(notification.userId),
html
switch(event){
case 'newReply':
var n = {
subject: 'Someone replied to your comment on "'+p.postTitle+'"',
text: p.commentAuthorName+' has replied to your comment on "'+p.postTitle+'": '+getPostCommentUrl(p.postId, p.commentId),
html: '<p><a href="'+getProfileUrlById(p.commentAuthorId)+'">'+p.commentAuthorName+'</a> has replied to your comment on "<a href="'+getPostCommentUrl(p.postId, p.commentId)+'" class="action-link">'+p.postTitle+'</a>"</p>'
};
if(context == 'email')
n.html += '<p>'+p.commentExcerpt+'</p><a href="'+getPostCommentUrl(p.postId, p.commentId)+'" class="action-link">Read more</a>';
html = '<p><a href="'+getProfileUrlById(p.commentAuthorId)+'">'+p.commentAuthorName+'</a> has replied to your comment on "<a href="'+getPostCommentUrl(p.postId, p.commentId)+'" class="action-link">'+p.postTitle+'</a>"</p>';
break;
case 'newComment':
var n = {
subject: 'A new comment on your post "'+p.postTitle+'"',
text: 'You have a new comment by '+p.commentAuthorName+' on your post "'+p.postTitle+'": '+getPostCommentUrl(p.postId, p.commentId),
html: '<p><a href="'+getProfileUrlById(p.commentAuthorId)+'">'+p.commentAuthorName+'</a> left a new comment on your post "<a href="'+getPostCommentUrl(p.postId, p.commentId)+'" class="action-link">'+p.postTitle+'</a>"</p>'
};
if(context == 'email')
n.html += '<p>'+p.commentExcerpt+'</p><a href="'+getPostCommentUrl(p.postId, p.commentId)+'" class="action-link">Read more</a>';
break;
case 'newPost':
var n = {
subject: p.postAuthorName+' has created a new post: "'+p.postTitle+'"',
text: p.postAuthorName+' has created a new post: "'+p.postTitle+'" '+getPostUrl(p.postId),
html: '<a href="'+getProfileUrlById(p.postAuthorId)+'">'+p.postAuthorName+'</a> has created a new post: "<a href="'+getPostUrl(p.postId)+'" class="action-link">'+p.postTitle+'</a>".'
};
break;
case 'accountApproved':
var n = {
subject: 'Your account has been approved.',
text: 'Welcome to '+getSetting('title')+'! Your account has just been approved.',
html: 'Welcome to '+getSetting('title')+'!<br/> Your account has just been approved. <a href="'+Meteor.absoluteUrl()+'">Start posting.</a>'
};
break;
case 'newUser':
var n = {
subject: 'New user: '+p.username,
text: 'A new user account has been created: '+p.profileUrl,
html: 'A new user account has been created: <a href="'+p.profileUrl+'">'+p.username+'</a>'
};
break;
html = '<p><a href="'+getProfileUrlById(p.commentAuthorId)+'">'+p.commentAuthorName+'</a> left a new comment on your post "<a href="'+getPostCommentUrl(p.postId, p.commentId)+'" class="action-link">'+p.postTitle+'</a>"</p>';
break;
default:
break;
break;
}
// if context is email, append unsubscribe link to all outgoing notifications
if(context == 'email'){
n.to = getEmail(userToNotify);
n.text = n.text + '\n\n Unsubscribe from all notifications: '+getUnsubscribeLink(userToNotify);
n.html = n.html + '<br/><br/><a href="'+getUnsubscribeLink(userToNotify)+'">Unsubscribe from all notifications</a>';
}
return n;
return html;
};
Meteor.methods({

View file

@ -223,19 +223,18 @@ Meteor.methods({
Meteor.call('upvotePost', post, postAuthor);
if(getSetting('emailNotifications', false)){
if(!!getSetting('emailNotifications', false)){
// notify users of new posts
var notification = {
event: 'newPost',
properties: {
postAuthorName : getDisplayName(postAuthor),
postAuthorId : post.userId,
postTitle : title,
postId : post._id
}
emailProperties = {
postAuthorName : getDisplayName(postAuthor),
postAuthorId : post.userId,
postTitle : title,
postId : post._id,
profileUrl: getProfileUrlById(post.userId),
postUrl: getPostUrl(post._id)
};
// call a server method because we do not have access to users' info on the client
Meteor.call('newPostNotify', notification, function(error, result){
Meteor.call('newPostNotify', emailProperties, function(error, result){
//run asynchronously
});
}
@ -290,5 +289,20 @@ Meteor.methods({
Meteor.users.update({_id: post.userId}, {$inc: {postCount: -1}});
Posts.remove(postId);
},
newPostNotify : function(p){
// only run on server since we need to get a list of all users
if(Meteor.isServer){
var currentUser = Meteor.users.findOne(this.userId);
console.log('newPostNotify');
var subject = p.postAuthorName+' has created a new post: '+p.postTitle;
var html = Handlebars.templates[getTemplate('emailNewPost')](p)
// send a notification to every user according to their notifications settings
Meteor.users.find({'profile.notifications.posts': 1}).forEach(function(user) {
// don't send users notifications for their own posts
if(user._id !== currentUser._id && getUserSetting('notifications.posts', false, user))
sendEmail(getEmail(user), subject, html);
});
}
}
});

View file

@ -704,6 +704,35 @@ Router.map(function() {
}
});
// Notification email online
this.route('notification', {
where: 'server',
path: '/email/notification/:id?',
action: function() {
var notification = Notifications.findOne(this.params.id);
var notificationContents = buildEmailNotification(notification);
var html = buildEmailTemplate(notificationContents.html);
this.response.write(html);
this.response.end();
}
});
this.route('newUser', {
where: 'server',
path: '/email/new-user/:id?',
action: function() {
var user = Meteor.users.findOne(this.params.id);
var emailProperties = {
profileUrl: getProfileUrl(user),
username: getUserName(user)
}
html = Handlebars.templates[getTemplate('emailNewUser')](emailProperties);
this.response.write(buildEmailTemplate(html));
this.response.end();
}
});
});
// adding common subscriptions that's need to be loaded on all the routes

View file

@ -1,3 +1,4 @@
{
"juice": "0.4.0"
"juice": "0.4.0",
"html-to-text": "0.1.0"
}

View file

@ -5,7 +5,7 @@ Meteor.startup(function () {
this.route('campaign', {
where: 'server',
path: '/campaign/:id?',
path: '/email/campaign/:id?',
action: function() {
var campaignId = parseInt(this.params.id);
var htmlContent = buildCampaign(2);

View file

@ -1,32 +1,7 @@
sendEmail = function(to, subject, text, html){
// TODO: limit who can send emails
// TODO: fix this error: Error: getaddrinfo ENOTFOUND
var from = getSetting('defaultEmail', 'noreply@example.com');
var siteName = getSetting('title');
var subject = '['+siteName+'] '+subject;
console.log('sending email…');
console.log(from);
console.log(to);
console.log(subject);
console.log(text);
console.log(html);
Email.send({
from: from,
to: to,
subject: subject,
text: text,
html: html
});
};
buildEmailTemplate = function (htmlContent) {
var juice = Meteor.require('juice');
var emailHTML = Handlebars.templates[getTemplate('emailMain')]({
var emailHTML = Handlebars.templates[getTemplate('emailWrapper')]({
headerColor: getSetting('headerColor'),
buttonColor: getSetting('buttonColor'),
logo: '',
@ -46,5 +21,40 @@ buildEmailTemplate = function (htmlContent) {
});
}).result;
return inlinedHTML;
}
var doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
return doctype+inlinedHTML;
}
sendEmail = function(to, subject, html, text){
// TODO: limit who can send emails
// TODO: fix this error: Error: getaddrinfo ENOTFOUND
var from = getSetting('defaultEmail', 'noreply@example.com');
var siteName = getSetting('title');
var subject = '['+siteName+'] '+subject;
if (typeof text == 'undefined'){
// Auto-generate text version if it doesn't exist. Has bugs, but should be good enough.
var htmlToText = Meteor.require('html-to-text');
var text = htmlToText.fromString(html, {
wordwrap: 130
});
}
console.log('sending email…');
console.log(from);
console.log(to);
console.log(subject);
console.log(text);
console.log(html);
Email.send({
from: from,
to: to,
subject: subject,
text: text,
html: html
});
};

View file

@ -24,13 +24,13 @@ Meteor.methods({
}});
console.log(a);
createNotification({
event: 'accountApproved',
properties: {},
userToNotify: invitedUser,
userDoingAction: currentUser,
sendEmail: true
});
var emailProperties = {
siteTitle: getSettings('siteTitle'),
siteUrl: getSiteUrl()
}
sendEmail(getEmail(invitedUser), 'You\'ve been invited',
Handlebars.templates[getTemplate('emailAccountApproved')](emailProperties)
)
}else{
throw new Meteor.Error(701, "You can't invite this user, sorry.");

View file

@ -26,11 +26,54 @@ createNotification = function(options) {
// the notificationsFrequency is set to 1, or if it's undefined (legacy compatibility)
if(sendEmail){
// get specific notification content for "email" context
var contents = getNotificationContents(notification, 'email');
var contents = buildEmailNotification(notification);
sendNotification(contents);
}
};
buildEmailNotification = function (notification) {
var event = notification.event,
p = notification.properties,
userToNotify = Meteor.users.findOne(notification.userId),
n = {}
n.to = getEmail(userToNotify);
p.profileUrl = getProfileUrlById(p.commentAuthorId);
p.postCommentUrl = getPostCommentUrl(p.postId, p.commentId);
p.unsubscribeLink = getUnsubscribeLink(userToNotify);
switch(event){
case 'newReply':
n.subject = 'Someone replied to your comment on "'+p.postTitle+'"';
n.template = 'emailNewReply';
n.text = p.commentAuthorName+' has replied to your comment on "'+p.postTitle+'": '+getPostCommentUrl(p.postId, p.commentId);
break;
case 'newComment':
n.subject = 'A new comment on your post "'+p.postTitle+'"';
n.template = 'emailNewComment';
n.text = 'You have a new comment by '+p.commentAuthorName+' on your post "'+p.postTitle+'": '+getPostCommentUrl(p.postId, p.commentId);
break;
default:
break;
}
n.html = Handlebars.templates[getTemplate(n.template)](p);
// append unsubscribe link to all outgoing notifications
n.text = n.text + '\n\n Unsubscribe from all notifications: '+getUnsubscribeLink(userToNotify);
return n;
}
sendNotification = function (notification) {
// console.log('send notification:')
// console.log(notification)
var html = buildEmailTemplate(notification.html)
sendEmail(notification.to, notification.subject, html, notification.text);
};
Meteor.methods({
unsubscribeUser : function(hash){
// TO-DO: currently, if you have somebody's email you can unsubscribe them
@ -48,24 +91,6 @@ Meteor.methods({
return true;
}
return false;
},
newPostNotify : function(properties){
var currentUser = Meteor.users.findOne(this.userId);
console.log('newPostNotify');
// send a notification to every user according to their notifications settings
Meteor.users.find({'profile.notifications.posts': 1}).forEach(function(user) {
// don't send users notifications for their own posts
if(user._id !== currentUser._id && getUserSetting('notifications.posts', false, user)){
properties.userId = user._id;
var notification = getNotificationContents(properties, 'email');
sendNotification(notification, user);
}
});
}
});
sendNotification = function (notification) {
// console.log('send notification:')
// console.log(notification)
sendEmail(notification.to, notification.subject, notification.text, notification.html);
};

View file

@ -0,0 +1,3 @@
<span class="heading">Welcome to {{siteTitle}}</span><br><br>
Your account has just been approved. <a href="{{siteUrl}}">Start posting!</a><br><br>

View file

@ -0,0 +1,7 @@
<span class="heading"><a href="{{profileUrl}}">{{commentAuthorName}}</a>
left a new comment on your post
"<a href="{{postCommentUrl}}" class="action-link">{{postTitle}}</a>"</span><br/><br/>
{{{commentExcerpt}}} <a href="{{postCommentUrl}}" class="action-link">Read more…</a><br/><br/>
<a href="{{unsubscribeLink}}">Unsubscribe from all notifications</a><br/><br/>

View file

@ -0,0 +1,5 @@
<span class="heading">
<a href="{{profileUrl}}">{{postAuthorName}}</a>
has created a new post:
"<a href="{{postUrl}}" class="action-link">{{postTitle}}}</a>
</span><br><br>

View file

@ -0,0 +1,7 @@
<span class="heading"><a href="{{profileUrl}}">{{commentAuthorName}}</a>
has replied to your comment on
"<a href="{{postCommentUrl}}" class="action-link">{{postTitle}}</a>"</span><br/><br/>
{{{commentExcerpt}}} <a href="{{postCommentUrl}}" class="action-link">Read more…</a><br/><br/>
<a href="{{unsubscribeLink}}">Unsubscribe from all notifications</a><br/><br/>

View file

@ -0,0 +1 @@
A new user account has been created: <a href="{{profileUrl}}">{{username}}</a><br><br>

View file

@ -1,5 +1,3 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
@ -36,7 +34,12 @@
padding-left: 12px !important;
padding-right: 12px !important;
}
}
}
a{
color: {{buttonColor}};
font-weight: bold;
text-decoration: none;
}
.container{
border-radius: 3px;
}
@ -58,19 +61,19 @@
font-family: Helvetica, sans-serif;
color: #555;
}
h3{
color: {{buttonColor}};
.heading{
font-weight: bold;
font-size: 18px;
line-height: 1.5;
margin: 0;
}
.footer-container{
background: #ccc;
background: #ddd;
font-family: Helvetica, sans-serif;
padding: 30px;
color: #777;
border-radius: 0px 0px 3px 3px;
font-size: 13px;
}
</style>
</head>

View file

@ -47,15 +47,13 @@ Accounts.onCreateUser(function(options, user){
var admins = Meteor.users.find({isAdmin: true});
admins.forEach(function(admin){
if(getUserSetting('notifications.users', false, admin)){
var notification = getNotificationContents({
event: 'newUser',
properties: {
username: getUserName(user),
profileUrl: getProfileUrl(user)
},
userId: admin._id
}, 'email');
sendNotification(notification, admin);
var emailProperties = {
profileUrl: getProfileUrl(user),
username: getUserName(user)
}
sendEmail(getEmail(admin), 'New user account: '+getUserName(user),
Handlebars.templates[getTemplate('emailNewUser')](emailProperties)
)
}
});