From 8f3617aaa8e84e7670220fae8efa9d8d337da650 Mon Sep 17 00:00:00 2001 From: Peter Zimon Date: Thu, 29 Feb 2024 17:09:34 +0100 Subject: [PATCH] Content card design improvements (#19737) refs. https://linear.app/tryghost/issue/DES-122/bookmark-card-issues This PR addresses the following content card related problems: 1. The design of the following cards are more self-contained so it makes more sense to use `px` for their font-sizes and spacings so it looks the same regardless of the theme. Of course themes still can override these values. Updated cards to use `px` for font sizing: - audio - bookmark - file - product 2. So far header and signup cards had been using `rem` for font-sizes and some sizing. This commit updates these to use `em` instead so that it's consistent with all other cards. 3. The favicon sometimes is not available for bookmark cards. This PR also fixes that by providing a default favicon for these cases. --- .../core/frontend/src/cards/css/audio.css | 21 ++++++------ .../core/frontend/src/cards/css/bookmark.css | 8 ++--- .../core/core/frontend/src/cards/css/file.css | 29 +++++++++------- .../core/frontend/src/cards/css/header_v2.css | 29 ++++++++-------- .../core/frontend/src/cards/css/product.css | 16 ++++----- .../core/frontend/src/cards/css/signup.css | 34 ++++++++++--------- .../core/frontend/src/cards/css/video.css | 6 ++-- ghost/core/test/e2e-api/admin/oembed.test.js | 29 ++++++++++++++++ ghost/oembed-service/lib/OEmbedService.js | 9 +++++ 9 files changed, 113 insertions(+), 68 deletions(-) diff --git a/ghost/core/core/frontend/src/cards/css/audio.css b/ghost/core/core/frontend/src/cards/css/audio.css index 05df0afe14..b0793f5ffe 100644 --- a/ghost/core/core/frontend/src/cards/css/audio.css +++ b/ghost/core/core/frontend/src/cards/css/audio.css @@ -7,7 +7,8 @@ display: flex; width: 100%; min-height: 96px; - border-radius: 3px; + border-radius: 6px; + padding: 4px; box-shadow: inset 0 0 0 1px rgba(124, 139, 154, 0.25); } @@ -25,7 +26,7 @@ background: transparent; object-fit: cover; aspect-ratio: 1/1; - border-radius: 2px; + border-radius: 3px; } .kg-audio-thumbnail.placeholder { @@ -55,8 +56,8 @@ padding: 8px 12px; border: none; font-family: inherit; - font-size: 1.15em; - font-weight: 700; + font-size: 16px; + font-weight: 600; line-height: 1.15em; background: transparent; } @@ -72,9 +73,9 @@ min-width: 38px; padding: 0 4px; font-family: inherit; - font-size: .85em; + font-size: 12.5px; font-weight: 500; - line-height: 1.4em; + line-height: 1em; white-space: nowrap; } @@ -82,9 +83,9 @@ width: 56px; color: #ababab; font-family: inherit; - font-size: .85em; + font-size: 12.5px; font-weight: 500; - line-height: 1.4em; + line-height: 1em; white-space: nowrap; } @@ -127,9 +128,9 @@ min-width: 37px; padding: 0 4px; font-family: inherit; - font-size: .85em; + font-size: 12.5px; font-weight: 600; - line-height: 1.4em; + line-height: 1em; text-align: left; background: transparent; white-space: nowrap; diff --git a/ghost/core/core/frontend/src/cards/css/bookmark.css b/ghost/core/core/frontend/src/cards/css/bookmark.css index 818308f633..e14af83b01 100644 --- a/ghost/core/core/frontend/src/cards/css/bookmark.css +++ b/ghost/core/core/frontend/src/cards/css/bookmark.css @@ -13,7 +13,7 @@ .kg-bookmark-card a.kg-bookmark-container:hover { display: flex; text-decoration: none; - border-radius: 3px; + border-radius: 6px; border: 1px solid rgb(124 139 154 / 25%); overflow: hidden; color: inherit; @@ -31,14 +31,14 @@ } .kg-bookmark-title { - font-size: 1.5rem; + font-size: 15px; line-height: 1.4em; font-weight: 600; } .kg-bookmark-description { display: -webkit-box; - font-size: 1.4rem; + font-size: 14px; line-height: 1.5em; margin-top: 3px; font-weight: 400; @@ -54,7 +54,7 @@ align-items: center; margin-top: 22px; width: 100%; - font-size: 1.4rem; + font-size: 14px; font-weight: 500; white-space: nowrap; } diff --git a/ghost/core/core/frontend/src/cards/css/file.css b/ghost/core/core/frontend/src/cards/css/file.css index 2ebbb1d815..165ba035c0 100644 --- a/ghost/core/core/frontend/src/cards/css/file.css +++ b/ghost/core/core/frontend/src/cards/css/file.css @@ -12,10 +12,10 @@ align-items: stretch; justify-content: space-between; color: inherit; - padding: 6px; + padding: 12px; min-height: 92px; border: 1px solid rgb(124 139 154 / 25%); - border-radius: 3px; + border-radius: 5px; transition: all ease-in-out 0.35s; text-decoration: none; width: 100%; @@ -34,26 +34,27 @@ } .kg-file-card-title { - font-size: 1.15em; - font-weight: 700; + font-size: 16px; + font-weight: 600; line-height: 1.3em; } .kg-file-card-caption { - font-size: 0.95em; + font-size: 14px; line-height: 1.3em; - opacity: 0.6; + opacity: 0.7; } .kg-file-card-title + .kg-file-card-caption { - margin-top: -3px; + flex-grow: 1; + margin-top: 3px; } .kg-file-card-metadata { display: inline; - font-size: 0.825em; + font-size: 14px; line-height: 1.3em; - margin-top: 2px; + margin-top: 5px; } .kg-file-card-filename { @@ -63,14 +64,15 @@ .kg-file-card-filesize { display: inline-block; - font-size: 0.925em; + font-size: 14px; opacity: 0.6; } .kg-file-card-filesize:before { display: inline-block; content: "\2022"; - margin-right: 4px; + margin-left: 6px; + margin-right: 6px; } .kg-file-card-icon { @@ -81,6 +83,7 @@ width: 80px; min-width: 80px; height: 100%; + min-height: 80px; } .kg-file-card-icon:before { @@ -94,7 +97,7 @@ background: currentColor; opacity: 0.06; transition: opacity ease-in-out 0.35s; - border-radius: 2px; + border-radius: 3px; } .kg-file-card a.kg-file-card-container:hover .kg-file-card-icon:before { @@ -123,7 +126,7 @@ } .kg-file-card-small .kg-file-card-metadata { - font-size: 1.0em; + font-size: 14px; margin-top: 0; } diff --git a/ghost/core/core/frontend/src/cards/css/header_v2.css b/ghost/core/core/frontend/src/cards/css/header_v2.css index 209f4b6ddf..ef5a9a1c7e 100644 --- a/ghost/core/core/frontend/src/cards/css/header_v2.css +++ b/ghost/core/core/frontend/src/cards/css/header_v2.css @@ -104,7 +104,7 @@ .kg-content-wide .kg-header-card-content .kg-header-card-image { height: 100%; - padding: 8rem 0; + padding: 5.6em 0; object-fit: contain; } @@ -112,22 +112,22 @@ .kg-header-card h2.kg-header-card-heading { margin: 0; - font-size: clamp(2.4rem, 4vw, 3.6rem); + font-size: clamp(1.7em, 4vw, 2.5em); font-weight: 700; - line-height: 1em; + line-height: 1.05em; letter-spacing: -0.01em; } .kg-header-card.kg-width-wide h2.kg-header-card-heading { - font-size: clamp(2.4rem, 5vw, 4.8rem); + font-size: clamp(1.7em, 5vw, 3.3em); } .kg-header-card.kg-width-full h2.kg-header-card-heading { - font-size: clamp(2.8rem, 5.6vw, 6rem); + font-size: clamp(1.9em, 5.6vw, 4.2em); } .kg-header-card.kg-width-full.kg-layout-split h2.kg-header-card-heading { - font-size: clamp(2.8rem, 4vw, 4.8rem); + font-size: clamp(1.9em, 4vw, 3.3em); } /* Subheading */ @@ -139,7 +139,7 @@ .kg-header-card .kg-header-card-subheading { max-width: 40em; margin: 0; - font-size: clamp(1.05em, 2vw, 2rem); + font-size: clamp(1.05em, 2vw, 1.4em); font-weight: 500; line-height: 1.2em; } @@ -153,23 +153,24 @@ } .kg-header-card.kg-width-wide .kg-header-card-subheading { - font-size: clamp(1.05em, 2vw, 2.2rem); + font-size: clamp(1.05em, 2vw, 1.55em); } .kg-header-card.kg-width-full .kg-header-card-subheading:not(.kg-layout-split .kg-header-card-subheading) { max-width: min(65vmax, 1200px); - font-size: clamp(1.05em, 2vw, 2.4rem); + font-size: clamp(1.05em, 2vw, 1.7em); } .kg-header-card.kg-width-full.kg-layout-split .kg-header-card-subheading { - font-size: clamp(1.05em, 2vw, 2.2rem); + font-size: clamp(1.05em, 2vw, 1.55em); } .kg-header-card.kg-v2 .kg-header-card-button { display: flex; position: relative; align-items: center; - height: 4.6rem; + height: 2.9em; + min-height: 46px; padding: 0 1.2em; outline: none; border: none; @@ -243,15 +244,15 @@ } .kg-content-wide .kg-header-card-content .kg-header-card-image { - padding: 2.4rem 0 0; + padding: 1.7em 0 0; } .kg-content-wide.kg-swapped .kg-header-card-content .kg-header-card-image { - padding: 0 0 2.4rem; + padding: 0 0 1.7em; } .kg-header-card.kg-v2 .kg-header-card-button { - height: 4.2rem; + height: 2.9em; } .kg-header-card.kg-v2.kg-width-wide .kg-header-card-button, diff --git a/ghost/core/core/frontend/src/cards/css/product.css b/ghost/core/core/frontend/src/cards/css/product.css index e7c480365f..4bfda77c16 100644 --- a/ghost/core/core/frontend/src/cards/css/product.css +++ b/ghost/core/core/frontend/src/cards/css/product.css @@ -36,8 +36,8 @@ .kg-product-card h4.kg-product-card-title { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; text-decoration: none; - font-weight: 700; - font-size: 1.4em; + font-weight: 600; + font-size: 21px; margin-top: 0; margin-bottom: 0; line-height: 1.15em; @@ -51,7 +51,7 @@ .kg-product-card .kg-product-card-description ol, .kg-product-card .kg-product-card-description ul { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; - font-size: 0.95em; + font-size: 14px; line-height: 1.5em; opacity: .7; margin-bottom: 0; @@ -94,7 +94,7 @@ } .kg-product-card-rating-star { - height: 28px; + height: 20px; width: 20px; } @@ -116,14 +116,14 @@ position: static; align-items: center; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; - font-size: 0.95em; + font-size: 14px; font-weight: 600; line-height: 1em; text-decoration: none; width: 100%; - height: 2.4em; - border-radius: 5px; - padding: 0 1.2em; + height: 38px; + border-radius: 6px; + padding: 0 12px; transition: opacity 0.2s ease-in-out; } diff --git a/ghost/core/core/frontend/src/cards/css/signup.css b/ghost/core/core/frontend/src/cards/css/signup.css index 5817472a2d..264fa79d8b 100644 --- a/ghost/core/core/frontend/src/cards/css/signup.css +++ b/ghost/core/core/frontend/src/cards/css/signup.css @@ -97,7 +97,7 @@ .kg-content-wide .kg-signup-card-content .kg-signup-card-image { height: 100%; - padding: 8rem 0; + padding: 5.6em 0; object-fit: contain; } @@ -105,22 +105,22 @@ .kg-signup-card h2.kg-signup-card-heading { margin: 0; - font-size: clamp(2.4rem, 4vw, 3.6rem); + font-size: clamp(1.7em, 4vw, 2.5em); font-weight: 700; - line-height: 1em; + line-height: 1.05em; letter-spacing: -0.01em; } .kg-signup-card.kg-width-wide h2.kg-signup-card-heading { - font-size: clamp(2.4rem, 5vw, 4.8rem); + font-size: clamp(1.7em, 5vw, 3.3em); } .kg-signup-card.kg-width-full h2.kg-signup-card-heading { - font-size: clamp(2.8rem, 5.6vw, 6rem); + font-size: clamp(1.9em, 5.6vw, 4.2em); } .kg-signup-card.kg-width-full.kg-layout-split h2.kg-signup-card-heading { - font-size: clamp(2.8rem, 4vw, 4.8rem); + font-size: clamp(1.9em, 4vw, 3.3em); } /* Subheading */ @@ -132,7 +132,7 @@ .kg-signup-card .kg-signup-card-subheading { max-width: 40em; margin: 0; - font-size: clamp(1.05em, 2vw, 2rem); + font-size: clamp(1.05em, 2vw, 1.4em); font-weight: 500; line-height: 1.2em; } @@ -146,16 +146,16 @@ } .kg-signup-card.kg-width-wide .kg-signup-card-subheading { - font-size: clamp(1.05em, 2vw, 2.2rem); + font-size: clamp(1.05em, 2vw, 1.55em); } .kg-signup-card.kg-width-full .kg-signup-card-subheading:not(.kg-layout-split .kg-signup-card-subheading) { max-width: min(65vmax, 1200px); - font-size: clamp(1.05em, 2vw, 2.4rem); + font-size: clamp(1.05em, 2vw, 1.7em); } .kg-signup-card.kg-width-full.kg-layout-split .kg-signup-card-subheading { - font-size: clamp(1.05em, 2vw, 2.2rem); + font-size: clamp(1.05em, 2vw, 1.55em); } /* Subscribe form */ @@ -203,7 +203,8 @@ .kg-signup-card-input { width: 100%; - height: 4.6rem; + height: 2.9em; + min-height: 46px; margin: 0 3px 0 0; padding: 12px 16px; border: none; @@ -220,7 +221,8 @@ display: flex; position: relative; align-items: center; - height: 4.6rem; + height: 2.9em; + min-height: 46px; padding: 0 1.2em; outline: none; border: none; @@ -355,21 +357,21 @@ } .kg-content-wide .kg-signup-card-content .kg-signup-card-image { - padding: 2.4rem 0 0; + padding: 1.7em 0 0; } .kg-content-wide.kg-swapped .kg-signup-card-content .kg-signup-card-image { - padding: 0 0 2.4rem; + padding: 0 0 1.7em; } .kg-signup-card-input { - height: 4.2rem; + height: 2.9em; padding: 6px 12px; font-size: 1em; } .kg-signup-card-button { - height: 4.2rem; + height: 2.9em; } .kg-signup-card.kg-width-wide .kg-signup-card-button, diff --git a/ghost/core/core/frontend/src/cards/css/video.css b/ghost/core/core/frontend/src/cards/css/video.css index 3d0247b9f1..e3ddd5c797 100644 --- a/ghost/core/core/frontend/src/cards/css/video.css +++ b/ghost/core/core/frontend/src/cards/css/video.css @@ -82,7 +82,7 @@ padding: 0 4px; color: #fff; font-family: inherit; - font-size: .85em; + font-size: 12.5px; font-weight: 500; line-height: 1.4em; white-space: nowrap; @@ -91,7 +91,7 @@ .kg-video-time { color: rgba(255, 255, 255, 0.6); font-family: inherit; - font-size: .85em; + font-size: 12.5px; font-weight: 500; line-height: 1.4em; white-space: nowrap; @@ -142,7 +142,7 @@ padding: 0 4px; color: #fff; font-family: inherit; - font-size: .85em; + font-size: 12.5px; font-weight: 600; line-height: 1.4em; text-align: left; diff --git a/ghost/core/test/e2e-api/admin/oembed.test.js b/ghost/core/test/e2e-api/admin/oembed.test.js index 7f0b9920e5..dfeed17f11 100644 --- a/ghost/core/test/e2e-api/admin/oembed.test.js +++ b/ghost/core/test/e2e-api/admin/oembed.test.js @@ -221,6 +221,35 @@ describe('Oembed API', function () { should.exist(res.body.errors); }); + + it('should replace icon URL when it returns 404', async function () { + // Mock the page so it contains a readable icon URL + const pageMock = nock('http://example.com') + .get('/page-with-icon') + .reply( + 200, + 'TESTING', + {'content-type': 'text/html'} + ); + + // Mock the icon URL to return 404 + nock('http://example.com/') + .head('/icon.svg') + .reply(404); + + const url = encodeURIComponent(' http://example.com/page-with-icon\t '); // Whitespaces are to make sure urls are trimmed + const res = await request.get(localUtils.API.getApiQuery(`oembed/?url=${url}&type=bookmark`)) + .set('Origin', config.get('url')) + .expect('Content-Type', /json/) + .expect('Cache-Control', testUtils.cacheRules.private) + .expect(200); + + // Check that the icon URL mock was loaded + pageMock.isDone().should.be.true(); + + // Check that the substitute icon URL is returned in place of the original + res.body.metadata.icon.should.eql('https://static.ghost.org/v5.0.0/images/link-icon.svg'); + }); }); describe('with unknown provider', function () { diff --git a/ghost/oembed-service/lib/OEmbedService.js b/ghost/oembed-service/lib/OEmbedService.js index fedf30f5e2..738ff6895d 100644 --- a/ghost/oembed-service/lib/OEmbedService.js +++ b/ghost/oembed-service/lib/OEmbedService.js @@ -259,6 +259,15 @@ class OEmbedService { }); } + if (metadata.icon) { + try { + await this.externalRequest.head(metadata.icon); + } catch (err) { + metadata.icon = 'https://static.ghost.org/v5.0.0/images/link-icon.svg'; + logging.error(err); + } + } + return { version: '1.0', type: 'bookmark',