0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Merge pull request #6971 from ErisDS/tag-visibility

This commit is contained in:
Kevin Ansfield 2016-06-15 12:51:18 +01:00
commit 48b8660970
6 changed files with 306 additions and 46 deletions

View file

@ -6,20 +6,48 @@ var hbs = require('express-hbs'),
_ = require('lodash'),
errors = require('../errors'),
i18n = require('../i18n'),
labs = require('../utils/labs'),
utils = require('./utils'),
hbsUtils = hbs.handlebars.Utils,
foreach;
foreach = function (itemType, options) {
function filterItemsByVisibility(items, options) {
var visibility = utils.parseVisibility(options);
if (!labs.isSet('internalTags') || _.includes(visibility, 'all')) {
return items;
}
function visibilityFilter(item) {
// If the item doesn't have a visibility property && options.hash.visibility wasn't set
// We return the item, else we need to be sure that this item has the property
if (!item.visibility && !options.hash.visibility || _.includes(visibility, item.visibility)) {
return item;
}
}
// We don't want to change the structure of what is returned
return _.isArray(items) ? _.filter(items, visibilityFilter) : _.pickBy(items, visibilityFilter);
}
foreach = function (items, options) {
if (!options) {
errors.logWarn(i18n.t('warnings.helpers.foreach.iteratorNeeded'));
}
if (hbsUtils.isFunction(items)) {
items = items.call(this);
}
// Exclude items which should not be visible in the theme
items = filterItemsByVisibility(items, options);
// Initial values set based on parameters sent through. If nothing sent, set to defaults
var fn = options.fn,
inverse = options.inverse,
columns = options.hash.columns,
length = _.size(itemType),
length = _.size(items),
limit = parseInt(options.hash.limit, 10) || length,
from = parseInt(options.hash.from, 10) || 1,
to = parseInt(options.hash.to, 10) || length,
@ -37,10 +65,6 @@ foreach = function (itemType, options) {
contextPath = hbsUtils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
}
if (hbsUtils.isFunction(itemType)) {
itemType = itemType.call(this);
}
if (options.data) {
data = hbs.handlebars.createFrame(options.data);
}
@ -61,9 +85,9 @@ foreach = function (itemType, options) {
}
}
output = output + fn(itemType[field], {
output = output + fn(items[field], {
data: data,
blockParams: hbsUtils.blockParams([itemType[field], field], [contextPath + field, null])
blockParams: hbsUtils.blockParams([items[field], field], [contextPath + field, null])
});
}
@ -88,8 +112,8 @@ foreach = function (itemType, options) {
});
}
if (itemType && typeof itemType === 'object') {
iterateCollection(itemType);
if (items && typeof items === 'object') {
iterateCollection(items);
}
if (length === 0) {

View file

@ -17,37 +17,51 @@ tags = function (options) {
options = options || {};
options.hash = options.hash || {};
var autolink = !(_.isString(options.hash.autolink) && options.hash.autolink === 'false'),
separator = _.isString(options.hash.separator) ? options.hash.separator : ', ',
prefix = _.isString(options.hash.prefix) ? options.hash.prefix : '',
suffix = _.isString(options.hash.suffix) ? options.hash.suffix : '',
limit = options.hash.limit ? parseInt(options.hash.limit, 10) : undefined,
from = options.hash.from ? parseInt(options.hash.from, 10) : 1,
to = options.hash.to ? parseInt(options.hash.to, 10) : undefined,
output = '';
var autolink = !(_.isString(options.hash.autolink) && options.hash.autolink === 'false'),
separator = _.isString(options.hash.separator) ? options.hash.separator : ', ',
prefix = _.isString(options.hash.prefix) ? options.hash.prefix : '',
suffix = _.isString(options.hash.suffix) ? options.hash.suffix : '',
limit = options.hash.limit ? parseInt(options.hash.limit, 10) : undefined,
from = options.hash.from ? parseInt(options.hash.from, 10) : 1,
to = options.hash.to ? parseInt(options.hash.to, 10) : undefined,
visibility = utils.parseVisibility(options),
output = '';
function createTagList(tags) {
if (labs.isSet('internalTags')) {
tags = _.filter(tags, ['visibility', 'public']);
}
return _.reduce(tags, function (tagArray, tag) {
// If labs.internalTags is set && visibility is not set to all
// Then, if tag has a visibility property, and that visibility property is also not explicitly allowed, skip tag
// or if there is no visibility property, and options.hash.visibility was set, skip tag
if (labs.isSet('internalTags') && !_.includes(visibility, 'all')) {
if (
(tag.visibility && !_.includes(visibility, tag.visibility) && !_.includes(visibility, 'all')) ||
(!!options.hash.visibility && !_.includes(visibility, 'all') && !tag.visibility)
) {
// Skip this tag
return tagArray;
}
}
if (autolink) {
return _.map(tags, function (tag) {
return utils.linkTemplate({
url: config.urlFor('tag', {tag: tag}),
text: _.escape(tag.name)
});
});
}
return _(tags).map('name').each(_.escape);
var tagOutput = autolink ? utils.linkTemplate({
url: config.urlFor('tag', {tag: tag}),
text: _.escape(tag.name)
}) : _.escape(tag.name);
tagArray.push(tagOutput);
return tagArray;
}, []);
}
if (this.tags && this.tags.length) {
output = createTagList(this.tags);
from -= 1; // From uses 1-indexed, but array uses 0-indexed.
to = to || limit + from || this.tags.length;
to = to || limit + from || output.length;
output = output.slice(from, to).join(separator);
}
output = prefix + output.slice(from, to).join(separator) + suffix;
if (output) {
output = prefix + output + suffix;
}
return new hbs.handlebars.SafeString(output);

View file

@ -18,6 +18,13 @@ utils = {
}
return null;
},
parseVisibility: function parseVisibility(options) {
if (!options.hash.visibility) {
return ['public'];
}
return _.map(options.hash.visibility.split(','), _.trim);
}
};

View file

@ -7,6 +7,7 @@ var should = require('should'),
// Stuff we are testing
handlebars = hbs.handlebars,
labs = require('../../../server/utils/labs'),
helpers = require('../../../server/helpers');
describe('{{#foreach}} helper', function () {
@ -496,5 +497,115 @@ describe('{{#foreach}} helper', function () {
shouldCompileToExpected(templateString, arrayHash, expected);
shouldCompileToExpected(templateString, objectHash, expected);
});
describe('Internal Tags', function () {
var tagArrayHash = {
tags: [
{name: 'first', visibility: 'public'},
{name: 'second', visibility: 'public'},
{name: 'third', visibility: 'internal'},
{name: 'fourth', visibility: 'public'},
{name: 'fifth'}
]
},
tagObjectHash = {
tags: {
first: {name: 'first', visibility: 'public'},
second: {name: 'second', visibility: 'public'},
third: {name: 'third', visibility: 'internal'},
fourth: {name: 'fourth', visibility: 'public'},
fifth: {name: 'fifth'}
}
};
// @TODO: remove these once internal tags are out of beta
describe('Labs flag', function () {
it('will output internal tags when the labs flag IS NOT set', function () {
sandbox.stub(labs, 'isSet').returns(false);
var templateString = '<ul>{{#foreach tags}}<li>{{@index}} {{name}}</li>{{/foreach}}</ul>',
expected = '<ul><li>0 first</li><li>1 second</li><li>2 third</li><li>3 fourth</li><li>4 fifth</li></ul>';
shouldCompileToExpected(templateString, tagObjectHash, expected);
shouldCompileToExpected(templateString, tagArrayHash, expected);
});
it('will NOT output internal tags when the labs flag IS set', function () {
sandbox.stub(labs, 'isSet').returns(true);
var templateString = '<ul>{{#foreach tags}}<li>{{@index}} {{name}}</li>{{/foreach}}</ul>',
expected = '<ul><li>0 first</li><li>1 second</li><li>2 fourth</li><li>3 fifth</li></ul>';
shouldCompileToExpected(templateString, tagObjectHash, expected);
shouldCompileToExpected(templateString, tagArrayHash, expected);
});
});
describe('Enabled', function () {
beforeEach(function () {
sandbox.stub(labs, 'isSet').returns(true);
});
it('will not output internal tags by default', function () {
var templateString = '<ul>{{#foreach tags}}<li>{{@index}} {{name}}</li>{{/foreach}}</ul>',
expected = '<ul><li>0 first</li><li>1 second</li><li>2 fourth</li><li>3 fifth</li></ul>';
shouldCompileToExpected(templateString, tagObjectHash, expected);
shouldCompileToExpected(templateString, tagArrayHash, expected);
});
it('should still correctly apply from & limit tags', function () {
var templateString = '<ul>{{#foreach tags from="2" limit="2"}}<li>{{@index}} {{name}}</li>{{/foreach}}</ul>',
expected = '<ul><li>1 second</li><li>2 fourth</li></ul>';
shouldCompileToExpected(templateString, tagObjectHash, expected);
shouldCompileToExpected(templateString, tagArrayHash, expected);
});
it('should output all tags with visibility="all"', function () {
var templateString = '<ul>{{#foreach tags visibility="all"}}<li>{{@index}} {{name}}</li>{{/foreach}}</ul>',
expected = '<ul><li>0 first</li><li>1 second</li><li>2 third</li><li>3 fourth</li><li>4 fifth</li></ul>';
shouldCompileToExpected(templateString, tagObjectHash, expected);
shouldCompileToExpected(templateString, tagArrayHash, expected);
});
it('should output all tags with visibility property set with visibility="public,internal"', function () {
var templateString = '<ul>{{#foreach tags visibility="public,internal"}}<li>{{@index}} {{name}}</li>{{/foreach}}</ul>',
expected = '<ul><li>0 first</li><li>1 second</li><li>2 third</li><li>3 fourth</li></ul>';
shouldCompileToExpected(templateString, tagObjectHash, expected);
shouldCompileToExpected(templateString, tagArrayHash, expected);
});
it('should output all tags with visibility="internal"', function () {
var templateString = '<ul>{{#foreach tags visibility="internal"}}<li>{{@index}} {{name}}</li>{{/foreach}}</ul>',
expected = '<ul><li>0 third</li></ul>';
shouldCompileToExpected(templateString, tagObjectHash, expected);
shouldCompileToExpected(templateString, tagArrayHash, expected);
});
it('should output nothing if all tags are internal', function () {
var tagArrayHash = {
tags: [
{name: 'first', visibility: 'internal'},
{name: 'second', visibility: 'internal'}
]
},
tagObjectHash = {
tags: {
first: {name: 'first', visibility: 'internal'},
second: {name: 'second', visibility: 'internal'}
}
},
templateString = '<ul>{{#foreach tags}}<li>{{@index}} {{name}}</li>{{/foreach}}</ul>',
expected = '<ul></ul>';
shouldCompileToExpected(templateString, tagObjectHash, expected);
shouldCompileToExpected(templateString, tagArrayHash, expected);
});
});
});
});
});

