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
This commit is contained in:
Sag 2023-10-10 15:56:59 -03:00 committed by GitHub
parent 0ffe4dd144
commit 3cc60bc7ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 76 additions and 32 deletions

View File

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

View File

@ -4,6 +4,7 @@
<li class="recommendation"> <li class="recommendation">
<a href="{{rec.url}}" data-recommendation="{{rec.id}}" target="_blank" rel="noopener"> <a href="{{rec.url}}" data-recommendation="{{rec.id}}" target="_blank" rel="noopener">
<img class="recommendation-favicon" src="{{rec.favicon}}" alt="{{rec.title}}"> <img class="recommendation-favicon" src="{{rec.favicon}}" alt="{{rec.title}}">
<span class="recommendation-url">{{readable_url rec.url}}</span>
<h5 class="recommendation-title">{{rec.title}}</h5> <h5 class="recommendation-title">{{rec.title}}</h5>
<p class="recommendation-description">{{rec.description}}</p> <p class="recommendation-description">{{rec.description}}</p>
</a> </a>

View File

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

View File

@ -10,6 +10,7 @@ const proxy = require('../../../../core/frontend/services/proxy');
const recommendations = require('../../../../core/frontend/helpers/recommendations'); const recommendations = require('../../../../core/frontend/helpers/recommendations');
const foreach = require('../../../../core/frontend/helpers/foreach'); const foreach = require('../../../../core/frontend/helpers/foreach');
const readable_url = require('../../../../core/frontend/helpers/readable_url');
const {settingsCache} = proxy; const {settingsCache} = proxy;
function trimSpaces(string) { function trimSpaces(string) {
@ -30,6 +31,7 @@ describe('{{#recommendations}} helper', function () {
// The recommendation template expects this helper // The recommendation template expects this helper
hbs.registerHelper('foreach', foreach); hbs.registerHelper('foreach', foreach);
hbs.registerHelper('readable_url', readable_url);
// Stub settings cache // Stub settings cache
sinon.stub(settingsCache, 'get'); sinon.stub(settingsCache, 'get');
@ -42,10 +44,7 @@ describe('{{#recommendations}} helper', function () {
return { return {
browse: sinon.stub().resolves({recommendations: [ browse: sinon.stub().resolves({recommendations: [
{id: '1', title: 'Recommendation 1', url: 'https://recommendations1.com', favicon: 'https://recommendations1.com/favicon.ico', description: 'Description 1'}, {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: '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'}
], meta: meta}) ], meta: meta})
}; };
}); });
@ -73,6 +72,7 @@ describe('{{#recommendations}} helper', function () {
<li class="recommendation"> <li class="recommendation">
<a href="https://recommendations1.com" data-recommendation="1" target="_blank" rel="noopener"> <a href="https://recommendations1.com" data-recommendation="1" target="_blank" rel="noopener">
<img class="recommendation-favicon" src="https://recommendations1.com/favicon.ico" alt="Recommendation 1"> <img class="recommendation-favicon" src="https://recommendations1.com/favicon.ico" alt="Recommendation 1">
<span class="recommendation-url">recommendations1.com</span>
<h5 class="recommendation-title">Recommendation 1</h5> <h5 class="recommendation-title">Recommendation 1</h5>
<p class="recommendation-description">Description 1</p> <p class="recommendation-description">Description 1</p>
</a> </a>
@ -80,31 +80,11 @@ describe('{{#recommendations}} helper', function () {
<li class="recommendation"> <li class="recommendation">
<a href="https://recommendations2.com" data-recommendation="2" target="_blank" rel="noopener"> <a href="https://recommendations2.com" data-recommendation="2" target="_blank" rel="noopener">
<img class="recommendation-favicon" src="https://recommendations2.com/favicon.ico" alt="Recommendation 2"> <img class="recommendation-favicon" src="https://recommendations2.com/favicon.ico" alt="Recommendation 2">
<span class="recommendation-url">recommendations2.com</span>
<h5 class="recommendation-title">Recommendation 2</h5> <h5 class="recommendation-title">Recommendation 2</h5>
<p class="recommendation-description">Description 2</p> <p class="recommendation-description">Description 2</p>
</a> </a>
</li> </li>
<li class="recommendation">
<a href="https://recommendations3.com" data-recommendation="3" target="_blank" rel="noopener">
<img class="recommendation-favicon" src="https://recommendations3.com/favicon.ico" alt="Recommendation 3">
<h5 class="recommendation-title">Recommendation 3</h5>
<p class="recommendation-description">Description 3</p>
</a>
</li>
<li class="recommendation">
<a href="https://recommendations4.com" data-recommendation="4" target="_blank" rel="noopener">
<img class="recommendation-favicon" src="https://recommendations4.com/favicon.ico" alt="Recommendation 4">
<h5 class="recommendation-title">Recommendation 4</h5>
<p class="recommendation-description">Description 4</p>
</a>
</li>
<li class="recommendation">
<a href="https://recommendations5.com" data-recommendation="5" target="_blank" rel="noopener">
<img class="recommendation-favicon" src="https://recommendations5.com/favicon.ico" alt="Recommendation 5">
<h5 class="recommendation-title">Recommendation 5</h5>
<p class="recommendation-description">Description 5</p>
</a>
</li>
</ul> </ul>
`); `);
const actual = trimSpaces(response.string); const actual = trimSpaces(response.string);

View File

@ -11,7 +11,7 @@ describe('Helpers', function () {
'asset', 'authors', 'body_class', 'cancel_link', 'concat', 'content', 'date', 'encode', 'excerpt', 'facebook_url', 'foreach', 'get', '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', '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', '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']; const experimentalHelpers = ['match', 'tiers', 'comments', 'search'];