Skip to content

Commit

Permalink
Added the feedback buttons in the emails (#15619)
Browse files Browse the repository at this point in the history
closes TryGhost/Team#2046
closes TryGhost/Team#2045
- Added feedback buttons markup.
- Added feedback links generation.
  • Loading branch information
lenabaidakova committed Oct 14, 2022
1 parent e074676 commit e831be6
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 16 deletions.
26 changes: 23 additions & 3 deletions ghost/audience-feedback/lib/AudienceFeedbackService.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
class AudienceFeedbackService {
buildLink() {
// todo
return new URL('https://example.com');
/** @type URL */
#baseURL;
/**
* @param {object} deps
* @param {object} deps.config
* @param {URL} deps.config.baseURL
*/
constructor(deps) {
this.#baseURL = deps.config.baseURL;
}
/**
* @param {string} uuid
* @param {string} postId
* @param {0 | 1} score
*/
buildLink(uuid, postId, score) {
const url = new URL(this.#baseURL);
url.searchParams.set('action', 'feedback');
url.searchParams.set('post', postId);
url.searchParams.set('uuid', uuid);
url.searchParams.set('score', `${score}`);

return url;
}
}

Expand Down
7 changes: 6 additions & 1 deletion ghost/core/core/server/services/audience-feedback/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const urlUtils = require('../../../shared/url-utils');
const FeedbackRepository = require('./FeedbackRepository');

class AudienceFeedbackServiceWrapper {
Expand All @@ -20,7 +21,11 @@ class AudienceFeedbackServiceWrapper {
});

// Expose the service
this.service = new AudienceFeedbackService();
this.service = new AudienceFeedbackService({
config: {
baseURL: new URL(urlUtils.urlFor('home', true))
}
});
this.controller = new AudienceFeedbackController({repository: this.repository});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const debug = require('@tryghost/debug')('mega');
const postEmailSerializer = require('../mega/post-email-serializer');
const configService = require('../../../shared/config');
const settingsCache = require('../../../shared/settings-cache');
const labs = require('../../../shared/labs');

const messages = {
error: 'The email service received an error from mailgun and was unable to send.'
Expand Down Expand Up @@ -234,6 +235,11 @@ module.exports = {
unsubscribe_url: postEmailSerializer.createUnsubscribeUrl(recipient.member_uuid, {newsletterUuid})
};

if (labs.isSet('audienceFeedback')) {
// create unique urls for every recipient (for example, for feedback buttons)
emailData = postEmailSerializer.createUserLinks(emailData, recipient.member_uuid);
}

// computed properties on recipients - TODO: better way of handling these
recipient.member_first_name = (recipient.member_name || '').split(' ')[0];

Expand Down
69 changes: 69 additions & 0 deletions ghost/core/core/server/services/mega/feedback-buttons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const {Color} = require('@tryghost/color-utils');
const audienceFeedback = require('../audience-feedback');

const templateStrings = {
like: '%{feedback_button_like}%',
dislike: '%{feedback_button_dislike}%'
};

const generateLinks = (postId, uuid, html) => {
const positiveLink = audienceFeedback.service.buildLink(
uuid,
postId,
1
);
const negativeLink = audienceFeedback.service.buildLink(
uuid,
postId,
0
);

html = html.replace(templateStrings.like, positiveLink.href);
html = html.replace(templateStrings.dislike, negativeLink.href);

return html;
};

const getTemplate = (accentColor) => {
const likeButtonHtml = getButtonHtml(templateStrings.like, 'More like this', accentColor);
const dislikeButtonHtml = getButtonHtml(templateStrings.dislike, 'Less like this', accentColor);

return (`
<tr>
<td dir="ltr" width="100%" style="background-color: #ffffff; text-align: center; padding: 40px 4px; border-bottom: 1px solid #e5eff5" align="center">
<h3 style="text-align: center; margin-bottom: 22px; font-size: 17px; letter-spacing: -0.2px; margin-top: 0 !important;">What did you think of this post?</h3>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="margin: auto; width: auto !important;">
<tr>
${likeButtonHtml}
${dislikeButtonHtml}
</tr>
</table>
</td>
</tr>
`);
};

function getButtonHtml(href, buttonText, accentColor) {
const color = new Color(accentColor);
const bgColor = `${accentColor}10`;
const textColor = color.darken(0.6).hex();

return (`
<td dir="ltr" valign="top" align="center" style="font-family: inherit; font-size: 14px; text-align: center;" nowrap>
<table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="width: auto !important;">
<tr>
<td style="padding: 0 6px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';">
<a href=${href} style="background-color: ${bgColor}; color: ${textColor}; border-radius: 22px; font-family: inherit; padding: 12px 20px; border: none; font-size: 14px; font-weight: bold; line-height: 100%; text-decoration: none; display: block;">
${buttonText}
</a>
</td>
</tr>
</table>
</td>
`);
}

module.exports = {
generateLinks,
getTemplate
};
28 changes: 24 additions & 4 deletions ghost/core/core/server/services/mega/post-email-serializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ const urlService = require('../../services/url');
const linkReplacer = require('@tryghost/link-replacer');
const linkTracking = require('../link-tracking');
const memberAttribution = require('../member-attribution');
const feedbackButtons = require('./feedback-buttons');

const ALLOWED_REPLACEMENTS = ['first_name', 'uuid'];

const PostEmailSerializer = {

// Format a full html document ready for email by inlining CSS, adjusting links,
// and performing any client-specific fixes
formatHtmlForEmail(html) {
Expand Down Expand Up @@ -107,6 +108,23 @@ const PostEmailSerializer = {
return signupUrl.href;
},

/**
* createUserLinks
*
* Generate personalised links for each user
*
* @param {string} memberUuid member uuid
* @param {Object} email
*/
createUserLinks(email, memberUuid) {
const result = {...email};

result.html = feedbackButtons.generateLinks(result.post.id, memberUuid, result.html);
result.plaintext = htmlToPlaintext.email(result.html);

return result;
},

// NOTE: serialization is needed to make sure we do post transformations such as image URL transformation from relative to absolute
async serializePostModel(model) {
// fetch mobiledoc rather than html and plaintext so we can render email-specific contents
Expand Down Expand Up @@ -206,6 +224,7 @@ const PostEmailSerializer = {
titleAlignment: newsletter.get('title_alignment'),
bodyFontCategory: newsletter.get('body_font_category'),
showBadge: newsletter.get('show_badge'),
feedbackEnabled: newsletter.get('feedback_enabled'),
footerContent: newsletter.get('footer_content'),
showHeaderName: newsletter.get('show_header_name'),
accentColor,
Expand Down Expand Up @@ -335,7 +354,7 @@ const PostEmailSerializer = {
plaintext: post.plaintext
};

/**
/**
* If a part of the email is members-only and the post is paid-only, add a paywall:
* - Just before sending the email, we'll hide the paywall or paid content depending on the member segment it is sent to.
* - We already need to do URL-replacement on the HTML here
Expand Down Expand Up @@ -369,7 +388,7 @@ const PostEmailSerializer = {

// Add link click tracking
url = await linkTracking.service.addTrackingToUrl(url, post, '--uuid--');

// We need to convert to a string at this point, because we need invalid string characters in the URL
const str = url.toString().replace(/--uuid--/g, '%%{uuid}%%');
return str;
Expand Down Expand Up @@ -490,7 +509,7 @@ const PostEmailSerializer = {
});

result.html = this.formatHtmlForEmail($.html());
result.plaintext = htmlToPlaintext.email(result.html);
result.plaintext = htmlToPlaintext.email(result.html);
delete result.post;

return result;
Expand All @@ -501,6 +520,7 @@ module.exports = {
serialize: PostEmailSerializer.serialize.bind(PostEmailSerializer),
createUnsubscribeUrl: PostEmailSerializer.createUnsubscribeUrl.bind(PostEmailSerializer),
createPostSignupUrl: PostEmailSerializer.createPostSignupUrl.bind(PostEmailSerializer),
createUserLinks: PostEmailSerializer.createUserLinks.bind(PostEmailSerializer),
renderEmailForSegment: PostEmailSerializer.renderEmailForSegment.bind(PostEmailSerializer),
parseReplacements: PostEmailSerializer.parseReplacements.bind(PostEmailSerializer),
// Export for tests
Expand Down
3 changes: 3 additions & 0 deletions ghost/core/core/server/services/mega/template.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const {escapeHtml: escape} = require('@tryghost/string');
const feedbackButtons = require('./feedback-buttons');

/* eslint indent: warn, no-irregular-whitespace: warn */
const iff = (cond, yes, no) => (cond ? yes : no);
Expand Down Expand Up @@ -1265,6 +1266,8 @@ ${ templateSettings.showBadge ? `
<!-- END MAIN CONTENT AREA -->
${iff(templateSettings.feedbackEnabled, feedbackButtons.getTemplate(templateSettings.accentColor), '')}
<tr>
<td class="wrapper" align="center">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="padding-top: 40px; padding-bottom: 30px;">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,8 @@ table.body figcaption a {
<!-- END MAIN CONTENT AREA -->
<tr>
<td class=\\"wrapper\\" align=\\"center\\" style=\\"font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, Helvetica, Arial, sans-serif, &#39;Apple Color Emoji&#39;, &#39;Segoe UI Emoji&#39;, &#39;Segoe UI Symbol&#39;; font-size: 18px; vertical-align: top; color: #15212A; box-sizing: border-box; padding: 0 20px;\\" valign=\\"top\\">
<table role=\\"presentation\\" border=\\"0\\" cellpadding=\\"0\\" cellspacing=\\"0\\" width=\\"100%\\" style=\\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; padding-top: 40px; padding-bottom: 30px;\\">
Expand Down Expand Up @@ -468,7 +470,7 @@ exports[`Email Preview API Read can read post email preview with email card and
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "18188",
"content-length": "18216",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Version, Origin, Accept-Encoding",
Expand Down Expand Up @@ -806,6 +808,8 @@ table.body figcaption a {
<!-- END MAIN CONTENT AREA -->
<tr>
<td class=\\"wrapper\\" align=\\"center\\" style=\\"font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, Helvetica, Arial, sans-serif, &#39;Apple Color Emoji&#39;, &#39;Segoe UI Emoji&#39;, &#39;Segoe UI Symbol&#39;; font-size: 18px; vertical-align: top; color: #15212A; box-sizing: border-box; padding: 0 20px;\\" valign=\\"top\\">
<table role=\\"presentation\\" border=\\"0\\" cellpadding=\\"0\\" cellspacing=\\"0\\" width=\\"100%\\" style=\\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; padding-top: 40px; padding-bottom: 30px;\\">
Expand Down Expand Up @@ -870,7 +874,7 @@ exports[`Email Preview API Read can read post email preview with fields 2: [head
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "23013",
"content-length": "23041",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Version, Origin, Accept-Encoding",
Expand Down Expand Up @@ -1234,6 +1238,8 @@ table.body figcaption a {
<!-- END MAIN CONTENT AREA -->
<tr>
<td class=\\"wrapper\\" align=\\"center\\" style=\\"font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, Helvetica, Arial, sans-serif, &#39;Apple Color Emoji&#39;, &#39;Segoe UI Emoji&#39;, &#39;Segoe UI Symbol&#39;; font-size: 18px; vertical-align: top; color: #15212A; box-sizing: border-box; padding: 0 20px;\\" valign=\\"top\\">
<table role=\\"presentation\\" border=\\"0\\" cellpadding=\\"0\\" cellspacing=\\"0\\" width=\\"100%\\" style=\\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; padding-top: 40px; padding-bottom: 30px;\\">
Expand Down Expand Up @@ -1280,7 +1286,7 @@ exports[`Email Preview API Read has custom content transformations for email com
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "17950",
"content-length": "17978",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Version, Origin, Accept-Encoding",
Expand Down Expand Up @@ -1618,6 +1624,8 @@ table.body figcaption a {
<!-- END MAIN CONTENT AREA -->
<tr>
<td class=\\"wrapper\\" align=\\"center\\" style=\\"font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, Helvetica, Arial, sans-serif, &#39;Apple Color Emoji&#39;, &#39;Segoe UI Emoji&#39;, &#39;Segoe UI Symbol&#39;; font-size: 18px; vertical-align: top; color: #15212A; box-sizing: border-box; padding: 0 20px;\\" valign=\\"top\\">
<table role=\\"presentation\\" border=\\"0\\" cellpadding=\\"0\\" cellspacing=\\"0\\" width=\\"100%\\" style=\\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; padding-top: 40px; padding-bottom: 30px;\\">
Expand Down Expand Up @@ -1664,7 +1672,7 @@ exports[`Email Preview API Read uses the newsletter provided through ?newsletter
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "18316",
"content-length": "18344",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Version, Origin, Accept-Encoding",
Expand Down Expand Up @@ -2388,6 +2396,8 @@ table.body figcaption a {
<!-- END MAIN CONTENT AREA -->
<tr>
<td class=\\"wrapper\\" align=\\"center\\" style=\\"font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, Helvetica, Arial, sans-serif, &#39;Apple Color Emoji&#39;, &#39;Segoe UI Emoji&#39;, &#39;Segoe UI Symbol&#39;; font-size: 18px; vertical-align: top; color: #15212A; box-sizing: border-box; padding: 0 20px;\\" valign=\\"top\\">
<table role=\\"presentation\\" border=\\"0\\" cellpadding=\\"0\\" cellspacing=\\"0\\" width=\\"100%\\" style=\\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; padding-top: 40px; padding-bottom: 30px;\\">
Expand Down Expand Up @@ -2434,7 +2444,7 @@ exports[`Email Preview API Read uses the posts newsletter by default 2: [headers
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "18316",
"content-length": "18344",
"content-type": "application/json; charset=utf-8",
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
"vary": "Accept-Version, Origin, Accept-Encoding",
Expand Down
Loading

0 comments on commit e831be6

Please sign in to comment.