0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-04-15 03:01:37 -05:00

🎨Improved image counting for the {{reading_time}} helper (#9366)

refs #9200

- We have not yet counted the images within your html, this commit counts images based on the this algorithm: https://blog.medium.com/read-time-and-you-bc2048ab620c
- Added imageCount utility, which counts images using an img-tag regex, amended from the general tag-regex found in wordCount
- Added this imageCount to the {{reading_time}} helper, adding 12 seconds to the reading time for every image
- The feature image is still counted as before
- The first image adds 12 seconds, the second 11, the third 10, and so on
- Images from the tenth onwards add 3 seconds to the reading time
This commit is contained in:
Rosco Kalis 2018-03-05 09:30:15 +01:00 committed by Katharina Irrgang
parent 1da2eec915
commit 301e1b2419
4 changed files with 65 additions and 36 deletions

View file

@ -39,11 +39,14 @@ module.exports = function reading_time(options) {// eslint-disable-line camelcas
html = this.html;
imageCount = this.feature_image ? 1 : 0;
imageCount += localUtils.imageCount(html);
wordCount = localUtils.wordCount(html);
readingTimeSeconds = wordCount / wordsPerSecond;
// add 12 seconds to reading time if feature image is present
readingTimeSeconds = imageCount ? readingTimeSeconds + 12 : readingTimeSeconds;
for (var i = 12; i > 12 - imageCount; i -= 1) {
// add 12 seconds for the first image, 11 for the second, etc. limiting at 3
readingTimeSeconds += Math.max(i, 3);
}
readingTimeMinutes = Math.round(readingTimeSeconds / 60);

View file

@ -23,6 +23,16 @@ module.exports.wordCount = function wordCount(html) {
return html.split(' ').length;
};
/**
* Image count Utility
* @param {string} html
* @returns {integer} image count
* @description Takes an HTML string and returns the number of images
**/
module.exports.imageCount = function wordCount(html) {
return (html.match(/<img(.|\n)*?>/g) || []).length;
};
module.exports.findKey = function findKey(key /* ...objects... */) {
let objects = Array.prototype.slice.call(arguments, 1);

View file

@ -3,10 +3,27 @@ var should = require('should'), // jshint ignore:line
// Stuff we are testing
helpers = require('../../../server/helpers');
var articleHtml =
'<div class="kg-card-markdown"><p>Ghost has a number of different user roles for your team</p>' +
'<h3 id="authors">Authors</h3><p>The base user level in Ghost is an author. Authors can write posts,' +
' edit their own posts, and publish their own posts. Authors are <strong>trusted</strong> users. If you ' +
'don\'t trust users to be allowed to publish their own posts, you shouldn\'t invite them to Ghost admin.</p>' +
'<h3 id="editors">Editors</h3><p>Editors are the 2nd user level in Ghost. Editors can do everything that an' +
' Author can do, but they can also edit and publish the posts of others - as well as their own. Editors can also invite new' +
' authors to the site.</p><h3 id="administrators">Administrators</h3><p>The top user level in Ghost is Administrator.' +
' Again, administrators can do everything that Authors and Editors can do, but they can also edit all site settings ' +
'and data, not just content. Additionally, administrators have full access to invite, manage or remove any other' +
' user of the site.</p><h3 id="theowner">The Owner</h3><p>There is only ever one owner of a Ghost site. ' +
'The owner is a special user which has all the same permissions as an Administrator, but with two exceptions: ' +
'The Owner can never be deleted. And in some circumstances the owner will have access to additional special settings ' +
'if applicable — for example, billing details, if using Ghost(Pro).</p><hr><p>It\'s a good idea to ask all of your' +
' users to fill out their user profiles, including bio and social links. These will populate rich structured data ' +
'for posts and generally create more opportunities for themes to fully populate their design.</p></div>';
describe('{{reading_time}} helper', function () {
it('[success] renders reading time for less than one minute text correctly', function () {
var data = {
html: '<div class="kg-card-markdown"><p>This is a text example! Count me in ;)</p></div>',
html: articleHtml,
title: 'Test',
slug: 'slug'
},
@ -17,23 +34,11 @@ describe('{{reading_time}} helper', function () {
it('[success] renders reading time for more than one minute text correctly', function () {
var data = {
html: '<div class="kg-card-markdown"><p>Ghost has a number of different user roles for your team</p>' +
'<h3 id="authors">Authors</h3><p>The base user level in Ghost is an author. Authors can write posts,' +
' edit their own posts, and publish their own posts. Authors are <strong>trusted</strong> users. If you ' +
'don\'t trust users to be allowed to publish their own posts, you shouldn\'t invite them to Ghost admin.</p>' +
'<h3 id="editors">Editors</h3><p>Editors are the 2nd user level in Ghost. Editors can do everything that an' +
' Author can do, but they can also edit and publish the posts of others - as well as their own. Editors can also invite new' + ' authors to the site.</p><h3 id="administrators">Administrators</h3><p>The top user level in Ghost is Administrator.' +
' Again, administrators can do everything that Authors and Editors can do, but they can also edit all site settings ' +
'and data, not just content. Additionally, administrators have full access to invite, manage or remove any other' +
' user of the site.</p><h3 id="theowner">The Owner</h3><p>There is only ever one owner of a Ghost site. ' +
'The owner is a special user which has all the same permissions as an Administrator, but with two exceptions: ' +
'The Owner can never be deleted. And in some circumstances the owner will have access to additional special settings ' +
'if applicable — for example, billing details, if using Ghost(Pro).</p><hr><p>It\'s a good idea to ask all of your' +
' users to fill out their user profiles, including bio and social links. These will populate rich structured data ' +
'for posts and generally create more opportunities for themes to fully populate their design.</p></div>',
html: articleHtml +
'This needed about twenty-five more words before passing the one minute reading time, ' +
'since the word count was 250, and the average speed is 275.',
title: 'Test',
slug: 'slug',
feature_image: '/content/images/someimage.jpg'
slug: 'slug'
},
result = helpers.reading_time.call(data);
@ -42,26 +47,28 @@ describe('{{reading_time}} helper', function () {
it('[success] adds time for feature image', function () {
var data = {
html: '<div class="kg-card-markdown"><p>Ghost has a number of different user roles for your team</p>' +
'<h3 id="authors">Authors</h3><p>The base user level in Ghost is an author. Authors can write posts,' +
' edit their own posts, and publish their own posts. Authors are <strong>trusted</strong> users. If you ' +
'don\'t trust users to be allowed to publish their own posts, you shouldn\'t invite them to Ghost admin.</p>' +
'<h3 id="editors">Editors</h3><p>Editors are the 2nd user level in Ghost. Editors can do everything that an' +
' Author can do, but they can also edit and publish the posts of others - as well as their own. Editors can also invite new' + ' authors to the site.</p><h3 id="administrators">Administrators</h3><p>The top user level in Ghost is Administrator.' +
' Again, administrators can do everything that Authors and Editors can do, but they can also edit all site settings ' +
'and data, not just content. Additionally, administrators have full access to invite, manage or remove any other' +
' user of the site.</p><h3 id="theowner">The Owner</h3><p>There is only ever one owner of a Ghost site. ' +
'The owner is a special user which has all the same permissions as an Administrator, but with two exceptions: ' +
'The Owner can never be deleted. And in some circumstances the owner will have access to additional special settings ' +
'if applicable — for example, billing details, if using Ghost(Pro).</p><hr><p>It\'s a good idea to ask all of your' +
' users to fill out their user profiles, including bio and social links. These will populate rich structured data ',
title: 'Test',
slug: 'slug',
feature_image: '/content/images/someimage.jpg'
html: articleHtml,
title: 'Test',
slug: 'slug',
feature_image: '/content/images/someimage.jpg'
},
result = helpers.reading_time.call(data);
// The reading time for this HTML snippet would be 63 seconds without the image
// The reading time for this HTML snippet would be 55 seconds without the image
// Adding the 12 additional seconds for the image results in a reading time > 1 minute
String(result).should.equal('1 min read');
});
it('[success] adds time for inline images', function () {
var data = {
html: articleHtml +
'<img src="test.png">',
title: 'Test',
slug: 'slug'
},
result = helpers.reading_time.call(data);
// The reading time for this HTML snippet would be 55 seconds without the image
// Adding the 12 additional seconds for the image results in a reading time > 1 minute
String(result).should.equal('1 min read');
});

View file

@ -31,4 +31,13 @@ describe('Helpers Utils', function () {
result.should.equal(8);
});
});
describe('Image Count', function () {
it('[success] can count images', function () {
var html = '<div class="kg-card-markdown"><p>This is a <img src="hello.png"> text example! Count me in ;)</p><img src="hello.png"></div>',
result = helperUtils.imageCount(html);
result.should.equal(2);
});
});
});