Support named logos and omit logos by default (#1092)
- Except for social badges, omit logos by default (#983) - Omit the logo from a social badge using the query string: `?logo=` (#983) - Opt in to named logos using the query string: `?logo=appveyor` - Provide custom logo data as before: `?logo=data:image/png;base64,...` - Rewrite badge data functions, with unit tests Unit tests are covering the new code very well, though the underlying functionality (logos) is untested. Close #983
This commit is contained in:
99
lib/badge-data.js
Normal file
99
lib/badge-data.js
Normal file
@@ -0,0 +1,99 @@
|
||||
const logos = require('./load-logos')();
|
||||
|
||||
function toArray(val) {
|
||||
if (val === undefined) {
|
||||
return [];
|
||||
} else if (Object(val) instanceof Array) {
|
||||
return val;
|
||||
} else {
|
||||
return [val];
|
||||
}
|
||||
}
|
||||
|
||||
function isDataUri(s) {
|
||||
return s !== undefined && /^(data:)([^;]+);([^,]+),(.+)$/.test(s);
|
||||
}
|
||||
|
||||
function hasPrefix(s, prefix) {
|
||||
return s !== undefined && s.slice(0, prefix.length) === prefix;
|
||||
}
|
||||
|
||||
function prependPrefix(s, prefix) {
|
||||
if (s === undefined) {
|
||||
return undefined;
|
||||
} else if (hasPrefix(s, prefix)) {
|
||||
return s;
|
||||
} else {
|
||||
return prefix + s;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidStyle(style) {
|
||||
const validStyles = ['default', 'plastic', 'flat', 'flat-square', 'social'];
|
||||
return style ? validStyles.indexOf(style) >= 0 : false;
|
||||
}
|
||||
|
||||
function isSixHex (s){
|
||||
return s !== undefined && /^[0-9a-fA-F]{6}$/.test(s);
|
||||
}
|
||||
|
||||
function makeColor(color) {
|
||||
if (isSixHex(color)) {
|
||||
return '#' + color;
|
||||
} else {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
function makeLabel(defaultLabel, overrides) {
|
||||
return overrides.label || defaultLabel;
|
||||
}
|
||||
|
||||
function makeLogo(defaultNamedLogo, overrides) {
|
||||
const maybeDataUri = prependPrefix(overrides.logo, 'data:');
|
||||
const maybeNamedLogo = overrides.logo === undefined ? defaultNamedLogo : overrides.logo;
|
||||
|
||||
if (isDataUri(maybeDataUri)) {
|
||||
return maybeDataUri;
|
||||
} else {
|
||||
return logos[maybeNamedLogo];
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the initial badge data. Pass the URL query parameters, which
|
||||
// override the default label.
|
||||
//
|
||||
// The following parameters are supported:
|
||||
//
|
||||
// - label
|
||||
// - style
|
||||
// - logo
|
||||
// - logoWidth
|
||||
// - link
|
||||
// - colorA
|
||||
// - colorB
|
||||
// - maxAge
|
||||
//
|
||||
// Note: maxAge is handled by cache(), not this function.
|
||||
function makeBadgeData(defaultLabel, overrides) {
|
||||
return {
|
||||
text: [makeLabel(defaultLabel, overrides), 'n/a'],
|
||||
colorscheme: 'lightgrey',
|
||||
template: isValidStyle(overrides.style) ? overrides.style : 'default',
|
||||
logo: makeLogo(undefined, overrides),
|
||||
logoWidth: +overrides.logoWidth,
|
||||
links: toArray(overrides.link),
|
||||
colorA: makeColor(overrides.colorA),
|
||||
colorB: makeColor(overrides.colorB),
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hasPrefix,
|
||||
isDataUri,
|
||||
isValidStyle,
|
||||
isSixHex,
|
||||
makeLabel,
|
||||
makeLogo,
|
||||
makeBadgeData,
|
||||
};
|
||||
84
lib/badge-data.spec.js
Normal file
84
lib/badge-data.spec.js
Normal file
@@ -0,0 +1,84 @@
|
||||
const assert = require('assert');
|
||||
const {
|
||||
isDataUri,
|
||||
hasPrefix,
|
||||
isValidStyle,
|
||||
isSixHex,
|
||||
makeLabel,
|
||||
makeLogo,
|
||||
makeBadgeData,
|
||||
} = require('./badge-data');
|
||||
|
||||
describe('Badge data helpers', function() {
|
||||
it('should detect prefixes', function() {
|
||||
assert.equal(hasPrefix('data:image/svg+xml;base64,PHN2ZyB4bWxu', 'data:'), true);
|
||||
assert.equal(hasPrefix('data:foobar', 'data:'), true);
|
||||
assert.equal(hasPrefix('foobar', 'data:'), false);
|
||||
});
|
||||
|
||||
it('should detect valid image data URIs', function() {
|
||||
assert.equal(isDataUri('data:image/svg+xml;base64,PHN2ZyB4bWxu'), true);
|
||||
assert.equal(isDataUri('data:foobar'), false);
|
||||
assert.equal(isDataUri('foobar'), false);
|
||||
});
|
||||
|
||||
it('should detect valid styles', function() {
|
||||
assert.equal(isValidStyle('flat'), true);
|
||||
assert.equal(isValidStyle('flattery'), false);
|
||||
assert.equal(isValidStyle(''), false);
|
||||
assert.equal(isValidStyle(undefined), false);
|
||||
});
|
||||
|
||||
it('should detect valid six-hex strings', function() {
|
||||
assert.equal(isSixHex('f00bae'), true);
|
||||
assert.equal(isSixHex('f00bar'), false);
|
||||
assert.equal(isSixHex(''), false);
|
||||
assert.equal(isSixHex(undefined), false);
|
||||
});
|
||||
|
||||
it('should make labels', function() {
|
||||
assert.equal(makeLabel('my badge', {}), 'my badge');
|
||||
assert.equal(makeLabel('my badge', { label: 'no, my badge' }), 'no, my badge');
|
||||
});
|
||||
|
||||
it('should make logos', function() {
|
||||
assert.equal(
|
||||
makeLogo('gratipay', { logo: 'image/svg+xml;base64,PHN2ZyB4bWxu' }),
|
||||
'data:image/svg+xml;base64,PHN2ZyB4bWxu');
|
||||
assert.equal(
|
||||
makeLogo('gratipay', { logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu' }),
|
||||
'data:image/svg+xml;base64,PHN2ZyB4bWxu');
|
||||
assert.equal(
|
||||
makeLogo('gratipay', { logo: '' }),
|
||||
undefined);
|
||||
assert.equal(
|
||||
makeLogo(undefined, {}),
|
||||
undefined);
|
||||
assert.ok(isDataUri(makeLogo('gratipay', {})));
|
||||
});
|
||||
|
||||
it('should make badge data', function() {
|
||||
const overrides = {
|
||||
label: 'no, my badge',
|
||||
style: 'flat-square',
|
||||
logo: 'image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
logoWidth: '25',
|
||||
link: 'https://example.com/',
|
||||
colorA: 'blue',
|
||||
colorB: 'f00bae',
|
||||
};
|
||||
|
||||
const expected = {
|
||||
text: ['no, my badge', 'n/a'],
|
||||
colorscheme: 'lightgrey',
|
||||
template: 'flat-square',
|
||||
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
|
||||
logoWidth: 25,
|
||||
links: ['https://example.com/'],
|
||||
colorA: 'blue',
|
||||
colorB: '#f00bae',
|
||||
};
|
||||
|
||||
assert.deepEqual(makeBadgeData('my badge', overrides), expected);
|
||||
});
|
||||
});
|
||||
101
server.js
101
server.js
@@ -26,7 +26,6 @@ var log = require('./lib/log.js');
|
||||
var LruCache = require('./lib/lru-cache.js');
|
||||
var badge = require('./lib/badge.js');
|
||||
var svg2img = require('./lib/svg-to-img.js');
|
||||
var loadLogos = require('./lib/load-logos.js');
|
||||
var githubAuth = require('./lib/github-auth.js');
|
||||
var querystring = require('querystring');
|
||||
var prettyBytes = require('pretty-bytes');
|
||||
@@ -65,14 +64,17 @@ const {
|
||||
incrMonthlyAnalytics,
|
||||
getAnalytics
|
||||
} = require('./lib/analytics');
|
||||
const {
|
||||
isValidStyle,
|
||||
isSixHex: sixHex,
|
||||
makeLabel: getLabel,
|
||||
makeLogo: getLogo,
|
||||
makeBadgeData: getBadgeData,
|
||||
} = require('./lib/badge-data');
|
||||
|
||||
var semver = require('semver');
|
||||
var serverStartTime = new Date((new Date()).toGMTString());
|
||||
|
||||
var validTemplates = ['default', 'plastic', 'flat', 'flat-square', 'social'];
|
||||
var darkBackgroundTemplates = ['default', 'flat', 'flat-square'];
|
||||
var logos = loadLogos();
|
||||
|
||||
analyticsAutoLoad();
|
||||
camp.ajax.on('analytics/v1', function(json, end) { end(getAnalytics()); });
|
||||
|
||||
@@ -762,7 +764,6 @@ cache(function(data, match, sendBadge, request) {
|
||||
apiUrl += '/branch/' + branch;
|
||||
}
|
||||
var badgeData = getBadgeData('build', data);
|
||||
badgeData.logo = badgeData.logo || logos['appveyor'];
|
||||
request(apiUrl, { headers: { 'Accept': 'application/json' } }, function(err, res, buffer) {
|
||||
if (err != null) {
|
||||
badgeData.text[1] = 'inaccessible';
|
||||
@@ -1095,7 +1096,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
var apiUrl = 'https://gratipay.com/' + user + '/public.json';
|
||||
var badgeData = getBadgeData('receives', data);
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.gratipay;
|
||||
badgeData.logo = getLogo('gratipay', data);
|
||||
}
|
||||
request(apiUrl, function dealWithData(err, res, buffer) {
|
||||
if (err != null) {
|
||||
@@ -3230,7 +3231,6 @@ cache(function (data, match, sendBadge, request) {
|
||||
}
|
||||
try {
|
||||
badgeData.colorscheme = 'brightgreen';
|
||||
badgeData.logo = logos.sourcegraph;
|
||||
var data = JSON.parse(buffer);
|
||||
badgeData.text[1] = data.value;
|
||||
sendBadge(format, badgeData);
|
||||
@@ -3250,7 +3250,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
var apiUrl = githubApiUrl + '/repos/' + user + '/' + repo + '/tags';
|
||||
var badgeData = getBadgeData('tag', data);
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.github;
|
||||
badgeData.logo = getLogo('github', data);
|
||||
}
|
||||
githubAuth.request(request, apiUrl, {}, function(err, res, buffer) {
|
||||
if (err != null) {
|
||||
@@ -3283,7 +3283,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
var apiUrl = githubApiUrl + '/repos/' + user + '/' + repo + '/contributors?page=1&per_page=1&anon=' + (!!isAnon);
|
||||
var badgeData = getBadgeData('contributors', data);
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.github;
|
||||
badgeData.logo = getLogo('github', data);
|
||||
}
|
||||
githubAuth.request(request, apiUrl, {}, function(err, res, buffer) {
|
||||
if (err != null) {
|
||||
@@ -3321,7 +3321,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
apiUrl = apiUrl + '/latest';
|
||||
}
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.github;
|
||||
badgeData.logo = getLogo('github', data);
|
||||
}
|
||||
githubAuth.request(request, apiUrl, {}, function(err, res, buffer) {
|
||||
if (err != null) {
|
||||
@@ -3357,7 +3357,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
var apiUrl = githubApiUrl + '/repos/' + user + '/' + repo + '/compare/' + version + '...master';
|
||||
var badgeData = getBadgeData('commits since ' + version, data);
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.github;
|
||||
badgeData.logo = getLogo('github', data);
|
||||
}
|
||||
githubAuth.request(request, apiUrl, {}, function(err, res, buffer) {
|
||||
if (err != null) {
|
||||
@@ -3401,7 +3401,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
}
|
||||
var badgeData = getBadgeData('downloads', data);
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.github;
|
||||
badgeData.logo = getLogo('github', data);
|
||||
}
|
||||
githubAuth.request(request, apiUrl, {}, function(err, res, buffer) {
|
||||
if (err != null) {
|
||||
@@ -3474,7 +3474,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
var targetText = isPR? 'pull requests': 'issues';
|
||||
var badgeData = getBadgeData(leftClassText + labelText + targetText, data);
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.github;
|
||||
badgeData.logo = getLogo('github', data);
|
||||
}
|
||||
githubAuth.request(request, apiUrl, query, function(err, res, buffer) {
|
||||
if (err != null) {
|
||||
@@ -3504,7 +3504,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
var apiUrl = githubApiUrl + '/repos/' + user + '/' + repo;
|
||||
var badgeData = getBadgeData('forks', data);
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.github;
|
||||
badgeData.logo = getLogo('github', data);
|
||||
badgeData.links = [
|
||||
'https://github.com/' + user + '/' + repo + '/fork',
|
||||
'https://github.com/' + user + '/' + repo + '/network',
|
||||
@@ -3539,7 +3539,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
var apiUrl = githubApiUrl + '/repos/' + user + '/' + repo;
|
||||
var badgeData = getBadgeData('stars', data);
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.github;
|
||||
badgeData.logo = getLogo('github', data);
|
||||
badgeData.links = [
|
||||
'https://github.com/' + user + '/' + repo,
|
||||
'https://github.com/' + user + '/' + repo + '/stargazers',
|
||||
@@ -3572,7 +3572,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
var apiUrl = githubApiUrl + '/repos/' + user + '/' + repo;
|
||||
var badgeData = getBadgeData('watchers', data);
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.github;
|
||||
badgeData.logo = getLogo('github', data);
|
||||
badgeData.links = [
|
||||
'https://github.com/' + user + '/' + repo,
|
||||
'https://github.com/' + user + '/' + repo + '/watchers',
|
||||
@@ -3604,7 +3604,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
var apiUrl = githubApiUrl + '/users/' + user;
|
||||
var badgeData = getBadgeData('followers', data);
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.github;
|
||||
badgeData.logo = getLogo('github', data);
|
||||
}
|
||||
githubAuth.request(request, apiUrl, {}, function(err, res, buffer) {
|
||||
if (err != null) {
|
||||
@@ -3633,7 +3633,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
var apiUrl = githubApiUrl + '/repos/' + user + '/' + repo;
|
||||
var badgeData = getBadgeData('license', data);
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.github;
|
||||
badgeData.logo = getLogo('github', data);
|
||||
}
|
||||
// Using our OAuth App secret grants us 5000 req/hour
|
||||
// instead of the standard 60 req/hour.
|
||||
@@ -3687,7 +3687,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
|
||||
var badgeData = getBadgeData('size', data);
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.github;
|
||||
badgeData.logo = getLogo('github', data);
|
||||
}
|
||||
|
||||
githubAuth.request(request, apiUrl, {}, function(err, res, buffer) {
|
||||
@@ -5266,8 +5266,6 @@ cache(function(data, match, sendBadge, request) {
|
||||
|
||||
request(apiUrl, {json: true}, function(err, res, data) {
|
||||
try {
|
||||
badgeData.logo = logos['dockbit'];
|
||||
|
||||
if (res && (res.statusCode === 404 || data.state === null)) {
|
||||
badgeData.text[1] = 'not found';
|
||||
sendBadge(format, badgeData);
|
||||
@@ -5755,7 +5753,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
//var url = 'http://cdn.api.twitter.com/1/urls/count.json?url=' + page;
|
||||
var badgeData = getBadgeData('tweet', data);
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.twitter;
|
||||
badgeData.logo = getLogo('twitter', data);
|
||||
badgeData.links = [
|
||||
'https://twitter.com/intent/tweet?text=Wow:&url=' + page,
|
||||
'https://twitter.com/search?q=' + page,
|
||||
@@ -5780,7 +5778,7 @@ cache(function(data, match, sendBadge, request) {
|
||||
badgeData.colorscheme = null;
|
||||
badgeData.colorB = '#55ACEE';
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = badgeData.logo || logos.twitter;
|
||||
badgeData.logo = getLogo('twitter', data);
|
||||
}
|
||||
badgeData.links = [
|
||||
'https://twitter.com/intent/follow?screen_name=' + user,
|
||||
@@ -5925,10 +5923,6 @@ cache(function(data, match, sendBadge, request) {
|
||||
var badgeData = getBadgeData('chat', data);
|
||||
badgeData.text[1] = 'on gitter';
|
||||
badgeData.colorscheme = 'brightgreen';
|
||||
if (darkBackgroundTemplates.some(function(t) { return t === badgeData.template; })) {
|
||||
badgeData.logo = badgeData.logo || logos['gitter-white'];
|
||||
badgeData.logoWidth = 9;
|
||||
}
|
||||
sendBadge(format, badgeData);
|
||||
}));
|
||||
|
||||
@@ -6089,8 +6083,6 @@ cache(function(data, match, sendBadge, request) {
|
||||
try {
|
||||
var data = JSON.parse(buffer);
|
||||
badgeData.text[1] = data.label;
|
||||
badgeData.logo = logos['bithound'];
|
||||
badgeData.logoWidth = 15;
|
||||
badgeData.colorscheme = null;
|
||||
badgeData.colorB = '#' + data.color;
|
||||
sendBadge(format, badgeData);
|
||||
@@ -6892,7 +6884,7 @@ function(data, match, end, ask) {
|
||||
badgeData.colorscheme = color;
|
||||
}
|
||||
}
|
||||
if (data.style && validTemplates.indexOf(data.style) > -1) {
|
||||
if (isValidStyle(data.style)) {
|
||||
badgeData.template = data.style;
|
||||
}
|
||||
badge(badgeData, makeSend(format, ask.res, end));
|
||||
@@ -6972,53 +6964,6 @@ function escapeFormatSlashes(t) {
|
||||
}
|
||||
|
||||
|
||||
function sixHex(s) { return /^[0-9a-fA-F]{6}$/.test(s); }
|
||||
|
||||
function getLabel(label, data) {
|
||||
return data.label || label;
|
||||
}
|
||||
|
||||
function colorParam(color) { return (sixHex(color) ? '#' : '') + color; }
|
||||
|
||||
// data (URL query) can include `label`, `style`, `logo`, `logoWidth`, `link`,
|
||||
// `colorA`, `colorB`.
|
||||
// It can also include `maxAge`.
|
||||
function getBadgeData(defaultLabel, data) {
|
||||
var label = getLabel(defaultLabel, data);
|
||||
var template = data.style || 'default';
|
||||
if (data.style && validTemplates.indexOf(data.style) > -1) {
|
||||
template = data.style;
|
||||
}
|
||||
if (!(Object(data.link) instanceof Array)) {
|
||||
if (data.link === undefined) {
|
||||
data.link = [];
|
||||
} else {
|
||||
data.link = [data.link];
|
||||
}
|
||||
}
|
||||
|
||||
if (data.logo !== undefined && !/^data:/.test(data.logo)) {
|
||||
data.logo = 'data:' + data.logo;
|
||||
}
|
||||
|
||||
if (data.colorA !== undefined) {
|
||||
data.colorA = colorParam(data.colorA);
|
||||
}
|
||||
if (data.colorB !== undefined) {
|
||||
data.colorB = colorParam(data.colorB);
|
||||
}
|
||||
|
||||
return {
|
||||
text: [label, 'n/a'],
|
||||
colorscheme: 'lightgrey',
|
||||
template: template,
|
||||
logo: data.logo,
|
||||
logoWidth: +data.logoWidth,
|
||||
links: data.link,
|
||||
colorA: data.colorA,
|
||||
colorB: data.colorB
|
||||
};
|
||||
}
|
||||
|
||||
function makeSend(format, askres, end) {
|
||||
if (format === 'svg') {
|
||||
|
||||
Reference in New Issue
Block a user