Skip to content

Commit 39a88eb

Browse files
committed
🐛 Fixed appearance of some welcome email image captions
closes https://linear.app/ghost/issue/NY-1233 Welcome emails use `<figure>` and `<figcaption>` for images and their captions, which some email clients don't support. That causes the styling to be messed up in those clients. [Newsletter rendering had already fixed this.][0] This patch fixes it the same way. In the long term, we should unify these renderers. In the short term, let's fix this bug. [0]: https://github.com/TryGhost/Ghost/blob/d26bfdb0b20f029a82709782804164d621848d3f/ghost/core/core/server/services/email-service/email-renderer.js#L514-L544
1 parent 19d6ad3 commit 39a88eb

2 files changed

Lines changed: 45 additions & 10 deletions

File tree

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,39 @@
1+
const cheerio = require('cheerio');
12
const juice = require('juice');
23
const htmlToPlaintext = require('@tryghost/html-to-plaintext');
34

5+
/**
6+
* @param {string} html
7+
* @returns {string}
8+
*/
9+
const finalizeHtml = (html) => {
10+
// Add a class to each figcaption so we can style them in the email.
11+
let $ = cheerio.load(html, null, false);
12+
$('figcaption').addClass('kg-card-figcaption');
13+
html = $.html();
14+
15+
const juicedHtml = juice(html, {inlinePseudoElements: true, removeStyleTags: true});
16+
17+
// Many email clients, like Outlook and Yahoo, [lack support for <figure>
18+
// and <figcaption>][0]. To work around this, change the tags to <div>s.
19+
// Juice should have properly styled them.
20+
//
21+
// [0]: https://www.caniemail.com/features/html-semantics/
22+
$ = cheerio.load(juicedHtml, null, false);
23+
$('figure, figcaption').each((_, el) => {
24+
el.tagName = 'div';
25+
});
26+
27+
return $.html();
28+
};
29+
430
module.exports = {
31+
/**
32+
* @param {string} html
33+
* @returns {{html: string, plaintext: string}}
34+
*/
535
finalize(html) {
6-
const inlinedHtml = juice(html, {inlinePseudoElements: true, removeStyleTags: true});
7-
const plaintext = htmlToPlaintext.email(inlinedHtml);
8-
return {html: inlinedHtml, plaintext};
36+
const resultHtml = finalizeHtml(html);
37+
return {html: resultHtml, plaintext: htmlToPlaintext.email(resultHtml)};
938
}
1039
};

ghost/core/test/unit/server/services/member-welcome-emails/member-welcome-email-renderer.test.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,7 @@ describe('MemberWelcomeEmailRenderer', function () {
902902
assert(!$button.attr('style')?.includes('margin: 0 auto'), 'button should not have margin: 0 auto');
903903
});
904904

905-
it('inlines figcaption styles for image card captions', async function () {
905+
it('uses <div>s with inline styles for image cards', async function () {
906906
lexicalRenderStub.resolves(`
907907
<figure class="kg-card kg-image-card kg-card-hascaption">
908908
<img src="https://example.com/photo.jpg" class="kg-image" alt="alt text" loading="lazy" width="600" height="400">
@@ -919,10 +919,15 @@ describe('MemberWelcomeEmailRenderer', function () {
919919
});
920920

921921
const $ = cheerio.load(result.html);
922-
const $figcaption = $('figcaption');
923-
assert.equal($figcaption.text(), 'A caption');
924-
assert($figcaption.attr('style').includes('text-align: center'), 'figcaption should be centered');
925-
assert($figcaption.attr('style').includes('font-size: 13px'), 'figcaption should have 13px font');
922+
923+
assert.equal($('figure').length, 0);
924+
assert.equal($('figcaption').length, 0);
925+
926+
const $caption = $('.kg-image-card .kg-card-figcaption');
927+
assert.equal($caption.text(), 'A caption');
928+
assert($caption.attr('style').includes('text-align: center'), 'caption should be centered');
929+
assert($caption.attr('style').includes('font-size: 13px'), 'caption should have 13px font');
930+
assert($caption.attr('style').includes('font-family: -apple-system'), 'caption should keep caption font family');
926931
});
927932

928933
it('inlines figure margin and image max-width for image cards', async function () {
@@ -941,8 +946,9 @@ describe('MemberWelcomeEmailRenderer', function () {
941946
});
942947

943948
const $ = cheerio.load(result.html);
944-
assert($('figure').attr('style').includes('margin: 0 0 1.5em'), 'figure should have bottom margin');
945-
assert($('figure img.kg-image').attr('style').includes('max-width: 100%'), 'img should have max-width: 100%');
949+
const $figure = $('.kg-image-card');
950+
assert($figure.attr('style').includes('margin: 0 0 1.5em'), 'figure should have bottom margin');
951+
assert($figure.find('img').attr('style').includes('max-width: 100%'), 'img should have max-width: 100%');
946952
});
947953

948954
it('inlines width 100% on button card outer table', async function () {

0 commit comments

Comments
 (0)