View file

@ -1,4 +1,4 @@
/*globals describe, afterEach, before, it*/
/*globals describe, afterEach, before, beforeEach, it*/
var should = require('should'),
sinon = require('sinon'),
@ -184,29 +184,132 @@ describe('{{tags}} helper', function () {
String(rendered).should.equal('<a href="/tag/foo-bar/">foo</a>, <a href="/tag/bar/">bar</a>, <a href="/tag/baz/">baz</a>');
});
describe('Hidden/Internal tags', function () {
describe('Internal tags', function () {
var tags = [
{name: 'foo', slug: 'foo-bar', visibility: 'public'},
{name: '#bar', slug: 'hash-bar', visibility: 'internal'},
{name: 'bar', slug: 'bar', visibility: 'public'},
{name: 'baz', slug: 'baz', visibility: 'public'},
{name: 'buzz', slug: 'buzz'}
];
// @TODO: remove these once internal tags are out of beta
it('Should output internal tags when the labs flag IS NOT set', function () {
sandbox.stub(labs, 'isSet').returns(false);
var tags = [{name: 'foo', slug: 'foo-bar', visibility: 'public'}, {name: 'bar', slug: 'bar', visibility: 'public'}],
rendered = helpers.tags.call(
describe('Labs Flag', function () {
it('will output internal tags when the labs flag IS NOT set', function () {
sandbox.stub(labs, 'isSet').returns(false);
var rendered = helpers.tags.call(
{tags: tags}
);
should.exist(rendered);
String(rendered).should.equal('<a href="/tag/foo-bar/">foo</a>, <a href="/tag/bar/">bar</a>');
String(rendered).should.equal(
'<a href="/tag/foo-bar/">foo</a>, ' +
'<a href="/tag/hash-bar/">#bar</a>, ' +
'<a href="/tag/bar/">bar</a>, ' +
'<a href="/tag/baz/">baz</a>, ' +
'<a href="/tag/buzz/">buzz</a>'
);
});
it('will NOT output internal tags when the labs flag IS set', function () {
sandbox.stub(labs, 'isSet').returns(true);
var rendered = helpers.tags.call(
{tags: tags}
);
String(rendered).should.equal(
'<a href="/tag/foo-bar/">foo</a>, ' +
'<a href="/tag/bar/">bar</a>, ' +
'<a href="/tag/baz/">baz</a>, ' +
'<a href="/tag/buzz/">buzz</a>'
);
});
});
it('Should NOT output internal tags when the labs flag IS set', function () {
sandbox.stub(labs, 'isSet').returns(true);
describe('Enabled', function () {
beforeEach(function () {
sandbox.stub(labs, 'isSet').returns(true);
});
var tags = [{name: 'foo', slug: 'foo-bar', visibility: 'public'}, {name: 'bar', slug: 'bar', visibility: 'internal'}],
rendered = helpers.tags.call(
it('will not output internal tags by default', function () {
var rendered = helpers.tags.call(
{tags: tags}
);
should.exist(rendered);
String(rendered).should.equal('<a href="/tag/foo-bar/">foo</a>');
String(rendered).should.equal(
'<a href="/tag/foo-bar/">foo</a>, ' +
'<a href="/tag/bar/">bar</a>, ' +
'<a href="/tag/baz/">baz</a>, ' +
'<a href="/tag/buzz/">buzz</a>'
);
});
it('should still correctly apply from & limit tags', function () {
var rendered = helpers.tags.call(
{tags: tags},
{hash: {from: '2', limit: '2'}}
);
String(rendered).should.equal(
'<a href="/tag/bar/">bar</a>, ' +
'<a href="/tag/baz/">baz</a>'
);
});
it('should output all tags with visibility="all"', function () {
var rendered = helpers.tags.call(
{tags: tags},
{hash: {visibility: 'all'}}
);
String(rendered).should.equal(
'<a href="/tag/foo-bar/">foo</a>, ' +
'<a href="/tag/hash-bar/">#bar</a>, ' +
'<a href="/tag/bar/">bar</a>, ' +
'<a href="/tag/baz/">baz</a>, ' +
'<a href="/tag/buzz/">buzz</a>'
);
});
it('should output all tags with visibility property set with visibility="public,internal"', function () {
var rendered = helpers.tags.call(
{tags: tags},
{hash: {visibility: 'public,internal'}}
);
should.exist(rendered);
String(rendered).should.equal(
'<a href="/tag/foo-bar/">foo</a>, ' +
'<a href="/tag/hash-bar/">#bar</a>, ' +
'<a href="/tag/bar/">bar</a>, ' +
'<a href="/tag/baz/">baz</a>'
);
});
it('Should output only internal tags with visibility="internal"', function () {
var rendered = helpers.tags.call(
{tags: tags},
{hash: {visibility: 'internal'}}
);
should.exist(rendered);
String(rendered).should.equal('<a href="/tag/hash-bar/">#bar</a>');
});
it('should output nothing if all tags are internal', function () {
var tags = [
{name: '#foo', slug: 'hash-foo-bar', visibility: 'internal'},
{name: '#bar', slug: 'hash-bar', visibility: 'internal'}
],
rendered = helpers.tags.call(
{tags: tags},
{hash: {prefix: 'stuff'}}
);
should.exist(rendered);
String(rendered).should.equal('');
});
});
});
});

View file

@ -50,6 +50,7 @@
"jsonpath": "0.2.4",
"knex": "0.10.0",
"lodash": "4.13.1",
"lodash.pickby": "4.4.0",
"moment": "2.13.0",
"moment-timezone": "0.5.4",
"morgan": "1.7.0",