diff --git a/ghost/audience-feedback/lib/AudienceFeedbackService.js b/ghost/audience-feedback/lib/AudienceFeedbackService.js
index 3836a31d45..fbd1da0f74 100644
--- a/ghost/audience-feedback/lib/AudienceFeedbackService.js
+++ b/ghost/audience-feedback/lib/AudienceFeedbackService.js
@@ -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;
}
}
diff --git a/ghost/core/core/server/services/audience-feedback/index.js b/ghost/core/core/server/services/audience-feedback/index.js
index a111db6e5a..0eda9c0c75 100644
--- a/ghost/core/core/server/services/audience-feedback/index.js
+++ b/ghost/core/core/server/services/audience-feedback/index.js
@@ -1,3 +1,4 @@
+const urlUtils = require('../../../shared/url-utils');
const FeedbackRepository = require('./FeedbackRepository');
class AudienceFeedbackServiceWrapper {
@@ -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});
}
}
diff --git a/ghost/core/core/server/services/bulk-email/bulk-email-processor.js b/ghost/core/core/server/services/bulk-email/bulk-email-processor.js
index 88514e5a71..70e312f9cd 100644
--- a/ghost/core/core/server/services/bulk-email/bulk-email-processor.js
+++ b/ghost/core/core/server/services/bulk-email/bulk-email-processor.js
@@ -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.'
@@ -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];
diff --git a/ghost/core/core/server/services/mega/feedback-buttons.js b/ghost/core/core/server/services/mega/feedback-buttons.js
new file mode 100644
index 0000000000..763cc38631
--- /dev/null
+++ b/ghost/core/core/server/services/mega/feedback-buttons.js
@@ -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 (`
+
diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap
index c8989d40bb..677ab84a1b 100644
--- a/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap
+++ b/ghost/core/test/e2e-api/admin/__snapshots__/email-previews.test.js.snap
@@ -416,6 +416,8 @@ table.body figcaption a {
+
+
@@ -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",
@@ -806,6 +808,8 @@ table.body figcaption a {
+
+
@@ -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",
@@ -1234,6 +1238,8 @@ table.body figcaption a {
+
+
@@ -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",
@@ -1618,6 +1624,8 @@ table.body figcaption a {
+
+
@@ -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",
@@ -2388,6 +2396,8 @@ table.body figcaption a {
+
+
@@ -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",
diff --git a/ghost/core/test/unit/server/services/mega/post-email-serializer.test.js b/ghost/core/test/unit/server/services/mega/post-email-serializer.test.js
index 24816b9d6a..9251935c6d 100644
--- a/ghost/core/test/unit/server/services/mega/post-email-serializer.test.js
+++ b/ghost/core/test/unit/server/services/mega/post-email-serializer.test.js
@@ -7,7 +7,7 @@ const urlService = require('../../../../../core/server/services/url');
const labs = require('../../../../../core/shared/labs');
const {parseReplacements, renderEmailForSegment, serialize, _getTemplateSettings, createUnsubscribeUrl, createPostSignupUrl, _PostEmailSerializer} = require('../../../../../core/server/services/mega/post-email-serializer');
const {HtmlValidate} = require('html-validate');
-
+
function assertKeys(object, keys) {
assert.deepStrictEqual(Object.keys(object).sort(), keys.sort());
}
@@ -16,7 +16,7 @@ describe('Post Email Serializer', function () {
afterEach(function () {
sinon.restore();
});
-
+
it('creates replacement pattern for valid format and value', function () {
const html = 'Hey %%{first_name}%%, what is up?';
const plaintext = 'Hey %%{first_name}%%, what is up?';
@@ -137,7 +137,7 @@ describe('Post Email Serializer', function () {
// Improve debugging and show a snippet of the invalid HTML instead of just the line number or a huge HTML-dump
const parsedErrors = [];
-
+
if (!report.valid) {
const lines = output.html.split('\n');
const messages = report.results[0].messages;
@@ -344,6 +344,67 @@ describe('Post Email Serializer', function () {
assert(!output.html.includes(''));
assert(!output.html.includes(''));
});
+
+ it('should hide/show feedback buttons depending on feedback_enabled flag', async function () {
+ sinon.stub(_PostEmailSerializer, 'serializePostModel').callsFake(async () => {
+ return {
+ url: 'https://testpost.com/',
+ title: 'This is a test',
+ excerpt: 'This is a test',
+ authors: 'This is a test',
+ feature_image_alt: 'This is a test',
+ feature_image_caption: 'This is a test',
+
+ // eslint-disable-next-line
+ mobiledoc: JSON.stringify({"version":"0.3.1","atoms":[],"cards":[],"markups":[],"sections":[[1,"p",[[0,[],0,"Free content only"]]]],"ghostVersion":"4.0"})
+ };
+ });
+ const customSettings = {
+ accent_color: '#000099',
+ timezone: 'UTC'
+ };
+
+ const settingsMock = sinon.stub(settingsCache, 'get');
+ settingsMock.callsFake(function (key, options) {
+ if (customSettings[key]) {
+ return customSettings[key];
+ }
+
+ return settingsMock.wrappedMethod.call(settingsCache, key, options);
+ });
+ const template = {
+ name: 'My newsletter',
+ header_image: '',
+ show_header_icon: true,
+ show_header_title: true,
+ show_feature_image: true,
+ title_font_category: 'sans-serif',
+ title_alignment: 'center',
+ body_font_category: 'serif',
+ show_badge: true,
+ show_header_name: true,
+ feedback_enabled: false,
+ footer_content: 'footer'
+ };
+ const newsletterMock = {
+ get: function (key) {
+ return template[key];
+ },
+ toJSON: function () {
+ return template;
+ }
+ };
+
+ const output = await serialize({}, newsletterMock, {isBrowserPreview: false});
+ assert(!output.html.includes('%{feedback_button_like}%'));
+ assert(!output.html.includes('%{feedback_button_dislike}%'));
+
+ template.feedback_enabled = true;
+
+ const outputWithButtons = await serialize({}, newsletterMock, {isBrowserPreview: false});
+ assert(outputWithButtons.html.includes('%{feedback_button_like}%'));
+ assert(outputWithButtons.html.includes('%{feedback_button_dislike}%'));
+ });
});
describe('renderEmailForSegment', function () {
@@ -708,6 +769,7 @@ describe('Post Email Serializer', function () {
title_alignment: 'center',
body_font_category: 'serif',
show_badge: true,
+ feedback_enabled: false,
footer_content: 'footer',
show_header_name: true
}[key];
@@ -723,6 +785,7 @@ describe('Post Email Serializer', function () {
titleAlignment: 'center',
bodyFontCategory: 'serif',
showBadge: true,
+ feedbackEnabled: false,
footerContent: 'footer',
accentColor: '#000099',
adjustedAccentColor: '#000099',
| | | | | |