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:
commit
48b8660970
6 changed files with 306 additions and 46 deletions
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -18,6 +18,13 @@ utils = {
|
|||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
parseVisibility: function parseVisibility(options) {
|
||||
if (!options.hash.visibility) {
|
||||
return ['public'];
|
||||
}
|
||||
|
||||
return _.map(options.hash.visibility.split(','), _.trim);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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('');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Reference in a new issue