Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the feedback buttons in the emails #15619

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Buttons in email without icons yet, will add them in https://github.com/TryGhost/Team/issues/2075 (need more time)

</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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary that we do this here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest keeping it :) We added new links to Html and I think it's better to update the plain version


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