searching...
- }
- }
-
- render() {
- return (
- searching...
+ } else if (isQueryTooShort) {
+ return
Documentation
@@ -91,55 +172,57 @@ export default class MarkupModal extends React.Component {
}
render() {
- const { markdown, reStructuredText, asciiDoc } = this.generateMarkup()
+ const { isOpen } = this
+ const { onRequestClose } = this.props
+ const { link, badgeUrl, exampleUrl, style } = this.state
- const completeBadgeUrl = this.isOpen
- ? this.generateCompleteBadgeUrl()
- : undefined
+ const common = {
+ autoComplete: 'off',
+ autoCorrect: 'off',
+ autoCapitalize: 'off',
+ spellCheck: 'false',
+ }
return (
)
diff --git a/frontend/components/search-results.js b/frontend/components/search-results.js
deleted file mode 100644
index a1a601a978ad3..0000000000000
--- a/frontend/components/search-results.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from 'react'
-import { Link } from 'react-router-dom'
-import PropTypes from 'prop-types'
-import { BadgeExamples } from './badge-examples'
-import badgeExampleData from '../../badge-examples.json'
-import { prepareExamples, predicateFromQuery } from '../lib/prepare-examples'
-import { baseUrl, longCache } from '../constants'
-
-export default class SearchResults extends React.Component {
- static propTypes = {
- category: PropTypes.string,
- query: PropTypes.string,
- clickHandler: PropTypes.func.isRequired,
- }
-
- prepareExamples(category) {
- const examples = category
- ? badgeExampleData.filter(example => example.category.id === category)
- : badgeExampleData
- return prepareExamples(examples, () => predicateFromQuery(this.props.query))
- }
-
- renderExamples() {
- return (
-
- )
- }
-
- renderCategoryHeadings() {
- return this.preparedExamples.map((category, i) => (
-
-
{category.category.name}
-
- ))
- }
-
- render() {
- this.preparedExamples = this.prepareExamples(this.props.category)
-
- if (this.props.category) {
- return this.renderExamples()
- } else if (this.props.query == null || this.props.query.length === 0) {
- return this.renderCategoryHeadings()
- } else {
- return this.renderExamples()
- }
- }
-}
diff --git a/frontend/components/suggestion-and-search.js b/frontend/components/suggestion-and-search.js
index afd1ed755c6c2..b8b59d76da11a 100644
--- a/frontend/components/suggestion-and-search.js
+++ b/frontend/components/suggestion-and-search.js
@@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import fetchPonyfill from 'fetch-ponyfill'
import debounce from 'lodash.debounce'
-import { Badge } from './badge-examples'
+import BadgeExamples from './badge-examples'
import resolveUrl from '../lib/resolve-url'
export default class SuggestionAndSearch extends React.Component {
@@ -51,7 +51,7 @@ export default class SuggestionAndSearch extends React.Component {
const json = await res.json()
// This doesn't validate the response. The default value here prevents
// a crash if the server returns {"err":"Disallowed"}.
- suggestions = json.badges || []
+ suggestions = json.suggestions || []
} catch (e) {
suggestions = []
}
@@ -68,28 +68,24 @@ export default class SuggestionAndSearch extends React.Component {
return null
}
+ const transformed = [
+ {
+ examples: suggestions.map(({ title, path, link, queryParams }) => ({
+ title,
+ preview: { path, queryParams },
+ example: { path, queryParams },
+ link,
+ })),
+ },
+ ]
+
return (
-
-
- {suggestions.map(({ name, link, badge }, i) => (
- // TODO We need to deal with `link`.
-
- this.props.onBadgeClick({
- title: name,
- previewUrl: badge,
- link,
- })
- }
- baseUrl={baseUrl}
- longCache={longCache}
- />
- ))}
-
-
+
)
}
diff --git a/frontend/constants.js b/frontend/constants.js
index 1a927a6971db5..1e81b0d708116 100644
--- a/frontend/constants.js
+++ b/frontend/constants.js
@@ -1,6 +1,6 @@
import envFlag from 'node-env-flag'
-const baseUrl = process.env.BASE_URL
+const baseUrl = process.env.BASE_URL || ''
const longCache = envFlag(process.env.LONG_CACHE, false)
export { baseUrl, longCache }
diff --git a/frontend/lib/badge-url.js b/frontend/lib/badge-url.js
index ceaee81a8dfc5..30e7d368f5202 100644
--- a/frontend/lib/badge-url.js
+++ b/frontend/lib/badge-url.js
@@ -4,7 +4,7 @@ import { staticBadgeUrl as makeStaticBadgeUrl } from '../../lib/make-badge-url'
export default function resolveBadgeUrl(
url,
baseUrl,
- { longCache, style, queryParams: inQueryParams } = {}
+ { longCache, style, queryParams: inQueryParams, format = 'svg' } = {}
) {
const outQueryParams = Object.assign({}, inQueryParams)
if (longCache) {
@@ -13,7 +13,8 @@ export default function resolveBadgeUrl(
if (style) {
outQueryParams.style = style
}
- return resolveUrl(url, baseUrl, outQueryParams)
+
+ return resolveUrl(`${url}.${format}`, baseUrl, outQueryParams)
}
export function staticBadgeUrl(baseUrl, label, message, color, options) {
@@ -48,5 +49,5 @@ export function dynamicBadgeUrl(
const outOptions = Object.assign({ queryParams }, rest)
- return resolveBadgeUrl(`/badge/dynamic/${datatype}.svg`, baseUrl, outOptions)
+ return resolveBadgeUrl(`/badge/dynamic/${datatype}`, baseUrl, outOptions)
}
diff --git a/frontend/lib/badge-url.spec.js b/frontend/lib/badge-url.spec.js
index 34fe55e8847d2..65ab08138efc6 100644
--- a/frontend/lib/badge-url.spec.js
+++ b/frontend/lib/badge-url.spec.js
@@ -6,19 +6,17 @@ const resolveBadgeUrlWithLongCache = (url, baseUrl) =>
describe('Badge URL functions', function() {
test(resolveBadgeUrl, () => {
- given('/badge/foo-bar-blue.svg', undefined).expect(
- '/badge/foo-bar-blue.svg'
- )
- given('/badge/foo-bar-blue.svg', 'http://example.com').expect(
+ given('/badge/foo-bar-blue', undefined).expect('/badge/foo-bar-blue.svg')
+ given('/badge/foo-bar-blue', 'http://example.com').expect(
'http://example.com/badge/foo-bar-blue.svg'
)
})
test(resolveBadgeUrlWithLongCache, () => {
- given('/badge/foo-bar-blue.svg', undefined).expect(
+ given('/badge/foo-bar-blue', undefined).expect(
'/badge/foo-bar-blue.svg?maxAge=2592000'
)
- given('/badge/foo-bar-blue.svg', 'http://example.com').expect(
+ given('/badge/foo-bar-blue', 'http://example.com').expect(
'http://example.com/badge/foo-bar-blue.svg?maxAge=2592000'
)
})
diff --git a/frontend/lib/prepare-examples.js b/frontend/lib/prepare-examples.js
deleted file mode 100644
index 4293d2aff7ebe..0000000000000
--- a/frontend/lib/prepare-examples.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import escapeStringRegexp from 'escape-string-regexp'
-
-export function exampleMatchesRegex(example, regex) {
- const { title, keywords } = example
- const haystack = [title].concat(keywords).join(' ')
- return regex.test(haystack)
-}
-
-export function predicateFromQuery(query) {
- if (query) {
- const escaped = escapeStringRegexp(query)
- const regex = new RegExp(escaped, 'i') // Case-insensitive.
- return example => exampleMatchesRegex(example, regex)
- } else {
- return () => true
- }
-}
-
-export function mapExamples(categories, iteratee) {
- return (
- categories
- .map(({ category, examples }) => ({
- category,
- examples: iteratee(examples),
- }))
- // Remove empty categories.
- .filter(({ category, examples }) => examples.length > 0)
- )
-}
-
-export function prepareExamples(categories, predicateProvider) {
- let nextKey = 0
- return mapExamples(categories, examples =>
- examples.map(example =>
- Object.assign(
- {
- shouldDisplay: () => predicateProvider()(example),
- // Assign each example a unique ID.
- key: nextKey++,
- },
- example
- )
- )
- )
-}
diff --git a/frontend/lib/prepare-examples.spec.js b/frontend/lib/prepare-examples.spec.js
deleted file mode 100644
index 57b61caeaade9..0000000000000
--- a/frontend/lib/prepare-examples.spec.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { test, given, forCases } from 'sazerac'
-import { predicateFromQuery } from './prepare-examples'
-
-describe('Badge example functions', function() {
- const exampleMatchesQuery = (example, query) =>
- predicateFromQuery(query)(example)
-
- test(exampleMatchesQuery, () => {
- forCases([given({ title: 'node version' }, 'npm')]).expect(false)
-
- forCases([
- given({ title: 'node version', keywords: ['npm'] }, 'node'),
- given({ title: 'node version', keywords: ['npm'] }, 'npm'),
- // https://github.com/badges/shields/issues/1578
- given({ title: 'c++ is the best language' }, 'c++'),
- ]).expect(true)
- })
-})
diff --git a/frontend/lib/service-definitions/index.js b/frontend/lib/service-definitions/index.js
new file mode 100644
index 0000000000000..166843308d932
--- /dev/null
+++ b/frontend/lib/service-definitions/index.js
@@ -0,0 +1,13 @@
+import groupBy from 'lodash.groupby'
+
+import { services, categories } from '../../../service-definitions.yml'
+export { services, categories } from '../../../service-definitions.yml'
+
+export function findCategory(category) {
+ return categories.find(({ id }) => id === category)
+}
+
+const byCategory = groupBy(services, 'category')
+export function getDefinitionsForCategory(category) {
+ return byCategory[category]
+}
diff --git a/frontend/lib/service-definitions/service-definition-set-helper.js b/frontend/lib/service-definitions/service-definition-set-helper.js
new file mode 100644
index 0000000000000..105f560f20beb
--- /dev/null
+++ b/frontend/lib/service-definitions/service-definition-set-helper.js
@@ -0,0 +1,47 @@
+import escapeStringRegexp from 'escape-string-regexp'
+
+export function exampleMatchesRegex(example, regex) {
+ const { title, keywords } = example
+ const haystack = [title].concat(keywords).join(' ')
+ return regex.test(haystack)
+}
+
+export function predicateFromQuery(query) {
+ const escaped = escapeStringRegexp(query)
+ const regex = new RegExp(escaped, 'i') // Case-insensitive.
+ return ({ examples }) =>
+ examples.some(example => exampleMatchesRegex(example, regex))
+}
+
+export default class ServiceDefinitionSetHelper {
+ constructor(definitionData) {
+ this.definitionData = definitionData
+ }
+
+ static create(definitionData) {
+ return new ServiceDefinitionSetHelper(definitionData)
+ }
+
+ getCategory(wantedCategory) {
+ return ServiceDefinitionSetHelper.create(
+ this.definitionData.filter(({ category }) => category === wantedCategory)
+ )
+ }
+
+ search(query) {
+ const predicate = predicateFromQuery(query)
+ return ServiceDefinitionSetHelper.create(
+ this.definitionData.filter(predicate)
+ )
+ }
+
+ notDeprecated() {
+ return ServiceDefinitionSetHelper.create(
+ this.definitionData.filter(({ isDeprecated }) => !isDeprecated)
+ )
+ }
+
+ toArray() {
+ return this.definitionData
+ }
+}
diff --git a/frontend/lib/service-definitions/service-definition-set-helper.spec.js b/frontend/lib/service-definitions/service-definition-set-helper.spec.js
new file mode 100644
index 0000000000000..205c7631ec2ab
--- /dev/null
+++ b/frontend/lib/service-definitions/service-definition-set-helper.spec.js
@@ -0,0 +1,26 @@
+import { test, given, forCases } from 'sazerac'
+import { predicateFromQuery } from './service-definition-set-helper'
+
+describe('Badge example functions', function() {
+ const exampleMatchesQuery = (example, query) =>
+ predicateFromQuery(query)(example)
+
+ test(exampleMatchesQuery, () => {
+ forCases([given({ examples: [{ title: 'node version' }] }, 'npm')]).expect(
+ false
+ )
+
+ forCases([
+ given(
+ { examples: [{ title: 'node version', keywords: ['npm'] }] },
+ 'node'
+ ),
+ given(
+ { examples: [{ title: 'node version', keywords: ['npm'] }] },
+ 'npm'
+ ),
+ // https://github.com/badges/shields/issues/1578
+ given({ examples: [{ title: 'c++ is the best language' }] }, 'c++'),
+ ]).expect(true)
+ })
+})
diff --git a/lib/all-badge-examples.js b/lib/all-badge-examples.js
deleted file mode 100644
index 93a1414918431..0000000000000
--- a/lib/all-badge-examples.js
+++ /dev/null
@@ -1,139 +0,0 @@
-'use strict'
-
-const { loadServiceClasses } = require('../services')
-
-const allBadgeExamples = [
- {
- category: {
- id: 'build',
- name: 'Build',
- },
- examples: [],
- },
- {
- category: {
- id: 'chat',
- name: 'Chat',
- },
- examples: [],
- },
- {
- category: {
- id: 'dependencies',
- name: 'Dependencies',
- },
- examples: [],
- },
- {
- category: {
- id: 'size',
- name: 'Size',
- },
- examples: [],
- },
- {
- category: {
- id: 'downloads',
- name: 'Downloads',
- },
- examples: [],
- },
- {
- category: {
- id: 'funding',
- name: 'Funding',
- },
- examples: [],
- },
- {
- category: {
- id: 'issue-tracking',
- name: 'Issue Tracking',
- },
- examples: [],
- },
- {
- category: {
- id: 'license',
- name: 'License',
- },
- examples: [],
- },
- {
- category: {
- id: 'rating',
- name: 'Rating',
- },
- examples: [],
- },
- {
- category: {
- id: 'social',
- name: 'Social',
- },
- examples: [],
- },
- {
- category: {
- id: 'version',
- name: 'Version',
- },
- examples: [],
- },
- {
- category: {
- id: 'platform-support',
- name: 'Platform & Version Support',
- },
- examples: [],
- },
- {
- category: {
- id: 'monitoring',
- name: 'Monitoring',
- },
- examples: [],
- },
- {
- category: {
- id: 'activity',
- name: 'Activity',
- },
- examples: [],
- },
- {
- category: {
- id: 'other',
- name: 'Other',
- },
- examples: [],
- },
-]
-
-function findCategory(wantedCategory) {
- return allBadgeExamples.find(
- thisCat => thisCat.category.id === wantedCategory
- )
-}
-
-function loadExamples() {
- loadServiceClasses().forEach(ServiceClass => {
- const prepared = ServiceClass.prepareExamples()
- if (prepared.length === 0) {
- return
- }
- const category = findCategory(ServiceClass.category)
- if (category === undefined) {
- throw Error(
- `Unknown category ${ServiceClass.category} referenced in ${
- ServiceClass.name
- }`
- )
- }
- category.examples = category.examples.concat(prepared)
- })
-}
-loadExamples()
-
-module.exports = allBadgeExamples
-module.exports.findCategory = findCategory
diff --git a/lib/all-badge-examples.spec.js b/lib/all-badge-examples.spec.js
deleted file mode 100644
index d7b21cfcd1e2a..0000000000000
--- a/lib/all-badge-examples.spec.js
+++ /dev/null
@@ -1,34 +0,0 @@
-'use strict'
-
-const { expect } = require('chai')
-
-const allBadgeExamples = require('./all-badge-examples')
-
-describe('The badge examples', function() {
- it('should include AppVeyor, which is added automatically', function() {
- const { examples } = allBadgeExamples.findCategory('build')
-
- const appVeyorBuildExamples = examples
- .filter(ex => ex.title.includes('AppVeyor'))
- .filter(ex => !ex.title.includes('tests'))
-
- expect(appVeyorBuildExamples).to.deep.equal([
- {
- title: 'AppVeyor',
- exampleUrl: '/appveyor/ci/gruntjs/grunt.svg',
- previewUrl: '/badge/build-passing-brightgreen.svg',
- urlPattern: '/appveyor/ci/:user/:repo.svg',
- documentation: undefined,
- keywords: undefined,
- },
- {
- title: 'AppVeyor branch',
- exampleUrl: '/appveyor/ci/gruntjs/grunt/master.svg',
- previewUrl: '/badge/build-passing-brightgreen.svg',
- urlPattern: '/appveyor/ci/:user/:repo/:branch.svg',
- documentation: undefined,
- keywords: undefined,
- },
- ])
- })
-})
diff --git a/lib/make-badge-url.js b/lib/make-badge-url.js
index 1b43f159d0538..aac423f45f191 100644
--- a/lib/make-badge-url.js
+++ b/lib/make-badge-url.js
@@ -1,13 +1,60 @@
'use strict'
const queryString = require('query-string')
+const pathToRegexp = require('path-to-regexp')
+
+function badgeUrlFromPath({
+ baseUrl = '',
+ path,
+ queryParams,
+ style,
+ format = 'svg',
+ longCache = false,
+}) {
+ const outExt = format.length ? `.${format}` : ''
+
+ const outQueryString = queryString.stringify({
+ maxAge: longCache ? '2592000' : undefined,
+ style,
+ ...queryParams,
+ })
+ const suffix = outQueryString ? `?${outQueryString}` : ''
+
+ return `${baseUrl}${path}${outExt}${suffix}`
+}
+
+function badgeUrlFromPattern({
+ baseUrl = '',
+ pattern,
+ namedParams,
+ queryParams,
+ style,
+ format = 'svg',
+ longCache = false,
+}) {
+ const toPath = pathToRegexp.compile(pattern, {
+ strict: true,
+ sensitive: true,
+ })
+
+ const path = toPath(namedParams)
+
+ return badgeUrlFromPath({
+ baseUrl,
+ path,
+ queryParams,
+ style,
+ format,
+ longCache,
+ })
+}
function encodeField(s) {
return encodeURIComponent(s.replace(/-/g, '--').replace(/_/g, '__'))
}
function staticBadgeUrl({
- baseUrl,
+ baseUrl = '',
label,
message,
color = 'lightgray',
@@ -22,10 +69,12 @@ function staticBadgeUrl({
style,
})
const suffix = outQueryString ? `?${outQueryString}` : ''
- return `/badge/${path}.${format}${suffix}`
+ return `${baseUrl}/badge/${path}.${format}${suffix}`
}
module.exports = {
+ badgeUrlFromPath,
+ badgeUrlFromPattern,
encodeField,
staticBadgeUrl,
}
diff --git a/lib/make-badge-url.spec.js b/lib/make-badge-url.spec.js
index 595455c668bb5..28dbdb306d1c5 100644
--- a/lib/make-badge-url.spec.js
+++ b/lib/make-badge-url.spec.js
@@ -1,9 +1,37 @@
'use strict'
const { test, given } = require('sazerac')
-const { encodeField, staticBadgeUrl } = require('./make-badge-url')
+const {
+ badgeUrlFromPath,
+ badgeUrlFromPattern,
+ encodeField,
+ staticBadgeUrl,
+} = require('./make-badge-url')
describe('Badge URL generation functions', function() {
+ test(badgeUrlFromPath, () => {
+ given({
+ baseUrl: 'http://example.com',
+ path: '/npm/v/gh-badges',
+ style: 'flat-square',
+ longCache: true,
+ }).expect(
+ 'http://example.com/npm/v/gh-badges.svg?maxAge=2592000&style=flat-square'
+ )
+ })
+
+ test(badgeUrlFromPattern, () => {
+ given({
+ baseUrl: 'http://example.com',
+ pattern: '/npm/v/:packageName',
+ namedParams: { packageName: 'gh-badges' },
+ style: 'flat-square',
+ longCache: true,
+ }).expect(
+ 'http://example.com/npm/v/gh-badges.svg?maxAge=2592000&style=flat-square'
+ )
+ })
+
test(encodeField, () => {
given('foo').expect('foo')
given('').expect('')
diff --git a/lib/suggest.js b/lib/suggest.js
index 8c6bc3843553e..28fb70b3e1ac6 100644
--- a/lib/suggest.js
+++ b/lib/suggest.js
@@ -19,38 +19,39 @@ function twitterPage(url) {
const host = url.host
const path = url.pathname
return {
- name: 'Twitter',
+ title: 'Twitter',
link: `https://twitter.com/intent/tweet?text=Wow:&url=${encodeURIComponent(
url.href
)}`,
- badge: `https://img.shields.io/twitter/url/${schema}/${host}${path}.svg?style=social`,
+ path: `/twitter/url/${schema}/${host}${path}`,
+ queryParams: { style: 'social' },
}
}
function githubIssues(user, repo) {
const repoSlug = `${user}/${repo}`
return {
- name: 'GitHub issues',
+ title: 'GitHub issues',
link: `https://github.com/${repoSlug}/issues`,
- badge: `https://img.shields.io/github/issues/${repoSlug}.svg`,
+ path: `/github/issues/${repoSlug}`,
}
}
function githubForks(user, repo) {
const repoSlug = `${user}/${repo}`
return {
- name: 'GitHub forks',
+ title: 'GitHub forks',
link: `https://github.com/${repoSlug}/network`,
- badge: `https://img.shields.io/github/forks/${repoSlug}.svg`,
+ path: `/github/forks/${repoSlug}`,
}
}
function githubStars(user, repo) {
const repoSlug = `${user}/${repo}`
return {
- name: 'GitHub stars',
+ title: 'GitHub stars',
link: `https://github.com/${repoSlug}/stargazers`,
- badge: `https://img.shields.io/github/stars/${repoSlug}.svg`,
+ path: `/github/stars/${repoSlug}`,
}
}
@@ -71,8 +72,8 @@ async function githubLicense(githubApiProvider, user, repo) {
} catch (e) {}
return {
- name: 'GitHub license',
- badge: `https://img.shields.io/github/license/${repoSlug}.svg`,
+ title: 'GitHub license',
+ path: `/github/license/${repoSlug}`,
link,
}
}
@@ -99,10 +100,11 @@ async function findSuggestions(githubApiProvider, url) {
// data: {url}, JSON-serializable object.
// end: function(json), with json of the form:
-// - badges: list of objects of the form:
+// - suggestions: list of objects of the form:
+// - title: string
// - link: target as a string URL.
-// - badge: shields image URL.
-// - name: string
+// - path: shields image URL path.
+// - queryParams: Object containing query params (Optional)
function setRoutes(allowedOrigin, githubApiProvider, server) {
server.ajax.on('suggest/v1', (data, end, ask) => {
// The typical dev and production setups are cross-origin. However, in
@@ -145,11 +147,11 @@ function setRoutes(allowedOrigin, githubApiProvider, server) {
findSuggestions(githubApiProvider, url)
// This interacts with callback code and can't use async/await.
// eslint-disable-next-line promise/prefer-await-to-then
- .then(badges => {
- end({ badges })
+ .then(suggestions => {
+ end({ suggestions })
})
.catch(err => {
- end({ badges: [], err })
+ end({ suggestions: [], err })
})
})
}
diff --git a/next.config.js b/next.config.js
index e20cf711fe59b..9bf62b997ad0c 100644
--- a/next.config.js
+++ b/next.config.js
@@ -24,9 +24,11 @@ module.exports = {
)
}
- config.module.loaders = (config.module.loaders || []).concat({
- test: /\.json$/,
- loader: 'json-loader',
+ config.module.rules.push({
+ test: /\.yml$/,
+ use: {
+ loader: 'js-yaml-loader',
+ },
})
if (assetPrefix) {
diff --git a/package-lock.json b/package-lock.json
index cae90d8ed7210..904d4734ab6ce 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2758,12 +2758,6 @@
}
}
},
- "classnames": {
- "version": "2.2.6",
- "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
- "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==",
- "dev": true
- },
"cli-cursor": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
@@ -7541,6 +7535,17 @@
}
}
},
+ "js-yaml-loader": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/js-yaml-loader/-/js-yaml-loader-1.0.1.tgz",
+ "integrity": "sha512-fts4y0A76YrlO+ARYue0dRy5CUUskF7hIoiFoffb2OlXGoUy3DPwNldDi/gwtwRhPyOBIAwXw9myqVLB1Mf17Q==",
+ "dev": true,
+ "requires": {
+ "js-yaml": "^3.9.1",
+ "loader-utils": "^1.1.0",
+ "un-eval": "^1.2.0"
+ }
+ },
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
@@ -8142,6 +8147,12 @@
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
"dev": true
},
+ "lodash.groupby": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz",
+ "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=",
+ "dev": true
+ },
"lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -15048,6 +15059,12 @@
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
},
+ "un-eval": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/un-eval/-/un-eval-1.2.0.tgz",
+ "integrity": "sha512-Wlj/pum6dQtGTPD/lclDtoVPkSfpjPfy1dwnnKw/sZP5DpBH9fLhBgQfsqNhe5/gS1D+vkZUuB771NRMUPA5CA==",
+ "dev": true
+ },
"underscore": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz",
diff --git a/package.json b/package.json
index a687216c80513..3d15424c27dc7 100644
--- a/package.json
+++ b/package.json
@@ -86,13 +86,12 @@
"prebuild": "npm run depcheck",
"features": "node scripts/export-supported-features-cli.js > supported-features.json",
"defs": "node scripts/export-service-definitions-cli.js > service-definitions.yml",
- "examples": "node scripts/export-badge-examples-cli.js > badge-examples.json",
- "build": "npm run examples && npm run defs && npm run features && next build && next export -o build/",
+ "build": "npm run defs && npm run features && next build && next export -o build/",
"heroku-postbuild": "npm run build",
"analyze": "ANALYZE=true LONG_CACHE=false BASE_URL=https://img.shields.io npm run build",
"start:server": "HANDLE_INTERNAL_ERRORS=false RATE_LIMIT=false node server 8080 ::",
"now-start": "node server",
- "prestart": "npm run depcheck && npm run examples && npm run defs && npm run features",
+ "prestart": "npm run depcheck && npm run defs && npm run features",
"start": "concurrently --names server,frontend \"ALLOWED_ORIGIN=http://localhost:3000 npm run start:server\" \"BASE_URL=http://[::]:8080 next dev\"",
"refactoring-report": "node scripts/refactoring-cli.js"
},
@@ -119,7 +118,6 @@
"chai-string": "^1.4.0",
"chainsmoker": "^0.1.0",
"child-process-promise": "^2.2.1",
- "classnames": "^2.2.5",
"concurrently": "^4.1.0",
"danger": "^6.1.9",
"danger-plugin-no-test-shortcuts": "^2.0.0",
@@ -146,9 +144,11 @@
"is-png": "^1.1.0",
"is-svg": "^3.0.0",
"joi-extension-semver": "2.0.0",
+ "js-yaml-loader": "^1.0.1",
"lint-staged": "^8.1.0",
"lodash.debounce": "^4.0.8",
"lodash.difference": "^4.5.0",
+ "lodash.groupby": "^4.6.0",
"lodash.mapvalues": "^4.6.0",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
diff --git a/pages/index.js b/pages/index.js
index c9444d2ce819f..90cd8635e6771 100644
--- a/pages/index.js
+++ b/pages/index.js
@@ -1,13 +1,13 @@
import React from 'react'
import { HashRouter, StaticRouter, Route } from 'react-router-dom'
-import ExamplesPage from '../frontend/components/examples-page'
+import Main from '../frontend/components/main'
export default class Router extends React.Component {
render() {
const router = (
-
-
+
+
)
diff --git a/scripts/export-badge-examples-cli.js b/scripts/export-badge-examples-cli.js
deleted file mode 100644
index 9f1d966a703bf..0000000000000
--- a/scripts/export-badge-examples-cli.js
+++ /dev/null
@@ -1,5 +0,0 @@
-'use strict'
-
-const allBadgeExamples = require('../lib/all-badge-examples')
-
-process.stdout.write(JSON.stringify(allBadgeExamples))
diff --git a/services/base.js b/services/base.js
index b7b03aea28259..5ff89f1dc35ad 100644
--- a/services/base.js
+++ b/services/base.js
@@ -2,7 +2,6 @@
// See available emoji at http://emoji.muan.co/
const emojic = require('emojic')
-const queryString = require('query-string')
const pathToRegexp = require('path-to-regexp')
const {
NotFound,
@@ -20,9 +19,7 @@ const {
makeColor,
setBadgeColor,
} = require('../lib/badge-data')
-const { staticBadgeUrl } = require('../lib/make-badge-url')
const trace = require('./trace')
-const oldValidateExample = require('./validate-example')
const { validateExample, transformExample } = require('./transform-example')
const { assertValidCategory } = require('./categories')
const { assertValidServiceDefinition } = require('./service-definitions')
@@ -125,92 +122,6 @@ class BaseService {
return `/${[this.route.base, partialUrl].filter(Boolean).join('/')}`
}
- static _makeFullUrlFromParams(pattern, namedParams, ext = 'svg') {
- const fullPattern = `${this._makeFullUrl(
- pattern
- )}.:ext(svg|png|gif|jpg|json)`
-
- const toPath = pathToRegexp.compile(fullPattern, {
- strict: true,
- sensitive: true,
- })
-
- return toPath({ ext, ...namedParams })
- }
-
- static _makeStaticExampleUrl(serviceData) {
- const badgeData = this._makeBadgeData({}, serviceData)
- return staticBadgeUrl({
- label: badgeData.text[0],
- message: `${badgeData.text[1]}`,
- color: badgeData.colorscheme || badgeData.colorB,
- })
- }
-
- static _dotSvg(url) {
- if (url.includes('?')) {
- return url.replace('?', '.svg?')
- } else {
- return `${url}.svg`
- }
- }
-
- /**
- * Return an array of examples. Each example is prepared according to the
- * schema in `lib/all-badge-examples.js`.
- */
- static prepareExamples() {
- return this.examples.map((example, index) => {
- const {
- title,
- query,
- namedParams,
- exampleUrl,
- previewUrl,
- pattern,
- staticExample,
- documentation,
- keywords,
- } = oldValidateExample(example, index, this)
-
- const stringified = queryString.stringify(query)
- const suffix = stringified ? `?${stringified}` : ''
-
- let outExampleUrl
- let outPreviewUrl
- let outPattern
- if (namedParams) {
- outPreviewUrl = this._makeStaticExampleUrl(staticExample)
- outPattern = `${this._dotSvg(this._makeFullUrl(pattern))}${suffix}`
- outExampleUrl = `${this._makeFullUrlFromParams(
- pattern,
- namedParams
- )}${suffix}`
- } else if (staticExample) {
- outPreviewUrl = this._makeStaticExampleUrl(staticExample)
- outPattern = `${this._dotSvg(this._makeFullUrl(pattern))}${suffix}`
- outExampleUrl = `${this._dotSvg(
- this._makeFullUrl(exampleUrl)
- )}${suffix}`
- } else {
- outPreviewUrl = `${this._dotSvg(
- this._makeFullUrl(previewUrl)
- )}${suffix}`
- outPattern = undefined
- outExampleUrl = undefined
- }
-
- return {
- title: title ? `${title}` : this.name,
- exampleUrl: outExampleUrl,
- previewUrl: outPreviewUrl,
- urlPattern: outPattern,
- documentation,
- keywords,
- }
- })
- }
-
static validateDefinition() {
assertValidCategory(this.category, `Category for ${this.name}`)
@@ -235,7 +146,7 @@ class BaseService {
let route
if (pattern) {
- route = { pattern, queryParams }
+ route = { pattern: this._makeFullUrl(pattern), queryParams }
} else if (format) {
route = { format, queryParams }
} else {
diff --git a/services/base.spec.js b/services/base.spec.js
index 855773dacf745..11d02f42e83ec 100644
--- a/services/base.spec.js
+++ b/services/base.spec.js
@@ -48,6 +48,11 @@ class DummyService extends BaseService {
staticExample: this.render({ namedParamA: 'foo', queryParamA: 'bar' }),
keywords: ['hello'],
},
+ {
+ namedParams: { namedParamA: 'World' },
+ staticExample: this.render({ namedParamA: 'foo', queryParamA: 'bar' }),
+ keywords: ['hello'],
+ },
{
pattern: ':world',
namedParams: { world: 'World' },
@@ -494,78 +499,31 @@ describe('BaseService', function() {
})
})
- describe('_makeStaticExampleUrl', function() {
- test(
- serviceData => DummyService._makeStaticExampleUrl(serviceData),
- () => {
- given({
- message: 'hello',
- color: 'dcdc00',
- }).expect('/badge/cat-hello-%23dcdc00.svg')
- given({
- message: 'hello',
- color: 'red',
- }).expect('/badge/cat-hello-red.svg')
- given({
- message: 'hello',
- }).expect('/badge/cat-hello-lightgrey.svg')
- }
- )
- })
-
- describe('prepareExamples', function() {
- it('returns the expected result', function() {
- const [
- first,
- second,
- third,
- fourth,
- fifth,
- ] = DummyService.prepareExamples()
- expect(first).to.deep.equal({
- title: 'DummyService',
- exampleUrl: undefined,
- previewUrl: '/foo/World.svg',
- urlPattern: undefined,
- documentation: undefined,
- keywords: undefined,
- })
- expect(second).to.deep.equal({
- title: 'DummyService',
- exampleUrl: undefined,
- previewUrl: '/foo/World.svg?queryParamA=%21%21%21',
- urlPattern: undefined,
- documentation: undefined,
- keywords: undefined,
- })
- const preparedStaticExample = {
- title: 'DummyService',
- exampleUrl: '/foo/World.svg',
- previewUrl:
- '/badge/cat-Hello%20namedParamA%3A%20foo%20with%20queryParamA%3A%20bar-lightgrey.svg',
- urlPattern: '/foo/:world.svg',
- documentation: undefined,
- keywords: ['hello'],
- }
- expect(third).to.deep.equal(preparedStaticExample)
- expect(fourth).to.deep.equal(preparedStaticExample)
- expect(fifth).to.deep.equal({
- title: 'DummyService',
- exampleUrl: '/foo/World.svg?queryParamA=%21%21%21',
- previewUrl:
- '/badge/cat-Hello%20namedParamA%3A%20foo%20with%20queryParamA%3A%20bar-lightgrey.svg',
- urlPattern: '/foo/:world.svg?queryParamA=%21%21%21',
- documentation: undefined,
- keywords: ['hello'],
- })
- })
- })
-
describe('getDefinition', function() {
it('returns the expected result', function() {
const {
- examples: [first, second, third, fourth, fifth],
+ category,
+ name,
+ isDeprecated,
+ route,
+ examples,
} = DummyService.getDefinition()
+ expect({
+ category,
+ name,
+ isDeprecated,
+ route,
+ }).to.deep.equal({
+ category: 'cat',
+ name: 'DummyService',
+ isDeprecated: false,
+ route: {
+ pattern: '/foo/:namedParamA',
+ queryParams: [],
+ },
+ })
+
+ const [first, second, third, fourth, fifth, sixth] = examples
expect(first).to.deep.equal({
title: 'DummyService',
example: {
@@ -623,6 +581,21 @@ describe('BaseService', function() {
documentation: undefined,
})
expect(fifth).to.deep.equal({
+ title: 'DummyService',
+ example: {
+ pattern: '/foo/:namedParamA',
+ namedParams: { namedParamA: 'World' },
+ queryParams: {},
+ },
+ preview: {
+ label: 'cat',
+ message: 'Hello namedParamA: foo with queryParamA: bar',
+ color: 'lightgrey',
+ },
+ keywords: ['hello'],
+ documentation: undefined,
+ })
+ expect(sixth).to.deep.equal({
title: 'DummyService',
example: {
pattern: '/foo/:world',
diff --git a/services/suggest/suggest.tester.js b/services/suggest/suggest.tester.js
index 71d2177168c40..bece51b96219d 100644
--- a/services/suggest/suggest.tester.js
+++ b/services/suggest/suggest.tester.js
@@ -15,36 +15,37 @@ module.exports = t
t.create('issues, forks, stars and twitter')
.get(`/v1?url=${encodeURIComponent('https://github.com/atom/atom')}`)
- // suggest resource requires this header value
- .expectJSON('badges.?', {
- name: 'GitHub issues',
+ .expectJSON('suggestions.?', {
+ title: 'GitHub issues',
link: 'https://github.com/atom/atom/issues',
- badge: 'https://img.shields.io/github/issues/atom/atom.svg',
+ path: '/github/issues/atom/atom',
})
- .expectJSON('badges.?', {
- name: 'GitHub forks',
+ .expectJSON('suggestions.?', {
+ title: 'GitHub forks',
link: 'https://github.com/atom/atom/network',
- badge: 'https://img.shields.io/github/forks/atom/atom.svg',
+ path: '/github/forks/atom/atom',
})
- .expectJSON('badges.?', {
- name: 'GitHub stars',
+ .expectJSON('suggestions.?', {
+ title: 'GitHub stars',
link: 'https://github.com/atom/atom/stargazers',
- badge: 'https://img.shields.io/github/stars/atom/atom.svg',
+ path: '/github/stars/atom/atom',
})
- .expectJSON('badges.?', {
- name: 'Twitter',
+ .expectJSON('suggestions.?', {
+ title: 'Twitter',
link:
'https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fatom%2Fatom',
- badge:
- 'https://img.shields.io/twitter/url/https/github.com/atom/atom.svg?style=social',
+ path: '/twitter/url/https/github.com/atom/atom',
+ queryParams: {
+ style: 'social',
+ },
})
t.create('license')
.get(`/v1?url=${encodeURIComponent('https://github.com/atom/atom')}`)
- .expectJSON('badges.?', {
- name: 'GitHub license',
+ .expectJSON('suggestions.?', {
+ title: 'GitHub license',
link: 'https://github.com/atom/atom/blob/master/LICENSE.md',
- badge: 'https://img.shields.io/github/license/atom/atom.svg',
+ path: '/github/license/atom/atom',
})
t.create('license for non-existing project')
@@ -54,10 +55,10 @@ t.create('license for non-existing project')
.get(/\/repos\/atom\/atom\/license/)
.reply(404)
)
- .expectJSON('badges.?', {
- name: 'GitHub license',
+ .expectJSON('suggestions.?', {
+ title: 'GitHub license',
link: 'https://github.com/atom/atom',
- badge: 'https://img.shields.io/github/license/atom/atom.svg',
+ path: '/github/license/atom/atom',
})
t.create('license when json response is invalid')
@@ -67,10 +68,10 @@ t.create('license when json response is invalid')
.get(/\/repos\/atom\/atom\/license/)
.reply(invalidJSON)
)
- .expectJSON('badges.?', {
- name: 'GitHub license',
+ .expectJSON('suggestions.?', {
+ title: 'GitHub license',
link: 'https://github.com/atom/atom',
- badge: 'https://img.shields.io/github/license/atom/atom.svg',
+ path: '/github/license/atom/atom',
})
t.create('license when html_url not found in GitHub api response')
@@ -82,8 +83,8 @@ t.create('license when html_url not found in GitHub api response')
license: 'MIT',
})
)
- .expectJSON('badges.?', {
- name: 'GitHub license',
+ .expectJSON('suggestions.?', {
+ title: 'GitHub license',
link: 'https://github.com/atom/atom',
- badge: 'https://img.shields.io/github/license/atom/atom.svg',
+ path: '/github/license/atom/atom',
})
diff --git a/services/transform-example.js b/services/transform-example.js
index c25079d2a3a99..20187181d738d 100644
--- a/services/transform-example.js
+++ b/services/transform-example.js
@@ -104,7 +104,7 @@ function transformExample(inExample, index, ServiceClass) {
let example
if (namedParams) {
example = {
- pattern: ServiceClass._makeFullUrl(pattern),
+ pattern: ServiceClass._makeFullUrl(pattern || ServiceClass.route.pattern),
namedParams,
queryParams,
}
diff --git a/services/validate-example.js b/services/validate-example.js
deleted file mode 100644
index 14e28b93b43ff..0000000000000
--- a/services/validate-example.js
+++ /dev/null
@@ -1,72 +0,0 @@
-'use strict'
-
-module.exports = function validateExample(
- {
- title,
- query,
- queryParams,
- namedParams,
- exampleUrl,
- previewUrl,
- pattern,
- urlPattern,
- staticExample,
- staticPreview,
- documentation,
- keywords,
- },
- index,
- ServiceClass
-) {
- pattern = pattern || urlPattern || ServiceClass.route.pattern
- staticExample = staticExample || staticPreview
- query = query || queryParams
-
- if (staticExample) {
- if (!pattern) {
- throw new Error(
- `Static example for ${
- ServiceClass.name
- } at index ${index} does not declare a pattern`
- )
- }
- if (namedParams && exampleUrl) {
- throw new Error(
- `Static example for ${
- ServiceClass.name
- } at index ${index} declares both namedParams and exampleUrl`
- )
- } else if (!namedParams && !exampleUrl) {
- throw new Error(
- `Static example for ${
- ServiceClass.name
- } at index ${index} does not declare namedParams nor exampleUrl`
- )
- }
- if (previewUrl) {
- throw new Error(
- `Static example for ${
- ServiceClass.name
- } at index ${index} also declares a dynamic previewUrl, which is not allowed`
- )
- }
- } else if (!previewUrl) {
- throw Error(
- `Example for ${
- ServiceClass.name
- } at index ${index} is missing required previewUrl or staticExample`
- )
- }
-
- return {
- title,
- query,
- namedParams,
- exampleUrl,
- previewUrl,
- pattern,
- staticExample,
- documentation,
- keywords,
- }
-}
diff --git a/services/validate-example.spec.js b/services/validate-example.spec.js
deleted file mode 100644
index 033785290e019..0000000000000
--- a/services/validate-example.spec.js
+++ /dev/null
@@ -1,45 +0,0 @@
-'use strict'
-
-const { expect } = require('chai')
-const validateExample = require('./validate-example')
-
-describe('validateExample function', function() {
- it('passes valid examples', function() {
- const validExamples = [
- { staticExample: {}, pattern: 'dt/:package', exampleUrl: 'dt/mypackage' },
- {
- staticExample: {},
- pattern: 'dt/:package',
- namedParams: { package: 'mypackage' },
- },
- { previewUrl: 'dt/mypackage' },
- ]
-
- validExamples.forEach(example => {
- expect(() =>
- validateExample(example, 0, { route: {}, name: 'mockService' })
- ).not.to.throw(Error)
- })
- })
-
- it('rejects invalid examples', function() {
- const invalidExamples = [
- {},
- { staticExample: {} },
- {
- staticExample: {},
- pattern: 'dt/:package',
- namedParams: { package: 'mypackage' },
- exampleUrl: 'dt/mypackage',
- },
- { staticExample: {}, pattern: 'dt/:package' },
- { staticExample: {}, pattern: 'dt/:package', previewUrl: 'dt/mypackage' },
- ]
-
- invalidExamples.forEach(example => {
- expect(() =>
- validateExample(example, 0, { route: {}, name: 'mockService' })
- ).to.throw(Error)
- })
- })
-})