0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-04-08 02:52:39 -05:00

Added dynamic text truncation in email latest posts

refs https://github.com/TryGhost/Team/issues/2675

Truncate text depending on mobile/desktop and feature image.
This commit is contained in:
Simon Backx 2023-03-24 12:14:00 +01:00
parent 045e1ee33d
commit d9c92816e0
6 changed files with 318 additions and 54 deletions

View file

@ -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 \\]\\|\\\\\\\\\\.\\)\\*"/,

View file

@ -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;
}

View file

@ -21,6 +21,15 @@ const messages = {
}
};
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
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)) + '<span class="mobile-only">' + escapeHtml(text.substring(maxLength - 1, maxLengthMobile - 1)) + '</span>' + '<span class="hide-mobile">…</span>';
}
return escapeHtml(text.substring(0, maxLength - 1)) + '<span class="mobile-only">' + escapeHtml(text.substring(maxLength - 1, maxLengthMobile - 1)) + '</span>' + '…';
} 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) {

View file

@ -21,9 +21,9 @@
{{/if}}
{{/if}}
<td valign="top" align="left" class="latest-post-title">
<h4 class="{{#if ../latestPostsHasImages}}{{#if (not featureImage)}}no-image{{/if}}{{/if}}">{{title}}</h4>
<h4 class="{{#if ../latestPostsHasImages}}{{#if (not featureImage)}}no-image{{/if}}{{/if}}">{{{title}}}</h4>
{{#if excerpt}}
<p>{{excerpt}}</p>
<p>{{{excerpt}}}</p>
{{/if}}
</td>
{{#if ../latestPostsHasImages}}

View file

@ -1071,6 +1071,10 @@ a[data-flickr-embed] img {
width: 0;
}
.mobile-only {
display: none;
}
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@ -1081,6 +1085,14 @@ a[data-flickr-embed] img {
min-width: 100%;
}
.hide-mobile {
display: none;
}
.mobile-only {
display: initial !important;
}
table.body p,
table.body ul,
table.body ol,
@ -1218,12 +1230,12 @@ a[data-flickr-embed] img {
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;
}

View file

@ -1659,7 +1659,7 @@ describe('Email renderer', function () {
assert.deepEqual(data.latestPosts,
[
{
excerpt: 'Super long custom excerpt. Super long custom excerpt. Super…',
excerpt: 'Super long custom excerpt. Super long custom excerpt. Super<span class="mobile-only"> long custom excerpt. Super long custom excer</span>…',
title: 'Test Post 1',
url: 'http://example.com',
featureImage: {
@ -1740,6 +1740,18 @@ describe('Email renderer', function () {
});
});
describe('truncateHTML', function () {
it('works correctly', async function () {
const emailRenderer = new EmailRenderer({});
assert.equal(emailRenderer.truncateHtml('This is a short one', 5, 10), 'This<span class="mobile-only"> is a</span>…');
assert.equal(emailRenderer.truncateHtml('This is a', 5, 10), 'This<span class="mobile-only"> is a</span><span class="hide-mobile">…</span>');
assert.equal(emailRenderer.truncateHtml('This', 5, 10), 'This');
assert.equal(emailRenderer.truncateHtml('This is a long text', 5, 5), 'This…');
assert.equal(emailRenderer.truncateHtml('This is a long text', 5), 'This…');
assert.equal(emailRenderer.truncateHtml(null, 5, 10), '');
});
});
describe('limitImageWidth', function () {
it('Limits width of local images', async function () {
const emailRenderer = new EmailRenderer({