Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added basic HTML template support to MEGA (#11336)
no issue - Sends formatted email to members - Added css inlining support for MEGA template - Migrated MEGA service to use API serializers - Service needs to be compliant with the API to be able to serve absolute URLs for resources like images - Fixed send email check for previously sent mails
- Loading branch information
1 parent
424e3ae
commit 977298b
Showing
7 changed files
with
305 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = { | ||
mega: require('./mega') | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
const juice = require('juice'); | ||
const common = require('../../lib/common'); | ||
const api = require('../../api'); | ||
const membersService = require('../members'); | ||
const bulkEmailService = require('../bulk-email'); | ||
const models = require('../../models'); | ||
const template = require('./template'); | ||
const settingsCache = require('../../services/settings/cache'); | ||
const urlUtils = require('../../lib/url-utils'); | ||
|
||
const getSite = () => { | ||
return Object.assign({}, settingsCache.getPublic(), { | ||
url: urlUtils.urlFor('home', true) | ||
}); | ||
}; | ||
|
||
const sendEmail = async (post) => { | ||
const emailTmpl = { | ||
subject: post.email_subject || post.title, | ||
html: juice(template({post, site: getSite()})) | ||
}; | ||
|
||
const {members} = await membersService.api.members.list(); | ||
const emails = members.map(m => m.email); | ||
|
||
return bulkEmailService.send(emailTmpl, emails); | ||
}; | ||
|
||
// NOTE: serialization is needed to make sure we are using current API and do post transformations | ||
// such as image URL transformation from relative to absolute | ||
const serialize = async (model) => { | ||
const frame = {options: {previous: true, context: {user: true}}}; | ||
const apiVersion = model.get('api_version') || 'v3'; | ||
const docName = 'posts'; | ||
|
||
await api.shared | ||
.serializers | ||
.handle | ||
.output(model, {docName: docName, method: 'read'}, api[apiVersion].serializers.output, frame); | ||
|
||
return frame.response[docName][0]; | ||
}; | ||
|
||
async function listener(model, options) { | ||
// CASE: do not send email if we import a database | ||
// TODO: refactor post.published events to never fire on importing | ||
if (options && options.importing) { | ||
return; | ||
} | ||
|
||
const post = await serialize(model); | ||
|
||
if (!post.send_email_when_published) { | ||
return; | ||
} | ||
|
||
const deliveredEvents = await models.Action.findAll({ | ||
filter: `event:delivered+resource_id:${model.id}` | ||
}); | ||
|
||
if (deliveredEvents && deliveredEvents.toJSON().length > 0) { | ||
return; | ||
} | ||
|
||
sendEmail(post).then(async () => { | ||
|
||
let actor = {id: null, type: null}; | ||
if (options.context && options.context.user) { | ||
actor = { | ||
id: options.context.user, | ||
type: 'user' | ||
}; | ||
} | ||
const action = { | ||
event: 'delivered', | ||
resource_id: model.id, | ||
resource_type: 'post', | ||
actor_id: actor.id, | ||
actor_type: actor.type | ||
}; | ||
return models.Action.add(action, {context: {internal: true}}); | ||
}); | ||
} | ||
|
||
function listen() { | ||
common.events.on('post.published', listener); | ||
} | ||
|
||
// Public API | ||
module.exports = { | ||
listen: listen | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
module.exports = ({post, site}) => ` | ||
<!doctype html> | ||
<html> | ||
<head> | ||
<meta name="viewport" content="width=device-width"> | ||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | ||
<title>${post.title}!</title> | ||
<style> | ||
/* ------------------------------------- | ||
RESPONSIVE AND MOBILE FRIENDLY STYLES | ||
------------------------------------- */ | ||
@media only screen and (max-width: 620px) { | ||
table[class=body] h1 { | ||
font-size: 28px !important; | ||
margin-bottom: 10px !important; | ||
} | ||
table[class=body] p, | ||
table[class=body] ul, | ||
table[class=body] ol, | ||
table[class=body] td, | ||
table[class=body] span, | ||
table[class=body] a { | ||
font-size: 16px !important; | ||
} | ||
table[class=body] .wrapper, | ||
table[class=body] .article { | ||
padding: 10px !important; | ||
} | ||
table[class=body] .content { | ||
padding: 0 !important; | ||
} | ||
table[class=body] .container { | ||
padding: 0 !important; | ||
width: 100% !important; | ||
} | ||
table[class=body] .main { | ||
border-left-width: 0 !important; | ||
border-radius: 0 !important; | ||
border-right-width: 0 !important; | ||
} | ||
table[class=body] .btn table { | ||
width: 100% !important; | ||
} | ||
table[class=body] .btn a { | ||
width: 100% !important; | ||
} | ||
table[class=body] .img-responsive { | ||
height: auto !important; | ||
max-width: 100% !important; | ||
width: auto !important; | ||
} | ||
} | ||
/* ------------------------------------- | ||
PRESERVE THESE STYLES IN THE HEAD | ||
------------------------------------- */ | ||
@media all { | ||
.ExternalClass { | ||
width: 100%; | ||
} | ||
.ExternalClass, | ||
.ExternalClass p, | ||
.ExternalClass span, | ||
.ExternalClass font, | ||
.ExternalClass td, | ||
.ExternalClass div { | ||
line-height: 100%; | ||
} | ||
.recipient-link a { | ||
color: inherit !important; | ||
font-family: inherit !important; | ||
font-size: inherit !important; | ||
font-weight: inherit !important; | ||
line-height: inherit !important; | ||
text-decoration: none !important; | ||
} | ||
#MessageViewBody a { | ||
color: inherit; | ||
text-decoration: none; | ||
font-size: inherit; | ||
font-family: inherit; | ||
font-weight: inherit; | ||
line-height: inherit; | ||
} | ||
} | ||
hr { | ||
border-width: 0; | ||
height: 0; | ||
margin-top: 34px; | ||
margin-bottom: 34px; | ||
border-bottom-width: 1px; | ||
border-bottom-color: #EEF5F8; | ||
} | ||
</style> | ||
</head> | ||
<body class="" style="background-color: #F4F8FB; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.5em; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;"> | ||
<table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: #F4F8FB;"> | ||
<tr> | ||
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;"> </td> | ||
<td class="container" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; display: block; margin: 0 auto; max-width: 600px; padding: 10px; width: 600px;"> | ||
<div class="content" style="box-sizing: border-box; display: block; margin: 0 auto; max-width: 600px; padding: 30px 20px;"> | ||
<!-- START CENTERED WHITE CONTAINER --> | ||
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Thanks for signing up for ${site.title}!</span> | ||
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #ffffff; border-radius: 8px;"> | ||
<!-- START MAIN CONTENT AREA --> | ||
<tr> | ||
<td class="wrapper" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 40px 50px;"> | ||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;"> | ||
<tr> | ||
${post.html} | ||
</tr> | ||
</table> | ||
</td> | ||
</tr> | ||
<!-- END MAIN CONTENT AREA --> | ||
</table> | ||
<!-- START FOOTER --> | ||
<div class="footer" style="clear: both; Margin-top: 10px; text-align: center; width: 100%;"> | ||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;"> | ||
<tr> | ||
<td class="content-block" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; vertical-align: top; padding-bottom: 5px; padding-top: 15px; font-size: 13px; line-height: 19px; color: #738A94; text-align: center;"> | ||
If you did not make this request, you can simply delete this message. <br/>You will not be signed up, and no account will be created for you. | ||
</td> | ||
</tr> | ||
</table> | ||
</div> | ||
<!-- END FOOTER --> | ||
<!-- END CENTERED WHITE CONTAINER --> | ||
</div> | ||
</td> | ||
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;"> </td> | ||
</tr> | ||
</table> | ||
</body> | ||
</html> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.