mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
🐛 Fixed members-only content incorrectly showing in plaintext email (#17137)
fixes https://github.com/TryGhost/Ghost/issues/16131 Members only content was incorrectly being shown in a plaintext email due to the email `preheader` using the post model `plaintext` field directly (which contained the members-only content). This changes this behaviour so that the post html content is utilised for the `preheader` but has all members-only content (post-preview content + segmented content) removed
This commit is contained in:
parent
08e1bcd50c
commit
576fba0568
2 changed files with 101 additions and 19 deletions
|
@ -8,6 +8,7 @@ const {textColorForBackgroundColor, darkenToContrastThreshold} = require('@trygh
|
|||
const {DateTime} = require('luxon');
|
||||
const htmlToPlaintext = require('@tryghost/html-to-plaintext');
|
||||
const tpl = require('@tryghost/tpl');
|
||||
const cheerio = require('cheerio');
|
||||
|
||||
const messages = {
|
||||
subscriptionStatus: {
|
||||
|
@ -218,7 +219,6 @@ class EmailRenderer {
|
|||
return allowedSegments;
|
||||
}
|
||||
|
||||
const cheerio = require('cheerio');
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
let allSegments = $('[data-gh-segment]')
|
||||
|
@ -281,11 +281,30 @@ class EmailRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
let $ = cheerio.load(html);
|
||||
|
||||
// Remove parts of the HTML not applicable to the current segment - We do this
|
||||
// before rendering the template as the preheader for the email may be generated
|
||||
// using the HTML and we don't want to include content that should not be
|
||||
// visible depending on the segment
|
||||
$('[data-gh-segment]').get().forEach((node) => {
|
||||
// TODO: replace with NQL interpretation
|
||||
if (node.attribs['data-gh-segment'] !== segment) {
|
||||
$(node).remove();
|
||||
} else {
|
||||
// Getting rid of the attribute for a cleaner html output
|
||||
$(node).removeAttr('data-gh-segment');
|
||||
}
|
||||
});
|
||||
|
||||
html = $.html();
|
||||
|
||||
const templateData = await this.getTemplateData({
|
||||
post,
|
||||
newsletter,
|
||||
html,
|
||||
addPaywall
|
||||
addPaywall,
|
||||
segment
|
||||
});
|
||||
html = await this.renderTemplate(templateData);
|
||||
|
||||
|
@ -326,8 +345,7 @@ class EmailRenderer {
|
|||
html = juice(html, {inlinePseudoElements: true, removeStyleTags: true});
|
||||
|
||||
// happens after inlining of CSS so we can change element types without worrying about styling
|
||||
const cheerio = require('cheerio');
|
||||
const $ = cheerio.load(html);
|
||||
$ = cheerio.load(html);
|
||||
|
||||
// force all links to open in new tab
|
||||
$('a').attr('target', '_blank');
|
||||
|
@ -335,17 +353,6 @@ class EmailRenderer {
|
|||
// convert figure and figcaption to div so that Outlook applies margins
|
||||
$('figure, figcaption').each((i, elem) => !!(elem.tagName = 'div'));
|
||||
|
||||
// Remove/hide parts of the email based on segment data attributes
|
||||
$('[data-gh-segment]').get().forEach((node) => {
|
||||
// TODO: replace with NQL interpretation
|
||||
if (node.attribs['data-gh-segment'] !== segment) {
|
||||
$(node).remove();
|
||||
} else {
|
||||
// Getting rid of the attribute for a cleaner html output
|
||||
$(node).removeAttr('data-gh-segment');
|
||||
}
|
||||
});
|
||||
|
||||
// Remove duplicate black/white images (CSS based solution not working in Outlook)
|
||||
if (templateData.backgroundIsDark) {
|
||||
$('img.is-light-background').each((i, elem) => {
|
||||
|
@ -715,13 +722,19 @@ class EmailRenderer {
|
|||
* @param {object} postModel
|
||||
* @returns
|
||||
*/
|
||||
#getEmailPreheader(postModel) {
|
||||
#getEmailPreheader(postModel, segment, html) {
|
||||
let plaintext = postModel.get('plaintext');
|
||||
let customExcerpt = postModel.get('custom_excerpt');
|
||||
if (customExcerpt) {
|
||||
return customExcerpt;
|
||||
} else {
|
||||
if (plaintext) {
|
||||
// The plaintext field on the model may contain paid only content
|
||||
// so we use the provided HTML to generate the plaintext as this
|
||||
// should have already had the paid content removed
|
||||
if (segment === 'status:free') {
|
||||
plaintext = htmlToPlaintext.email(html);
|
||||
}
|
||||
return plaintext.substring(0, 500);
|
||||
} else {
|
||||
return `${postModel.get('title')} – `;
|
||||
|
@ -821,7 +834,7 @@ class EmailRenderer {
|
|||
/**
|
||||
* @private
|
||||
*/
|
||||
async getTemplateData({post, newsletter, html, addPaywall}) {
|
||||
async getTemplateData({post, newsletter, html, addPaywall, segment}) {
|
||||
let accentColor = this.#settingsCache.get('accent_color') || '#15212A';
|
||||
let adjustedAccentColor;
|
||||
let adjustedAccentContrastColor;
|
||||
|
@ -931,7 +944,7 @@ class EmailRenderer {
|
|||
image: this.#settingsCache.get('icon')
|
||||
}, true) : null
|
||||
},
|
||||
preheader: this.#getEmailPreheader(post),
|
||||
preheader: this.#getEmailPreheader(post, segment, html),
|
||||
html,
|
||||
|
||||
post: {
|
||||
|
|
|
@ -855,7 +855,7 @@ describe('Email renderer', function () {
|
|||
});
|
||||
|
||||
describe('renderBody', function () {
|
||||
let renderedPost = '<p>Lexical Test</p><img class="is-light-background" src="test-dark" /><img class="is-dark-background" src="test-light" />';
|
||||
let renderedPost;
|
||||
let postUrl = 'http://example.com';
|
||||
let customSettings = {};
|
||||
let emailRenderer;
|
||||
|
@ -864,6 +864,7 @@ describe('Email renderer', function () {
|
|||
let labsEnabled;
|
||||
|
||||
beforeEach(function () {
|
||||
renderedPost = '<p>Lexical Test</p><img class="is-light-background" src="test-dark" /><img class="is-dark-background" src="test-light" />';
|
||||
labsEnabled = true;
|
||||
basePost = {
|
||||
lexical: '{}',
|
||||
|
@ -1057,6 +1058,74 @@ describe('Email renderer', function () {
|
|||
should($('.preheader').text()).eql('Custom excerpt');
|
||||
});
|
||||
|
||||
it('does not include members-only content in preheader for non-members', async function () {
|
||||
renderedPost = '<div> Lexical Test </div> some text for both <!--members-only--> finishing part only for members';
|
||||
let post = {
|
||||
related: sinon.stub(),
|
||||
get: (key) => {
|
||||
if (key === 'lexical') {
|
||||
return '{}';
|
||||
}
|
||||
|
||||
if (key === 'visibility') {
|
||||
return 'paid';
|
||||
}
|
||||
|
||||
if (key === 'plaintext') {
|
||||
return 'foobarbaz';
|
||||
}
|
||||
},
|
||||
getLazyRelation: sinon.stub()
|
||||
};
|
||||
let newsletter = {
|
||||
get: sinon.stub()
|
||||
};
|
||||
|
||||
let response = await emailRenderer.renderBody(
|
||||
post,
|
||||
newsletter,
|
||||
'status:free',
|
||||
{}
|
||||
);
|
||||
|
||||
const $ = cheerio.load(response.html);
|
||||
should($('.preheader').text()).eql('Lexical Test some text for both');
|
||||
});
|
||||
|
||||
it('does not include paid segmented content in preheader for non-paying members', async function () {
|
||||
renderedPost = '<div> Lexical Test </div> <div data-gh-segment="status:-free"> members only section</div> some text for both';
|
||||
let post = {
|
||||
related: sinon.stub(),
|
||||
get: (key) => {
|
||||
if (key === 'lexical') {
|
||||
return '{}';
|
||||
}
|
||||
|
||||
if (key === 'visibility') {
|
||||
return 'public';
|
||||
}
|
||||
|
||||
if (key === 'plaintext') {
|
||||
return 'foobarbaz';
|
||||
}
|
||||
},
|
||||
getLazyRelation: sinon.stub()
|
||||
};
|
||||
let newsletter = {
|
||||
get: sinon.stub()
|
||||
};
|
||||
|
||||
let response = await emailRenderer.renderBody(
|
||||
post,
|
||||
newsletter,
|
||||
'status:free',
|
||||
{}
|
||||
);
|
||||
|
||||
const $ = cheerio.load(response.html);
|
||||
should($('.preheader').text()).eql('Lexical Test some text for both');
|
||||
});
|
||||
|
||||
it('only includes first author if more than 2', async function () {
|
||||
const post = createModel({...basePost, authors: [
|
||||
createModel({
|
||||
|
|
Loading…
Add table
Reference in a new issue