- The goal of this PR is:
- Consume the new service-definition format. (#2397)
- Make the frontend more readable.
- Behavior changes:
- I changed the **Image** field in the markup modal to show only the path.
- I added another click-to-select field below that shows the complete URL.
- This made it easier to suppress the live badge preview while it contains placeholders like `:user` or `:gem`, a minor tweak discussed at https://github.com/badges/shields/issues/2427#issuecomment-442972100.
- The search box now searches all categories, regardless of the current page. (This is an improvement, I would say.)
- I did not deliberately address performance, though I ripped out a bunch of anonymous functions and avoided re-filtering all the examples by category on every render, which I expect will not hurt. I haven't really tested this on a mobile connection and it'd be worth doing that.
- It would be great to have some tests of the components, though getting started with that seemed like a big project and I did not want to make this any larger than it already is.
It's a medium-sized refactor:
1. Replace `BadgeExamples`, `Category` and `Badge` component with a completely rewritten `BadgeExamples` component which renders a table of badges, and `CategoryHeading` and `CategoryHeadings` components.
2. Refactor `ExamplesPage` and `SearchResults` components into a new `Main` component.
3. Rewrite the data flow for `MarkupModal`. Rather than rely on unmounting and remounting the component to copy the badge URL into state, employ the `getDerivedStateFromProps` lifecycle method.
4. Remove `prepareExamples` and `all-badge-examples`.
5. Rewrite the `$suggest` schema to harmonize with the service definition format. It's not backward-compatible which means at deploy time there probably will be 10–20 minutes of downtime on that feature, between the first server deploy and the final gh-pages deploy. 🤷♂️ (We could leave the old version in place if it seems worth it.)
6. Added two new functions in `make-badge-url` with tests. I removed _most_ of the uses of the old functions, but there are some in parts of the frontend I didn't touch like the static and dynamic badge generators, and again I didn't want to make this any larger than it already is.
7. Fix a couple bugs in the service-definition export.
140 lines
3.3 KiB
JavaScript
140 lines
3.3 KiB
JavaScript
'use strict'
|
|
|
|
const Joi = require('joi')
|
|
|
|
const optionalObjectOfKeyValues = Joi.object().pattern(
|
|
/./,
|
|
Joi.string().allow(null)
|
|
)
|
|
|
|
const optionalServiceData = Joi.object({
|
|
label: Joi.string(),
|
|
message: Joi.alternatives()
|
|
.try(
|
|
Joi.string()
|
|
.allow('')
|
|
.required(),
|
|
Joi.number()
|
|
)
|
|
.required(),
|
|
color: Joi.string(),
|
|
})
|
|
|
|
const schema = Joi.object({
|
|
// This should be:
|
|
// title: Joi.string().required(),
|
|
title: Joi.string(),
|
|
namedParams: optionalObjectOfKeyValues,
|
|
queryParams: optionalObjectOfKeyValues.default({}),
|
|
pattern: Joi.string(),
|
|
staticPreview: optionalServiceData,
|
|
previewUrl: Joi.string(),
|
|
exampleUrl: Joi.string(),
|
|
keywords: Joi.array()
|
|
.items(Joi.string())
|
|
.default([]),
|
|
documentation: Joi.string(), // Valid HTML.
|
|
})
|
|
.rename('staticExample', 'staticPreview', { ignoreUndefined: true })
|
|
.required()
|
|
|
|
function validateExample(example, index, ServiceClass) {
|
|
const result = Joi.attempt(
|
|
example,
|
|
schema,
|
|
`Example for ${ServiceClass.name} at index ${index}`
|
|
)
|
|
|
|
const { namedParams, pattern, staticPreview, previewUrl, exampleUrl } = result
|
|
|
|
if (staticPreview) {
|
|
if (!pattern && !ServiceClass.route.pattern) {
|
|
throw new Error(
|
|
`Static preview for ${
|
|
ServiceClass.name
|
|
} at index ${index} does not declare a pattern`
|
|
)
|
|
}
|
|
if (namedParams && exampleUrl) {
|
|
throw new Error(
|
|
`Static preview for ${
|
|
ServiceClass.name
|
|
} at index ${index} declares both namedParams and exampleUrl`
|
|
)
|
|
} else if (!namedParams && !exampleUrl) {
|
|
throw new Error(
|
|
`Static preview for ${
|
|
ServiceClass.name
|
|
} at index ${index} does not declare namedParams nor exampleUrl`
|
|
)
|
|
}
|
|
if (previewUrl) {
|
|
throw new Error(
|
|
`Static preview 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 staticPreview`
|
|
)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
function transformExample(inExample, index, ServiceClass) {
|
|
const {
|
|
// We should get rid of this transform, since the class name is never what
|
|
// we want to see.
|
|
title = ServiceClass.name,
|
|
namedParams,
|
|
queryParams,
|
|
pattern,
|
|
staticPreview,
|
|
previewUrl,
|
|
exampleUrl,
|
|
keywords,
|
|
documentation,
|
|
} = validateExample(inExample, index, ServiceClass)
|
|
|
|
let example
|
|
if (namedParams) {
|
|
example = {
|
|
pattern: ServiceClass._makeFullUrl(pattern || ServiceClass.route.pattern),
|
|
namedParams,
|
|
queryParams,
|
|
}
|
|
} else {
|
|
example = {
|
|
path: ServiceClass._makeFullUrl(exampleUrl || previewUrl),
|
|
queryParams,
|
|
}
|
|
}
|
|
|
|
let preview
|
|
if (staticPreview) {
|
|
const badgeData = ServiceClass._makeBadgeData({}, staticPreview)
|
|
preview = {
|
|
label: badgeData.text[0],
|
|
message: `${badgeData.text[1]}`,
|
|
color: badgeData.colorscheme || badgeData.colorB,
|
|
}
|
|
} else {
|
|
preview = {
|
|
path: ServiceClass._makeFullUrl(previewUrl),
|
|
queryParams,
|
|
}
|
|
}
|
|
|
|
return { title, example, preview, keywords, documentation }
|
|
}
|
|
|
|
module.exports = {
|
|
validateExample,
|
|
transformExample,
|
|
}
|