mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
Fixed in-browser email preview showing raw replacement strings
no issue - fixed plaintext templates being word wrapped and breaking across replacement strings - updated `postEmailSerializer.serialize` to return the email template plus a replacements array that can be used for creating Mailgun-like recipient variable objects or more straight forward replacement - updated email-preview API to work with the replacements data to show fallback data when previewing
This commit is contained in:
parent
2ea504255c
commit
a09a6caf5f
3 changed files with 69 additions and 47 deletions
|
@ -30,7 +30,17 @@ module.exports = {
|
|||
});
|
||||
}
|
||||
|
||||
return mega.postEmailSerializer.serialize(model, {isBrowserPreview: true});
|
||||
return mega.postEmailSerializer.serialize(model, {isBrowserPreview: true}).then(({emailTmpl, replacements}) => {
|
||||
// perform replacements using no member data
|
||||
replacements.forEach((replacement) => {
|
||||
emailTmpl[replacement.format] = emailTmpl[replacement.format].replace(
|
||||
replacement.match,
|
||||
replacement.fallback || ''
|
||||
);
|
||||
});
|
||||
|
||||
return emailTmpl;
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
|
@ -9,51 +9,16 @@ const postEmailSerializer = require('./post-email-serializer');
|
|||
const config = require('../../config');
|
||||
|
||||
const getEmailData = async (postModel, members = []) => {
|
||||
const emailTmpl = await postEmailSerializer.serialize(postModel);
|
||||
const {emailTmpl, replacements} = await postEmailSerializer.serialize(postModel);
|
||||
|
||||
emailTmpl.from = membersService.config.getEmailFromAddress();
|
||||
|
||||
const EMAIL_REPLACEMENT_REGEX = /%%(\{.*?\})%%/g;
|
||||
// the " is necessary here because `juice` will convert "->" for email compatibility
|
||||
const REPLACEMENT_STRING_REGEX = /\{(?<memberProp>\w*?)(?:,? *(?:"|")(?<fallback>.*?)(?:"|"))?\}/;
|
||||
const ALLOWED_REPLACEMENTS = ['subscriber_firstname'];
|
||||
|
||||
// extract replacements with fallbacks. We have to handle replacements here because
|
||||
// it's the only place we have access to both member data and specified fallbacks
|
||||
const replacements = [];
|
||||
emailTmpl.html = emailTmpl.html.replace(EMAIL_REPLACEMENT_REGEX, (replacementMatch, replacementStr) => {
|
||||
const match = replacementStr.match(REPLACEMENT_STRING_REGEX);
|
||||
|
||||
if (match) {
|
||||
const {memberProp, fallback} = match.groups;
|
||||
|
||||
if (ALLOWED_REPLACEMENTS.includes(memberProp)) {
|
||||
const varName = `replacement_${replacements.length}`;
|
||||
|
||||
replacements.push({
|
||||
varName,
|
||||
memberProp: memberProp.replace('subscriber_', ''),
|
||||
fallback
|
||||
});
|
||||
return `%recipient.${varName}%`;
|
||||
}
|
||||
}
|
||||
|
||||
// output the user-entered replacement string for unknown or invalid replacements
|
||||
// so that it's obvious there's an error in test emails
|
||||
return replacementStr;
|
||||
});
|
||||
|
||||
// plaintext will have the same replacements so no need to add them to the list and
|
||||
// bloat the template variables object but we still need replacements for mailgun template syntax
|
||||
let count = 0;
|
||||
emailTmpl.plaintext = emailTmpl.plaintext.replace(EMAIL_REPLACEMENT_REGEX, (match, replacementStr) => {
|
||||
const {groups: {memberProp}} = replacementStr.match(REPLACEMENT_STRING_REGEX);
|
||||
if (ALLOWED_REPLACEMENTS.includes(memberProp)) {
|
||||
const varName = `replacement_${count}`;
|
||||
count = count + 1;
|
||||
return `%recipient.${varName}`;
|
||||
}
|
||||
return replacementStr;
|
||||
// update templates to use Mailgun variable syntax for replacements
|
||||
replacements.forEach((replacement) => {
|
||||
emailTmpl[replacement.format] = emailTmpl[replacement.format].replace(
|
||||
replacement.match,
|
||||
`%recipient.${replacement.id}%`
|
||||
);
|
||||
});
|
||||
|
||||
const emails = [];
|
||||
|
@ -72,8 +37,8 @@ const getEmailData = async (postModel, members = []) => {
|
|||
};
|
||||
|
||||
// add replacement data/requested fallback to mailgun template variables
|
||||
replacements.forEach(({varName, memberProp, fallback}) => {
|
||||
data[varName] = member[memberProp] || fallback || '';
|
||||
replacements.forEach(({id, memberProp, fallback}) => {
|
||||
data[id] = member[memberProp] || fallback || '';
|
||||
});
|
||||
|
||||
emailData[member.email] = data;
|
||||
|
|
|
@ -54,6 +54,46 @@ const serializePostModel = async (model) => {
|
|||
return frame.response[docName][0];
|
||||
};
|
||||
|
||||
// parses templates and extracts an array of replacements with desired fallbacks
|
||||
// removes %% wrappers from unknown replacement strings (modifies emailTmpl in place)
|
||||
const _parseReplacements = (emailTmpl) => {
|
||||
const EMAIL_REPLACEMENT_REGEX = /%%(\{.*?\})%%/g;
|
||||
// the " is necessary here because `juice` will convert "->" for email compatibility
|
||||
const REPLACEMENT_STRING_REGEX = /\{(?<memberProp>\w*?)(?:,? *(?:"|")(?<fallback>.*?)(?:"|"))?\}/;
|
||||
const ALLOWED_REPLACEMENTS = ['subscriber_firstname'];
|
||||
|
||||
const replacements = [];
|
||||
['html', 'plaintext'].forEach((format) => {
|
||||
emailTmpl[format] = emailTmpl[format].replace(EMAIL_REPLACEMENT_REGEX, (replacementMatch, replacementStr) => {
|
||||
const match = replacementStr.match(REPLACEMENT_STRING_REGEX);
|
||||
|
||||
if (match) {
|
||||
const {memberProp, fallback} = match.groups;
|
||||
|
||||
if (ALLOWED_REPLACEMENTS.includes(memberProp)) {
|
||||
const id = `replacement_${replacements.length + 1}`;
|
||||
|
||||
replacements.push({
|
||||
format,
|
||||
id,
|
||||
match: replacementMatch,
|
||||
memberProp: memberProp.replace('subscriber_', ''),
|
||||
fallback
|
||||
});
|
||||
|
||||
// keeps wrapping %% for later replacement with real data
|
||||
return replacementMatch;
|
||||
}
|
||||
}
|
||||
|
||||
// removes %% so output matches user supplied content
|
||||
return replacementStr;
|
||||
});
|
||||
});
|
||||
|
||||
return replacements;
|
||||
};
|
||||
|
||||
const serialize = async (postModel, options = {isBrowserPreview: false}) => {
|
||||
const post = await serializePostModel(postModel);
|
||||
|
||||
|
@ -67,6 +107,7 @@ const serialize = async (postModel, options = {isBrowserPreview: false}) => {
|
|||
// to avoid replacement strings being split across lines and for mail clients to handle
|
||||
// word wrapping based on user preferences
|
||||
post.plaintext = htmlToText.fromString(post.html, {
|
||||
wordwrap: false,
|
||||
ignoreImage: true,
|
||||
hideLinkHrefIfSameAsText: true,
|
||||
preserveNewlines: true,
|
||||
|
@ -86,11 +127,17 @@ const serialize = async (postModel, options = {isBrowserPreview: false}) => {
|
|||
let _cheerio = cheerio.load(juicedHtml);
|
||||
_cheerio('a').attr('target','_blank');
|
||||
juicedHtml = _cheerio.html();
|
||||
return {
|
||||
|
||||
const emailTmpl = {
|
||||
subject: post.email_subject || post.title,
|
||||
html: juicedHtml,
|
||||
plaintext: post.plaintext
|
||||
};
|
||||
|
||||
// Extract known replacements and clean up unknown replacement strings
|
||||
const replacements = _parseReplacements(emailTmpl);
|
||||
|
||||
return {emailTmpl, replacements};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
|
Loading…
Reference in a new issue