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 45469deaaa..e3ac021810 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 @@ -169,6 +169,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -305,12 +313,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -701,7 +711,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": "21891", + "content-length": "22062", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -784,6 +794,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -920,12 +938,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -1336,7 +1356,7 @@ exports[`Email Preview API Read can read post email preview with fields 4: [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": "26714", + "content-length": "26885", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -1450,6 +1470,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -1586,12 +1614,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -1989,7 +2019,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": "21657", + "content-length": "21828", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -2432,6 +2462,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -2568,12 +2606,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -2981,7 +3021,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": "22149", + "content-length": "22320", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, @@ -3450,6 +3490,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -3586,12 +3634,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -3999,7 +4049,7 @@ exports[`Email Preview API Read uses the posts newsletter by default 4: [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": "22149", + "content-length": "22320", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, diff --git a/ghost/core/test/integration/services/email-service/__snapshots__/batch-sending.test.js.snap b/ghost/core/test/integration/services/email-service/__snapshots__/batch-sending.test.js.snap index a2c608b885..c6661e18c4 100644 --- a/ghost/core/test/integration/services/email-service/__snapshots__/batch-sending.test.js.snap +++ b/ghost/core/test/integration/services/email-service/__snapshots__/batch-sending.test.js.snap @@ -62,6 +62,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -198,12 +206,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -657,6 +667,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -793,12 +811,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -1238,6 +1258,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -1374,12 +1402,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -1819,6 +1849,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -1955,12 +1993,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -2400,6 +2440,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -2536,12 +2584,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -2939,6 +2989,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -3075,12 +3133,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -4594,6 +4654,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -4730,12 +4798,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -5275,6 +5345,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -5411,12 +5489,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -5912,6 +5992,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -6048,12 +6136,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -7797,6 +7887,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -7933,12 +8031,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -8433,6 +8533,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -8569,12 +8677,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -9069,6 +9179,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -9205,12 +9323,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -9705,6 +9825,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -9841,12 +9969,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -10341,6 +10471,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -10477,12 +10615,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -11613,6 +11753,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -11749,12 +11897,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } @@ -12196,6 +12346,14 @@ Object { min-width: 100%; } + .hide-mobile { + display: none; + } + + .mobile-only { + display: initial !important; + } + table.body p, table.body ul, table.body ol, @@ -12332,12 +12490,14 @@ table.body .footer a { padding-bottom: 8px !important; } - table.body .latest-post h4 { + table.body .latest-post h4, +table.body .latest-post h4 span { padding: 4px 0 !important; font-size: 18px !important; } - table.body .latest-post p { + table.body .latest-post p, +table.body .latest-post p span { font-size: 13px !important; line-height: 1.25em; } diff --git a/ghost/email-service/lib/email-renderer.js b/ghost/email-service/lib/email-renderer.js index 3c2493b7c2..fde383874b 100644 --- a/ghost/email-service/lib/email-renderer.js +++ b/ghost/email-service/lib/email-renderer.js @@ -21,6 +21,15 @@ const messages = { } }; +function escapeHtml(unsafe) { + return unsafe + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + function formatDateLong(date, timezone) { return DateTime.fromJSDate(date).setZone(timezone).setLocale('en-gb').toLocaleString({ year: 'numeric', @@ -710,6 +719,27 @@ class EmailRenderer { } } + /** + * + * @param {*} text + * @param {number} maxLength + * @param {number} maxLengthMobile should be larger than maxLength + * @returns + */ + truncateHtml(text, maxLength, maxLengthMobile) { + if (!maxLengthMobile || maxLength >= maxLengthMobile) { + return escapeHtml(this.truncateText(text, maxLength)); + } + if (text && text.length > maxLength) { + if (text.length <= maxLengthMobile) { + return escapeHtml(text.substring(0, maxLength - 1)) + '' + escapeHtml(text.substring(maxLength - 1, maxLengthMobile - 1)) + '' + ' '; + } + return escapeHtml(text.substring(0, maxLength - 1)) + '' + escapeHtml(text.substring(maxLength - 1, maxLengthMobile - 1)) + '' + '…'; + } else { + return escapeHtml(text ?? ''); + } + } + /** * @private */ @@ -784,7 +814,7 @@ class EmailRenderer { const {href: featureImageMobile, width: featureImageMobileWidth, height: featureImageMobileHeight} = await this.limitImageWidth(latestPost.get('feature_image'), 600, 480); latestPosts.push({ - title: this.truncateText(latestPost.get('title'), 85), + title: this.truncateHtml(latestPost.get('title'), featureImage ? 85 : 105, 105), url: this.#getPostUrl(latestPost), featureImage: featureImage ? { src: featureImage, @@ -796,7 +826,7 @@ class EmailRenderer { width: featureImageMobileWidth, height: featureImageMobileHeight } : null, - excerpt: this.truncateText(latestPost.get('custom_excerpt') || latestPost.get('plaintext'), 60) + excerpt: this.truncateHtml(latestPost.get('custom_excerpt') || latestPost.get('plaintext'), featureImage ? 60 : 70, 105) }); if (featureImage) { diff --git a/ghost/email-service/lib/email-templates/partials/latest-posts.hbs b/ghost/email-service/lib/email-templates/partials/latest-posts.hbs index 113903bf80..cff32deb18 100644 --- a/ghost/email-service/lib/email-templates/partials/latest-posts.hbs +++ b/ghost/email-service/lib/email-templates/partials/latest-posts.hbs @@ -21,9 +21,9 @@ {{/if}} {{/if}}
{{excerpt}}
+{{{excerpt}}}
{{/if}}