From 3cc60bc7eafa428127b9dabfc059ccf00b69acba Mon Sep 17 00:00:00 2001 From: Sag Date: Tue, 10 Oct 2023 15:56:59 -0300 Subject: [PATCH] Added {{readable_url}} helper (#18564) refs https://github.com/TryGhost/Product/issues/4021 - used in the recommendations template to display urls in a short readable way, e.g. google.com instead of https://www.google.com?query=1#section --- ghost/core/core/frontend/helpers/img_url.js | 12 +++---- .../core/frontend/helpers/readable_url.js | 32 +++++++++++++++++++ .../frontend/helpers/tpl/recommendations.hbs | 1 + .../frontend/helpers/readable_url.test.js | 31 ++++++++++++++++++ .../frontend/helpers/recommendations.test.js | 30 +++-------------- .../theme-engine/handlebars/helpers.test.js | 2 +- 6 files changed, 76 insertions(+), 32 deletions(-) create mode 100644 ghost/core/core/frontend/helpers/readable_url.js create mode 100644 ghost/core/test/unit/frontend/helpers/readable_url.test.js diff --git a/ghost/core/core/frontend/helpers/img_url.js b/ghost/core/core/frontend/helpers/img_url.js index 2ff53034d5..1511a0b71e 100644 --- a/ghost/core/core/frontend/helpers/img_url.js +++ b/ghost/core/core/frontend/helpers/img_url.js @@ -54,7 +54,7 @@ module.exports = function imgUrl(requestedImageUrl, options) { // ignore errors and just return the original URL } } - + return requestedImageUrl; } @@ -117,7 +117,7 @@ function getUnsplashImage(imagePath, sizeOptions) { const parsedUrl = new URL(imagePath); const {requestedSize, imageSizes, requestedFormat} = sizeOptions; - if (requestedFormat) { + if (requestedFormat) { const supportedFormats = ['avif', 'gif', 'jpg', 'png', 'webp']; if (supportedFormats.includes(requestedFormat)) { parsedUrl.searchParams.set('fm', requestedFormat); @@ -150,13 +150,13 @@ function getUnsplashImage(imagePath, sizeOptions) { } /** - * - * @param {string} imagePath - * @param {Object} sizeOptions + * + * @param {string} imagePath + * @param {Object} sizeOptions * @param {string} sizeOptions.requestedSize * @param {Object[]} sizeOptions.imageSizes * @param {string} [sizeOptions.requestedFormat] - * @returns + * @returns */ function getImageWithSize(imagePath, sizeOptions) { const hasLeadingSlash = imagePath[0] === '/'; diff --git a/ghost/core/core/frontend/helpers/readable_url.js b/ghost/core/core/frontend/helpers/readable_url.js new file mode 100644 index 0000000000..f91d8ab7e3 --- /dev/null +++ b/ghost/core/core/frontend/helpers/readable_url.js @@ -0,0 +1,32 @@ +// # Readable URL helper +// Usage: `{{readable_url "https://google.com"}}` +// +// Returns a human readable URL for the given URL, e.g. google.com for https://www.google.com?query=1#section + +const logging = require('@tryghost/logging'); +const sentry = require('../../shared/sentry'); +const errors = require('@tryghost/errors'); +const {SafeString} = require('../services/handlebars'); + +function captureError(message) { + const error = new errors.IncorrectUsageError({message}); + sentry.captureException(error); + logging.error(error); +} + +module.exports = function readableUrl(inputUrl) { + if (!inputUrl || typeof inputUrl !== 'string') { + captureError(`Expected a string, received ${inputUrl}.`); + return new SafeString(''); + } + + try { + const url = new URL(inputUrl); + const readable = url.hostname.replace(/^www\./, '') + url.pathname.replace(/\/$/, ''); + + return new SafeString(readable); + } catch (e) { + captureError(`The string "${inputUrl}" could not be parsed as URL.`); + return new SafeString(inputUrl); + } +}; diff --git a/ghost/core/core/frontend/helpers/tpl/recommendations.hbs b/ghost/core/core/frontend/helpers/tpl/recommendations.hbs index 00ad7ccf8a..48da613502 100644 --- a/ghost/core/core/frontend/helpers/tpl/recommendations.hbs +++ b/ghost/core/core/frontend/helpers/tpl/recommendations.hbs @@ -4,6 +4,7 @@
  • {{rec.title}} + {{readable_url rec.url}}
    {{rec.title}}

    {{rec.description}}

    diff --git a/ghost/core/test/unit/frontend/helpers/readable_url.test.js b/ghost/core/test/unit/frontend/helpers/readable_url.test.js new file mode 100644 index 0000000000..9adb8a9b66 --- /dev/null +++ b/ghost/core/test/unit/frontend/helpers/readable_url.test.js @@ -0,0 +1,31 @@ +const should = require('should'); +const readable_url = require('../../../../core/frontend/helpers/readable_url'); + +describe('{{#readable_url}} helper', function () { + it('renders a short URL, without protocol, www, query params nor hash fragments', function () { + const readable = readable_url.call( + {}, + 'https://www.foobar.com/some/path/?query=param#hash/' + ); + + readable.string.should.equal('foobar.com/some/path'); + }); + + it('renders an empty string when the input is not a string', function () { + const readable = readable_url.call( + {}, + {foo: 'bar'} + ); + + readable.string.should.equal(''); + }); + + it('returns the input string if not parsable as URL', function () { + const readable = readable_url.call( + {}, + 'hello world' + ); + + readable.string.should.equal('hello world'); + }); +}); diff --git a/ghost/core/test/unit/frontend/helpers/recommendations.test.js b/ghost/core/test/unit/frontend/helpers/recommendations.test.js index 4649d69a64..3c2060a63f 100644 --- a/ghost/core/test/unit/frontend/helpers/recommendations.test.js +++ b/ghost/core/test/unit/frontend/helpers/recommendations.test.js @@ -10,6 +10,7 @@ const proxy = require('../../../../core/frontend/services/proxy'); const recommendations = require('../../../../core/frontend/helpers/recommendations'); const foreach = require('../../../../core/frontend/helpers/foreach'); +const readable_url = require('../../../../core/frontend/helpers/readable_url'); const {settingsCache} = proxy; function trimSpaces(string) { @@ -30,6 +31,7 @@ describe('{{#recommendations}} helper', function () { // The recommendation template expects this helper hbs.registerHelper('foreach', foreach); + hbs.registerHelper('readable_url', readable_url); // Stub settings cache sinon.stub(settingsCache, 'get'); @@ -42,10 +44,7 @@ describe('{{#recommendations}} helper', function () { return { browse: sinon.stub().resolves({recommendations: [ {id: '1', title: 'Recommendation 1', url: 'https://recommendations1.com', favicon: 'https://recommendations1.com/favicon.ico', description: 'Description 1'}, - {id: '2', title: 'Recommendation 2', url: 'https://recommendations2.com', favicon: 'https://recommendations2.com/favicon.ico', description: 'Description 2'}, - {id: '3', title: 'Recommendation 3', url: 'https://recommendations3.com', favicon: 'https://recommendations3.com/favicon.ico', description: 'Description 3'}, - {id: '4', title: 'Recommendation 4', url: 'https://recommendations4.com', favicon: 'https://recommendations4.com/favicon.ico', description: 'Description 4'}, - {id: '5', title: 'Recommendation 5', url: 'https://recommendations5.com', favicon: 'https://recommendations5.com/favicon.ico', description: 'Description 5'} + {id: '2', title: 'Recommendation 2', url: 'https://recommendations2.com', favicon: 'https://recommendations2.com/favicon.ico', description: 'Description 2'} ], meta: meta}) }; }); @@ -73,6 +72,7 @@ describe('{{#recommendations}} helper', function () {
  • Recommendation 1 + recommendations1.com
    Recommendation 1

    Description 1

    @@ -80,31 +80,11 @@ describe('{{#recommendations}} helper', function () {
  • Recommendation 2 + recommendations2.com
    Recommendation 2

    Description 2

  • -
  • - - Recommendation 3 -
    Recommendation 3
    -

    Description 3

    -
    -
  • -
  • - - Recommendation 4 -
    Recommendation 4
    -

    Description 4

    -
    -
  • -
  • - - Recommendation 5 -
    Recommendation 5
    -

    Description 5

    -
    -
  • `); const actual = trimSpaces(response.string); diff --git a/ghost/core/test/unit/frontend/services/theme-engine/handlebars/helpers.test.js b/ghost/core/test/unit/frontend/services/theme-engine/handlebars/helpers.test.js index 42d79e9f8f..5cb6dc1d80 100644 --- a/ghost/core/test/unit/frontend/services/theme-engine/handlebars/helpers.test.js +++ b/ghost/core/test/unit/frontend/services/theme-engine/handlebars/helpers.test.js @@ -11,7 +11,7 @@ describe('Helpers', function () { 'asset', 'authors', 'body_class', 'cancel_link', 'concat', 'content', 'date', 'encode', 'excerpt', 'facebook_url', 'foreach', 'get', 'ghost_foot', 'ghost_head', 'has', 'img_url', 'is', 'link', 'link_class', 'meta_description', 'meta_title', 'navigation', 'next_post', 'page_url', 'pagination', 'plural', 'post_class', 'prev_post', 'price', 'raw', 'reading_time', 't', 'tags', 'title','total_members', 'total_paid_members', 'twitter_url', - 'url', 'comment_count', 'collection', 'recommendations' + 'url', 'comment_count', 'collection', 'recommendations', 'readable_url' ]; const experimentalHelpers = ['match', 'tiers', 'comments', 'search'];