Skip to content

Commit

Permalink
Added the feedback buttons in the emails
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 bd0f4b4 commit c328a4c
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 15 deletions.
18 changes: 15 additions & 3 deletions ghost/audience-feedback/lib/AudienceFeedbackService.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
class AudienceFeedbackService {
buildLink() {
// todo
return new URL('https://example.com');
/**
* @param {string} siteUrl
* @param {string} uuid
* @param {string} postId
* @param {0 | 1} score
*/
buildLink(siteUrl, uuid, postId, score) {
const params = new URLSearchParams({
action: 'feedback',
post: postId,
uuid,
score
});

return new URL(`${siteUrl}?${params.toString()}`);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,9 @@ module.exports = {
unsubscribe_url: postEmailSerializer.createUnsubscribeUrl(recipient.member_uuid, {newsletterUuid})
};

// 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
73 changes: 73 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,73 @@
const {Color} = require('@tryghost/color-utils');
const urlUtils = require('../../../shared/url-utils');

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

const generateLinks = (postId, uuid, html) => {
const siteUrl = urlUtils.getSiteUrl();
const audienceFeedback = require('../audience-feedback');
const positiveLink = audienceFeedback.service.buildLink(
siteUrl,
uuid,
postId,
1
);
const negativeLink = audienceFeedback.service.buildLink(
siteUrl,
uuid,
postId,
0
);

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

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;">What did you think of this post?</h3>
<table class="inline" role="presentation" border="0" cellpadding="0" cellspacing="0" style="margin: auto;">
<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 class="inline" align="center" role="presentation" cellspacing="0" cellpadding="0" border="0">
<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
7 changes: 7 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 @@ -70,6 +71,10 @@ table {
width: 100%;
}
table.inline {
width: auto !important;
}
table td {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
font-size: 18px;
Expand Down Expand Up @@ -1265,6 +1270,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 c328a4c

Please sign in to comment.