mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-10 11:24:39 +03:00
✨ Member Counter template helpers (#15013)
ref https://github.com/TryGhost/Team/issues/1667 Introducing 2 new helper handlebars tags, `{{total_members}}` and `{{total_paid_members}}` ideal for Member Sites who want to display these metrics to incentivise users to upgrade.
This commit is contained in:
parent
b6818b77bd
commit
a0c8db46fb
17
core/frontend/helpers/total_members.js
Normal file
17
core/frontend/helpers/total_members.js
Normal file
@ -0,0 +1,17 @@
|
||||
// # Total Members Helper
|
||||
// Usage: `{{total_members}}`
|
||||
|
||||
const {SafeString} = require('../services/handlebars');
|
||||
const {memberCountRounding, getMemberStats} = require('../utils/member-count');
|
||||
|
||||
module.exports = async function total_members () { //eslint-disable-line
|
||||
if (this.total) {
|
||||
return new SafeString(memberCountRounding(this.total));
|
||||
} else {
|
||||
let memberStats = await getMemberStats();
|
||||
const {total} = memberStats;
|
||||
return new SafeString(total > 0 ? memberCountRounding(total) : 0);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.async = true;
|
16
core/frontend/helpers/total_paid_members.js
Normal file
16
core/frontend/helpers/total_paid_members.js
Normal file
@ -0,0 +1,16 @@
|
||||
// {{total_paid_members}} helper
|
||||
|
||||
const {SafeString} = require('../services/handlebars');
|
||||
const {memberCountRounding, getMemberStats} = require('../utils/member-count');
|
||||
|
||||
module.exports = async function total_paid_members () { //eslint-disable-line
|
||||
if (this.paid) {
|
||||
return new SafeString(memberCountRounding(this.paid));
|
||||
} else {
|
||||
let memberStats = await getMemberStats();
|
||||
const {paid} = memberStats;
|
||||
return new SafeString(paid > 0 ? memberCountRounding(paid) : 0);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.async = true;
|
50
core/frontend/utils/member-count.js
Normal file
50
core/frontend/utils/member-count.js
Normal file
@ -0,0 +1,50 @@
|
||||
const humanNumber = require('human-number');
|
||||
const {api} = require('../services/proxy');
|
||||
|
||||
async function getMemberStats() {
|
||||
let memberStats = this.data || await api.stats.memberCountHistory.query();
|
||||
const {free, paid, comped} = memberStats.meta.totals;
|
||||
let total = free + paid + comped;
|
||||
return {free, paid, comped, total};
|
||||
}
|
||||
|
||||
const numberWithCommas = (n) => {
|
||||
return n.toLocaleString();
|
||||
};
|
||||
|
||||
const rounding = (n, roundTo) => {
|
||||
return Math.floor(n / roundTo) * roundTo;
|
||||
};
|
||||
|
||||
// Rounding https://github.com/TryGhost/Team/issues/1667
|
||||
const memberCountRounding = (memberCount) => {
|
||||
if (memberCount <= 50) {
|
||||
return memberCount;
|
||||
}
|
||||
|
||||
if (memberCount > 50 && memberCount <= 100) {
|
||||
return `${numberWithCommas(rounding(memberCount, 10))}+`;
|
||||
}
|
||||
|
||||
if (memberCount > 100 && memberCount <= 1000) {
|
||||
return `${numberWithCommas(rounding(memberCount, 50))}+`;
|
||||
}
|
||||
|
||||
if (memberCount > 1000 && memberCount <= 10000) {
|
||||
return `${numberWithCommas(rounding(memberCount, 100))}+`;
|
||||
}
|
||||
|
||||
if (memberCount > 10000 && memberCount <= 100000) {
|
||||
return `${numberWithCommas(rounding(memberCount, 1000))}+`;
|
||||
}
|
||||
|
||||
if (memberCount > 100000 && memberCount <= 1000000) {
|
||||
return `${humanNumber(rounding(memberCount, 10000)).toLowerCase()}+`;
|
||||
}
|
||||
|
||||
if (memberCount > 1000000) {
|
||||
return `${humanNumber(rounding(memberCount, 100000)).toLowerCase()}+`;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {memberCountRounding, getMemberStats};
|
@ -146,6 +146,7 @@
|
||||
"got": "9.6.0",
|
||||
"gscan": "4.31.2",
|
||||
"html-to-text": "8.2.0",
|
||||
"human-number": "2.0.0",
|
||||
"image-size": "1.0.2",
|
||||
"intl": "1.2.5",
|
||||
"intl-messageformat": "5.4.3",
|
||||
|
28
test/e2e-frontend/member_stats.test.js
Normal file
28
test/e2e-frontend/member_stats.test.js
Normal file
@ -0,0 +1,28 @@
|
||||
const should = require('should');
|
||||
const {memberCountRounding, getMemberStats} = require('../../core/frontend/utils/member-count.js');
|
||||
|
||||
describe('Front-end member stats ', function () {
|
||||
it('should return free', async function () {
|
||||
const members = await getMemberStats();
|
||||
const {free} = members;
|
||||
should.exist(free);
|
||||
});
|
||||
|
||||
it('should return paid', async function () {
|
||||
const members = await getMemberStats();
|
||||
const {paid} = members;
|
||||
should.exist(paid);
|
||||
});
|
||||
|
||||
it('should return comped', async function () {
|
||||
const members = await getMemberStats();
|
||||
const {comped} = members;
|
||||
should.exist(comped);
|
||||
});
|
||||
|
||||
it('should return total', async function () {
|
||||
const members = await getMemberStats();
|
||||
const {total} = members;
|
||||
should.exist(total);
|
||||
});
|
||||
});
|
10
test/unit/frontend/helpers/total_members.test.js
Normal file
10
test/unit/frontend/helpers/total_members.test.js
Normal file
@ -0,0 +1,10 @@
|
||||
const should = require('should');
|
||||
|
||||
const total_members = require('../../../../core/frontend/helpers/total_members');
|
||||
|
||||
describe('{{total_members}} helper', function () {
|
||||
it('can render total members', async function () {
|
||||
const rendered = await total_members.call({total: 50000});
|
||||
should.equal(rendered.string, '50,000+');
|
||||
});
|
||||
});
|
10
test/unit/frontend/helpers/total_paid_members.test.js
Normal file
10
test/unit/frontend/helpers/total_paid_members.test.js
Normal file
@ -0,0 +1,10 @@
|
||||
const should = require('should');
|
||||
|
||||
const total_paid_members = require('../../../../core/frontend/helpers/total_paid_members');
|
||||
|
||||
describe('{{total_paid_members}} helper', function () {
|
||||
it('can render total paid members', async function () {
|
||||
const rendered = await total_paid_members.call({paid: 3000});
|
||||
should.equal(rendered.string, '3,000+');
|
||||
});
|
||||
});
|
@ -10,7 +10,7 @@ describe('Helpers', function () {
|
||||
const ghostHelpers = [
|
||||
'asset', 'authors', 'body_class', 'cancel_link', 'concat', 'content', 'date', 'encode', 'excerpt', 'facebook_url', 'foreach', 'get',
|
||||
'ghost_foot', 'ghost_head', 'has', 'img_url', 'is', 'lang', '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', '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'
|
||||
];
|
||||
const experimentalHelpers = ['match', 'tiers', 'comments'];
|
||||
|
50
test/unit/frontend/utils/member-count.test.js
Normal file
50
test/unit/frontend/utils/member-count.test.js
Normal file
@ -0,0 +1,50 @@
|
||||
const should = require('should');
|
||||
const {memberCountRounding, getMemberStats} = require('../../../../core/frontend/utils/member-count');
|
||||
|
||||
const getMemberStatsMock = [
|
||||
{
|
||||
members: 30,
|
||||
expected: '30'
|
||||
},
|
||||
{
|
||||
members: 55,
|
||||
expected: '50+'
|
||||
},
|
||||
{
|
||||
members: 580,
|
||||
expected: '550+'
|
||||
},
|
||||
{
|
||||
members: 5555,
|
||||
expected: '5,500+'
|
||||
},
|
||||
{
|
||||
members: 55555,
|
||||
expected: '55,000+'
|
||||
},
|
||||
{
|
||||
members: 555555,
|
||||
expected: '550k+'
|
||||
},
|
||||
{
|
||||
members: 5555555,
|
||||
expected: '5.5m+'
|
||||
}
|
||||
];
|
||||
|
||||
describe('Member Count', function () {
|
||||
it('should return total members', async function () {
|
||||
const meta = {data: {
|
||||
meta: {totals: {paid: 1000, free: 500, comped: 500}}
|
||||
}};
|
||||
const members = await getMemberStats.call(meta);
|
||||
return should.equal(members.total, 2000);
|
||||
});
|
||||
|
||||
it('should return rounded numbers in correct format', function () {
|
||||
getMemberStatsMock.map((mock) => {
|
||||
const result = memberCountRounding(mock.members);
|
||||
return should.equal(result, mock.expected);
|
||||
});
|
||||
});
|
||||
});
|
12
yarn.lock
12
yarn.lock
@ -6574,6 +6574,13 @@ human-interval@^2.0.0:
|
||||
dependencies:
|
||||
numbered "^1.1.0"
|
||||
|
||||
human-number@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/human-number/-/human-number-2.0.0.tgz#ffdfa7954c40d32c02aa0ebb3b0f54f8fc4ed410"
|
||||
integrity sha512-hMb3DuF2aMTaFy795TU65AfQnZDd+UF6q8f29m3t6n648I3VCJyYXd1pJnnJsOJRYi7yBvYG29RdkrS59GwSEw==
|
||||
dependencies:
|
||||
round-to "~5.0.0"
|
||||
|
||||
humanize-ms@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
|
||||
@ -10626,6 +10633,11 @@ rimraf@~2.4.0:
|
||||
dependencies:
|
||||
glob "^6.0.1"
|
||||
|
||||
round-to@~5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/round-to/-/round-to-5.0.0.tgz#a66292701a93b194f630a0d57f04c08821b6eeed"
|
||||
integrity sha512-i4+Ntwmo5kY7UWWFSDEVN3RjT2PX1FqkZ9iCcAO3sKML3Ady9NgsjM/HLdYKUAnrxK4IlSvXzpBMDvMHZQALRQ==
|
||||
|
||||
rss@1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/rss/-/rss-1.2.2.tgz#50a1698876138133a74f9a05d2bdc8db8d27a921"
|
||||
|
Loading…
Reference in New Issue
Block a user