You've already forked dynamic-badges-action
Add save svg-badge directly to gist
This adds the posibility of saving an SVG badge generated by the same shields.io dirictly to the gist. Instead of prepering a JSON file to be sent to their service, we use their library directly, which outputs an SVG file that we can save to the user’s gist. Filenames ending in `.svg` will use this library automatically. Additionally there is a major refactoring where the older `node:http` library has been swapped out for `fetch`. Also swap from node 16 to node 20 fixes #24
This commit is contained in:
277
node_modules/badge-maker/CHANGELOG.md
generated
vendored
Normal file
277
node_modules/badge-maker/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,277 @@
|
||||
# Changelog
|
||||
|
||||
## 3.3.1
|
||||
|
||||
- Improve font measuring in for-the-badge and social styles
|
||||
- Make for-the-badge letter spacing more predictable
|
||||
|
||||
## 3.3.0
|
||||
|
||||
- Readability improvements: a dark font color is automatically used when the badge's background is too light. For example: 
|
||||
- Better CSS color compliance: thanks to a switch from _is-css-color_ to _[css-color-converter](https://www.npmjs.com/package/css-color-converter)_, you can use a wider range of color formats from the latest CSS specification, for example `rgb(0 255 0)`
|
||||
- Less dependencies: _badge-maker_ no longer depends on _camelcase_
|
||||
|
||||
## 3.2.0
|
||||
|
||||
- Accessibility improvements: Help users of assistive technologies to read the badges when used inline
|
||||
|
||||
## 3.1.0
|
||||
|
||||
- Add TypeScript definitions
|
||||
|
||||
## 3.0.1
|
||||
|
||||
- Fix missing dependency
|
||||
|
||||
## 3.0.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Dropped support for node < 10
|
||||
- Package name has changed to `badge-maker` and moved to https://www.npmjs.com/package/badge-maker
|
||||
- `BadgeFactory` class is removed and replaced by `makeBadge()` function.
|
||||
- Deprecated parameters have been removed. In version 2.2.0 the `colorA`, `colorB` and `colorscheme` params were deprecated. In version 3.0.0 these have been removed.
|
||||
- Only SVG output format is now provided. JSON format has been dropped and the `format` key has been removed.
|
||||
- The `text` array has been replaced by `label` and `message` keys.
|
||||
- The `template` key has been renamed `style`.
|
||||
To upgrade from v2.1.1, change your code from:
|
||||
```js
|
||||
const { BadgeFactory } = require('gh-badges')
|
||||
const bf = new BadgeFactory()
|
||||
const svg = bf.create({
|
||||
text: ['build', 'passed'],
|
||||
format: 'svg',
|
||||
template: 'flat-square',
|
||||
})
|
||||
```
|
||||
to:
|
||||
```js
|
||||
const { makeBadge } = require('badge-maker')
|
||||
const svg = makeBadge({
|
||||
label: 'build',
|
||||
message: 'passed',
|
||||
style: 'flat-square',
|
||||
})
|
||||
```
|
||||
- `ValidationError` had been added and inputs are now validated. In previous releases, invalid inputs would be discarded and replaced with defaults. For example, in 2.2.1
|
||||
```js
|
||||
const { BadgeFactory } = require('gh-badges')
|
||||
const bf = new BadgeFactory()
|
||||
const svg = bf.create({
|
||||
text: ['build', 'passed'],
|
||||
template: 'some invalid value',
|
||||
})
|
||||
```
|
||||
would generate an SVG badge. In version >=3
|
||||
```js
|
||||
const { makeBadge } = require('badge-maker')
|
||||
const svg = makeBadge({
|
||||
label: 'build',
|
||||
message: 'passed',
|
||||
style: 'some invalid value',
|
||||
})
|
||||
```
|
||||
will throw a `ValidationError`.
|
||||
- Raster support has been removed from the CLI. It will now only output SVG. On the console, the output of `badge` can be piped to a utility like [imagemagick](https://imagemagick.org/script/command-line-processing.php). If you were previously using
|
||||
```sh
|
||||
badge build passed :green .gif
|
||||
```
|
||||
this could be replaced by
|
||||
```sh
|
||||
badge build passed :green | magick svg:- gif:-
|
||||
```
|
||||
|
||||
### Security
|
||||
|
||||
- Removed dependency on doT library which has known vulnerabilities.
|
||||
|
||||
## 2.2.1 - 2019-05-30
|
||||
|
||||
### Fixes
|
||||
|
||||
- Escape logos to prevent XSS vulnerability
|
||||
- Update docblock for BadgeFactory.create()
|
||||
|
||||
## 2.2.0 - 2019-05-29
|
||||
|
||||
### Deprecations
|
||||
|
||||
- `labelColor` and `color` are now the recommended attribute names for label color and message color.
|
||||
|
||||
- `colorA` (now an alias for `labelColor`),
|
||||
- `colorB` (now an alias for `color`) and
|
||||
- `colorscheme` (now an alias for `color`)
|
||||
|
||||
are now deprecated and will be removed in some future release.
|
||||
|
||||
### New Features
|
||||
|
||||
- Semantic color aliases. Add support for:
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
- Add directory field to package.json (to help tools find this package in the repo)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Prevent bad letter spacing when whitespace surrounds badge text
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Bump anafanafo
|
||||
- Use caret instead of tilde for dependencies
|
||||
|
||||
### Internals
|
||||
|
||||
- Generate JSON badges without using a template
|
||||
- Refactoring
|
||||
- Testing improvements
|
||||
|
||||
### Node support
|
||||
|
||||
- Declare support for all currently maintained Node versions
|
||||
- Explicitly test on all supported versions
|
||||
|
||||
## 2.1.0 - 2018-11-18
|
||||
|
||||
gh-badges v2.1.0 implements a new text width measurer which uses a lookup table, removing the dependency
|
||||
on PDFKit. It is no longer necessary to provide a local copy of Verdana for accurate text width computation.
|
||||
|
||||
As such, the `fontPath` and `precomputeWidths` parameters are now deprecated. The recommended call to create an instance of `BadgeFactory` is now
|
||||
|
||||
```js
|
||||
const bf = new BadgeFactory()
|
||||
```
|
||||
|
||||
For backwards compatibility you can still construct an instance of `BadgeFactory` with a call like
|
||||
|
||||
```js
|
||||
const bf = new BadgeFactory({
|
||||
fontPath: '/path/to/Verdana.ttf',
|
||||
precomputeWidths: true,
|
||||
})
|
||||
```
|
||||
|
||||
However, the function will issue a warning.
|
||||
|
||||
To clear the warning, change the code to:
|
||||
|
||||
```js
|
||||
const bf = new BadgeFactory()
|
||||
```
|
||||
|
||||
These arguments will be removed in a future release.
|
||||
|
||||
To upgrade from v1.3.0, change your code from:
|
||||
|
||||
```js
|
||||
const badge = require('gh-badges')
|
||||
|
||||
const format = {
|
||||
text: ['build', 'passed'],
|
||||
colorscheme: 'green',
|
||||
template: 'flat',
|
||||
}
|
||||
|
||||
badge.loadFont('/path/to/Verdana.ttf', err => {
|
||||
badge(format, (svg, err) => {
|
||||
// svg is a string containing your badge
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```js
|
||||
const { BadgeFactory } = require('gh-badges')
|
||||
|
||||
const bf = new BadgeFactory()
|
||||
|
||||
const format = {
|
||||
text: ['build', 'passed'],
|
||||
colorscheme: 'green',
|
||||
template: 'flat',
|
||||
}
|
||||
|
||||
const svg = bf.create(format)
|
||||
```
|
||||
|
||||
### Other changes in this release:
|
||||
|
||||
- Remove unnecessary dependencies
|
||||
- Documentation improvements
|
||||
|
||||
## 2.0.0 - 2018-11-09
|
||||
|
||||
gh-badges v2.0.0 declares a new public interface which is synchronous.
|
||||
If your version 1.3.0 code looked like this:
|
||||
|
||||
```js
|
||||
const badge = require('gh-badges')
|
||||
|
||||
const format = {
|
||||
text: ['build', 'passed'],
|
||||
colorscheme: 'green',
|
||||
template: 'flat',
|
||||
}
|
||||
|
||||
badge.loadFont('/path/to/Verdana.ttf', err => {
|
||||
badge(format, (svg, err) => {
|
||||
// svg is a string containing your badge
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
To upgrade to version 2.0.0, refactor you code to:
|
||||
|
||||
```js
|
||||
const { BadgeFactory } = require('gh-badges')
|
||||
|
||||
const bf = new BadgeFactory({ fontPath: '/path/to/Verdana.ttf' })
|
||||
|
||||
const format = {
|
||||
text: ['build', 'passed'],
|
||||
colorscheme: 'green',
|
||||
template: 'flat',
|
||||
}
|
||||
|
||||
const svg = bf.create(format)
|
||||
```
|
||||
|
||||
You can generate badges without a copy of Verdana, however font width computation is approximate and badges may be distorted.
|
||||
|
||||
```js
|
||||
const bf = new BadgeFactory({ fallbackFontPath: 'Helvetica' })
|
||||
```
|
||||
|
||||
## 1.3.0 - 2016-09-07
|
||||
|
||||
Add support for optionally specifying the path to `Verdana.ttf`. In earlier versions, the file needed to be in the directory containing Shields.
|
||||
|
||||
Without font path:
|
||||
|
||||
```js
|
||||
const badge = require('gh-badges')
|
||||
|
||||
badge({ text: ['build', 'passed'], colorscheme: 'green' }, (svg, err) => {
|
||||
// svg is a string containing your badge
|
||||
})
|
||||
```
|
||||
|
||||
With font path:
|
||||
|
||||
```js
|
||||
const badge = require('gh-badges')
|
||||
|
||||
// Optional step, to have accurate text width computation.
|
||||
badge.loadFont('/path/to/Verdana.ttf', err => {
|
||||
badge(
|
||||
{ text: ['build', 'passed'], colorscheme: 'green', template: 'flat' },
|
||||
(svg, err) => {
|
||||
// svg is a string containing your badge
|
||||
}
|
||||
)
|
||||
})
|
||||
```
|
||||
116
node_modules/badge-maker/LICENSE
generated
vendored
Normal file
116
node_modules/badge-maker/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
CC0 1.0 Universal
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator and
|
||||
subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||
purpose of contributing to a commons of creative, cultural and scientific
|
||||
works ("Commons") that the public can reliably and without fear of later
|
||||
claims of infringement build upon, modify, incorporate in other works, reuse
|
||||
and redistribute as freely as possible in any form whatsoever and for any
|
||||
purposes, including without limitation commercial purposes. These owners may
|
||||
contribute to the Commons to promote the ideal of a free culture and the
|
||||
further production of creative, cultural and scientific works, or to gain
|
||||
reputation or greater distribution for their Work in part through the use and
|
||||
efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any expectation
|
||||
of additional consideration or compensation, the person associating CC0 with a
|
||||
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
|
||||
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
|
||||
and publicly distribute the Work under its terms, with knowledge of his or her
|
||||
Copyright and Related Rights in the Work and the meaning and intended legal
|
||||
effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not limited
|
||||
to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display, communicate,
|
||||
and translate a Work;
|
||||
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
|
||||
iii. publicity and privacy rights pertaining to a person's image or likeness
|
||||
depicted in a Work;
|
||||
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data in
|
||||
a Work;
|
||||
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation thereof,
|
||||
including any amended or successor version of such directive); and
|
||||
|
||||
vii. other similar, equivalent or corresponding rights throughout the world
|
||||
based on applicable law or treaty, and any national implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
|
||||
and Related Rights and associated claims and causes of action, whether now
|
||||
known or unknown (including existing as well as future claims and causes of
|
||||
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||
duration provided by applicable law or treaty (including future time
|
||||
extensions), (iii) in any current or future medium and for any number of
|
||||
copies, and (iv) for any purpose whatsoever, including without limitation
|
||||
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
|
||||
the Waiver for the benefit of each member of the public at large and to the
|
||||
detriment of Affirmer's heirs and successors, fully intending that such Waiver
|
||||
shall not be subject to revocation, rescission, cancellation, termination, or
|
||||
any other legal or equitable action to disrupt the quiet enjoyment of the Work
|
||||
by the public as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||
shall be preserved to the maximum extent permitted taking into account
|
||||
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
|
||||
is so judged Affirmer hereby grants to each affected person a royalty-free,
|
||||
non transferable, non sublicensable, non exclusive, irrevocable and
|
||||
unconditional license to exercise Affirmer's Copyright and Related Rights in
|
||||
the Work (i) in all territories worldwide, (ii) for the maximum duration
|
||||
provided by applicable law or treaty (including future time extensions), (iii)
|
||||
in any current or future medium and for any number of copies, and (iv) for any
|
||||
purpose whatsoever, including without limitation commercial, advertising or
|
||||
promotional purposes (the "License"). The License shall be deemed effective as
|
||||
of the date CC0 was applied by Affirmer to the Work. Should any part of the
|
||||
License for any reason be judged legally invalid or ineffective under
|
||||
applicable law, such partial invalidity or ineffectiveness shall not
|
||||
invalidate the remainder of the License, and in such case Affirmer hereby
|
||||
affirms that he or she will not (i) exercise any of his or her remaining
|
||||
Copyright and Related Rights in the Work or (ii) assert any associated claims
|
||||
and causes of action with respect to the Work, in either case contrary to
|
||||
Affirmer's express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
|
||||
b. Affirmer offers the Work as-is and makes no representations or warranties
|
||||
of any kind concerning the Work, express, implied, statutory or otherwise,
|
||||
including without limitation warranties of title, merchantability, fitness
|
||||
for a particular purpose, non infringement, or the absence of latent or
|
||||
other defects, accuracy, or the present or absence of errors, whether or not
|
||||
discoverable, all to the greatest extent permissible under applicable law.
|
||||
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without limitation
|
||||
any person's Copyright and Related Rights in the Work. Further, Affirmer
|
||||
disclaims responsibility for obtaining any necessary consents, permissions
|
||||
or other rights required for any use of the Work.
|
||||
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to this
|
||||
CC0 or use of the Work.
|
||||
|
||||
For more information, please see
|
||||
<http://creativecommons.org/publicdomain/zero/1.0/>
|
||||
142
node_modules/badge-maker/README.md
generated
vendored
Normal file
142
node_modules/badge-maker/README.md
generated
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
# badge-maker
|
||||
|
||||
[](https://npmjs.org/package/badge-maker)
|
||||
[](https://npmjs.org/package/badge-maker)
|
||||
[](https://npmjs.org/package/badge-maker)
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install badge-maker
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### On the console
|
||||
|
||||
```sh
|
||||
npm install -g badge-maker
|
||||
badge build passed :green > mybadge.svg
|
||||
```
|
||||
|
||||
### As a library
|
||||
|
||||
With CommonJS in JavaScript,
|
||||
|
||||
```js
|
||||
const { makeBadge, ValidationError } = require('badge-maker')
|
||||
```
|
||||
|
||||
With ESM or TypeScript,
|
||||
|
||||
```ts
|
||||
import { makeBadge, ValidationError } from 'badge-maker'
|
||||
```
|
||||
|
||||
```js
|
||||
const format = {
|
||||
label: 'build',
|
||||
message: 'passed',
|
||||
color: 'green',
|
||||
}
|
||||
|
||||
const svg = makeBadge(format)
|
||||
console.log(svg) // <svg...
|
||||
|
||||
try {
|
||||
makeBadge({})
|
||||
} catch (e) {
|
||||
console.log(e) // ValidationError: Field `message` is required
|
||||
}
|
||||
```
|
||||
|
||||
### Node version support
|
||||
|
||||
The latest version of badge-maker supports all currently maintained Node
|
||||
versions. See the [Node Release Schedule][].
|
||||
|
||||
[node release schedule]: https://github.com/nodejs/Release#release-schedule
|
||||
|
||||
## Format
|
||||
|
||||
The format is the following:
|
||||
|
||||
```js
|
||||
{
|
||||
label: 'build', // (Optional) Badge label
|
||||
message: 'passed', // (Required) Badge message
|
||||
labelColor: '#555', // (Optional) Label color
|
||||
color: '#4c1', // (Optional) Message color
|
||||
|
||||
// (Optional) One of: 'plastic', 'flat', 'flat-square', 'for-the-badge' or 'social'
|
||||
// Each offers a different visual design.
|
||||
style: 'flat',
|
||||
}
|
||||
```
|
||||
|
||||
## Colors
|
||||
|
||||
There are three ways to specify `color` and `labelColor`:
|
||||
|
||||
1. One of the [Shields named colors](./lib/color.js):
|
||||
|
||||
- ![][brightgreen]
|
||||
- ![][green]
|
||||
- ![][yellow]
|
||||
- ![][yellowgreen]
|
||||
- ![][orange]
|
||||
- ![][red]
|
||||
- ![][blue]
|
||||
- ![][grey] ![][gray] – the default `labelColor`
|
||||
- ![][lightgrey] ![][lightgray] – the default `color`
|
||||
|
||||
- ![][success]
|
||||
- ![][important]
|
||||
- ![][critical]
|
||||
- ![][informational]
|
||||
- ![][inactive] – the default `color`
|
||||
|
||||
2. A three- or six-character hex color, optionally prefixed with `#`:
|
||||
|
||||
- ![][9cf]
|
||||
- ![][#007fff]
|
||||
- etc.
|
||||
|
||||
3. [Any valid CSS color][css color], e.g.
|
||||
|
||||
- `rgb(...)`, `rgba(...)`
|
||||
- `hsl(...)`, `hsla(...)`
|
||||
- ![][aqua] ![][fuchsia] ![][lightslategray] etc.
|
||||
|
||||
[brightgreen]: https://img.shields.io/badge/brightgreen-brightgreen.svg
|
||||
[success]: https://img.shields.io/badge/success-success.svg
|
||||
[green]: https://img.shields.io/badge/green-green.svg
|
||||
[yellow]: https://img.shields.io/badge/yellow-yellow.svg
|
||||
[yellowgreen]: https://img.shields.io/badge/yellowgreen-yellowgreen.svg
|
||||
[orange]: https://img.shields.io/badge/orange-orange.svg
|
||||
[important]: https://img.shields.io/badge/important-important.svg
|
||||
[red]: https://img.shields.io/badge/red-red.svg
|
||||
[critical]: https://img.shields.io/badge/critical-critical.svg
|
||||
[blue]: https://img.shields.io/badge/blue-blue.svg
|
||||
[informational]: https://img.shields.io/badge/informational-informational.svg
|
||||
[grey]: https://img.shields.io/badge/grey-grey.svg
|
||||
[gray]: https://img.shields.io/badge/gray-gray.svg
|
||||
[lightgrey]: https://img.shields.io/badge/lightgrey-lightgrey.svg
|
||||
[lightgray]: https://img.shields.io/badge/lightgray-lightgray.svg
|
||||
[inactive]: https://img.shields.io/badge/inactive-inactive.svg
|
||||
[9cf]: https://img.shields.io/badge/9cf-9cf.svg
|
||||
[#007fff]: https://img.shields.io/badge/%23007fff-007fff.svg
|
||||
[aqua]: https://img.shields.io/badge/aqua-aqua.svg
|
||||
[fuchsia]: https://img.shields.io/badge/fuchsia-fuchsia.svg
|
||||
[lightslategray]: https://img.shields.io/badge/lightslategray-lightslategray.svg
|
||||
[css color]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
|
||||
[css/svg color]: http://www.w3.org/TR/SVG/types.html#DataTypeColor
|
||||
|
||||
## Raster Formats
|
||||
|
||||
Conversion to raster formats is no longer directly supported. In javascript
|
||||
code, SVG badges can be converted to raster formats using a library like
|
||||
[gm](https://www.npmjs.com/package/gm). On the console, the output of `badge`
|
||||
can be piped to a utility like
|
||||
[imagemagick](https://imagemagick.org/script/command-line-processing.php)
|
||||
e.g: `badge build passed :green | magick svg:- gif:-`.
|
||||
11
node_modules/badge-maker/index.d.ts
generated
vendored
Normal file
11
node_modules/badge-maker/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
interface Format {
|
||||
label?: string
|
||||
message: string
|
||||
labelColor?: string
|
||||
color?: string
|
||||
style?: 'plastic' | 'flat' | 'flat-square' | 'for-the-badge' | 'social'
|
||||
}
|
||||
|
||||
export declare class ValidationError extends Error {}
|
||||
|
||||
export declare function makeBadge(format: Format): string
|
||||
65
node_modules/badge-maker/lib/badge-cli.js
generated
vendored
Executable file
65
node_modules/badge-maker/lib/badge-cli.js
generated
vendored
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict'
|
||||
|
||||
const { namedColors } = require('./color')
|
||||
const { makeBadge } = require('./index')
|
||||
|
||||
if (process.argv.length < 4) {
|
||||
console.log('Usage: badge label message [:color] [@style]')
|
||||
console.log('Or: badge label message color [labelColor] [@style]')
|
||||
console.log()
|
||||
console.log(' color, labelColor:')
|
||||
console.log(` one of ${Object.keys(namedColors).join(', ')}.`)
|
||||
console.log(' #xxx (three hex digits)')
|
||||
console.log(' #xxxxxx (six hex digits)')
|
||||
console.log(' color (CSS color)')
|
||||
console.log()
|
||||
console.log('Eg: badge cactus grown :green @flat')
|
||||
console.log()
|
||||
process.exit()
|
||||
}
|
||||
|
||||
// Find a format specifier.
|
||||
let style = ''
|
||||
for (let i = 4; i < process.argv.length; i++) {
|
||||
if (process.argv[i][0] === '@') {
|
||||
style = process.argv[i].slice(1)
|
||||
process.argv.splice(i, 1)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const label = process.argv[2]
|
||||
const message = process.argv[3]
|
||||
let color = process.argv[4] || ':green'
|
||||
const labelColor = process.argv[5]
|
||||
|
||||
const badgeData = { label, message }
|
||||
if (style) {
|
||||
badgeData.style = style
|
||||
}
|
||||
|
||||
if (color[0] === ':') {
|
||||
color = color.slice(1)
|
||||
if (namedColors[color] == null) {
|
||||
// Colorscheme not found.
|
||||
console.error('Invalid color scheme.')
|
||||
process.exit(1)
|
||||
}
|
||||
badgeData.color = color
|
||||
} else {
|
||||
badgeData.color = color
|
||||
if (labelColor) {
|
||||
badgeData.labelColor = labelColor
|
||||
}
|
||||
}
|
||||
|
||||
;(() => {
|
||||
try {
|
||||
console.log(makeBadge(badgeData))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
}
|
||||
})()
|
||||
807
node_modules/badge-maker/lib/badge-renderers.js
generated
vendored
Normal file
807
node_modules/badge-maker/lib/badge-renderers.js
generated
vendored
Normal file
@@ -0,0 +1,807 @@
|
||||
'use strict'
|
||||
|
||||
const anafanafo = require('anafanafo')
|
||||
const { brightness } = require('./color')
|
||||
const { XmlElement, escapeXml } = require('./xml')
|
||||
|
||||
// https://github.com/badges/shields/pull/1132
|
||||
const FONT_SCALE_UP_FACTOR = 10
|
||||
const FONT_SCALE_DOWN_VALUE = 'scale(.1)'
|
||||
|
||||
const FONT_FAMILY = 'Verdana,Geneva,DejaVu Sans,sans-serif'
|
||||
const fontFamily = `font-family="${FONT_FAMILY}"`
|
||||
const socialFontFamily =
|
||||
'font-family="Helvetica Neue,Helvetica,Arial,sans-serif"'
|
||||
const brightnessThreshold = 0.69
|
||||
|
||||
function capitalize(s) {
|
||||
return `${s.charAt(0).toUpperCase()}${s.slice(1)}`
|
||||
}
|
||||
|
||||
function colorsForBackground(color) {
|
||||
if (brightness(color) <= brightnessThreshold) {
|
||||
return { textColor: '#fff', shadowColor: '#010101' }
|
||||
} else {
|
||||
return { textColor: '#333', shadowColor: '#ccc' }
|
||||
}
|
||||
}
|
||||
|
||||
function roundUpToOdd(val) {
|
||||
return val % 2 === 0 ? val + 1 : val
|
||||
}
|
||||
|
||||
function preferredWidthOf(str, options) {
|
||||
// Increase chances of pixel grid alignment.
|
||||
return roundUpToOdd(anafanafo(str, options) | 0)
|
||||
}
|
||||
|
||||
function createAccessibleText({ label, message }) {
|
||||
const labelPrefix = label ? `${label}: ` : ''
|
||||
return labelPrefix + message
|
||||
}
|
||||
|
||||
function hasLinks({ links }) {
|
||||
const [leftLink, rightLink] = links || []
|
||||
const hasLeftLink = leftLink && leftLink.length
|
||||
const hasRightLink = rightLink && rightLink.length
|
||||
const hasLink = hasLeftLink && hasRightLink
|
||||
return { hasLink, hasLeftLink, hasRightLink }
|
||||
}
|
||||
|
||||
function shouldWrapBodyWithLink({ links }) {
|
||||
const { hasLeftLink, hasRightLink } = hasLinks({ links })
|
||||
return hasLeftLink && !hasRightLink
|
||||
}
|
||||
|
||||
function renderAriaAttributes({ accessibleText, links }) {
|
||||
const { hasLink } = hasLinks({ links })
|
||||
return hasLink ? '' : `role="img" aria-label="${escapeXml(accessibleText)}"`
|
||||
}
|
||||
|
||||
function renderTitle({ accessibleText, links }) {
|
||||
const { hasLink } = hasLinks({ links })
|
||||
return hasLink ? '' : `<title>${escapeXml(accessibleText)}</title>`
|
||||
}
|
||||
|
||||
function renderLogo({
|
||||
logo,
|
||||
badgeHeight,
|
||||
horizPadding,
|
||||
logoWidth = 14,
|
||||
logoPadding = 0,
|
||||
}) {
|
||||
if (logo) {
|
||||
const logoHeight = 14
|
||||
const y = (badgeHeight - logoHeight) / 2
|
||||
const x = horizPadding
|
||||
return {
|
||||
hasLogo: true,
|
||||
totalLogoWidth: logoWidth + logoPadding,
|
||||
renderedLogo: `<image x="${x}" y="${y}" width="${logoWidth}" height="${logoHeight}" xlink:href="${escapeXml(
|
||||
logo
|
||||
)}"/>`,
|
||||
}
|
||||
} else {
|
||||
return { hasLogo: false, totalLogoWidth: 0, renderedLogo: '' }
|
||||
}
|
||||
}
|
||||
|
||||
function renderLink({
|
||||
link,
|
||||
height,
|
||||
textLength,
|
||||
horizPadding,
|
||||
leftMargin,
|
||||
renderedText,
|
||||
}) {
|
||||
const rectHeight = height
|
||||
const rectWidth = textLength + horizPadding * 2
|
||||
const rectX = leftMargin > 1 ? leftMargin + 1 : 0
|
||||
return `<a target="_blank" xlink:href="${escapeXml(link)}">
|
||||
<rect width="${rectWidth}" x="${rectX}" height="${rectHeight}" fill="rgba(0,0,0,0)" />
|
||||
${renderedText}
|
||||
</a>`
|
||||
}
|
||||
|
||||
function renderText({
|
||||
leftMargin,
|
||||
horizPadding = 0,
|
||||
content,
|
||||
link,
|
||||
height,
|
||||
verticalMargin = 0,
|
||||
shadow = false,
|
||||
color,
|
||||
}) {
|
||||
if (!content.length) {
|
||||
return { renderedText: '', width: 0 }
|
||||
}
|
||||
|
||||
const textLength = preferredWidthOf(content, { font: '11px Verdana' })
|
||||
const escapedContent = escapeXml(content)
|
||||
|
||||
const shadowMargin = 150 + verticalMargin
|
||||
const textMargin = 140 + verticalMargin
|
||||
|
||||
const outTextLength = 10 * textLength
|
||||
const x = 10 * (leftMargin + 0.5 * textLength + horizPadding)
|
||||
|
||||
let renderedText = ''
|
||||
const { textColor, shadowColor } = colorsForBackground(color)
|
||||
if (shadow) {
|
||||
renderedText = `<text aria-hidden="true" x="${x}" y="${shadowMargin}" fill="${shadowColor}" fill-opacity=".3" transform="scale(.1)" textLength="${outTextLength}">${escapedContent}</text>`
|
||||
}
|
||||
renderedText += `<text x="${x}" y="${textMargin}" transform="scale(.1)" fill="${textColor}" textLength="${outTextLength}">${escapedContent}</text>`
|
||||
|
||||
return {
|
||||
renderedText: link
|
||||
? renderLink({
|
||||
link,
|
||||
height,
|
||||
textLength,
|
||||
horizPadding,
|
||||
leftMargin,
|
||||
renderedText,
|
||||
})
|
||||
: renderedText,
|
||||
width: textLength,
|
||||
}
|
||||
}
|
||||
|
||||
function renderBadge(
|
||||
{ links, leftWidth, rightWidth, height, accessibleText },
|
||||
main
|
||||
) {
|
||||
const width = leftWidth + rightWidth
|
||||
const leftLink = escapeXml(links[0])
|
||||
|
||||
return `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}" ${renderAriaAttributes(
|
||||
{ links, accessibleText }
|
||||
)}>
|
||||
|
||||
${renderTitle({ accessibleText, links })}
|
||||
${
|
||||
shouldWrapBodyWithLink({ links })
|
||||
? `<a target="_blank" xlink:href="${leftLink}">${main}</a>`
|
||||
: main
|
||||
}
|
||||
</svg>`
|
||||
}
|
||||
|
||||
class Badge {
|
||||
static get fontFamily() {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
static get height() {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
static get verticalMargin() {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
static get shadow() {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
constructor({
|
||||
label,
|
||||
message,
|
||||
links,
|
||||
logo,
|
||||
logoWidth,
|
||||
logoPadding,
|
||||
color = '#4c1',
|
||||
labelColor,
|
||||
}) {
|
||||
const horizPadding = 5
|
||||
const { hasLogo, totalLogoWidth, renderedLogo } = renderLogo({
|
||||
logo,
|
||||
badgeHeight: this.constructor.height,
|
||||
horizPadding,
|
||||
logoWidth,
|
||||
logoPadding,
|
||||
})
|
||||
const hasLabel = label.length || labelColor
|
||||
if (labelColor == null) {
|
||||
labelColor = '#555'
|
||||
}
|
||||
|
||||
const [leftLink, rightLink] = links
|
||||
|
||||
labelColor = hasLabel || hasLogo ? labelColor : color
|
||||
labelColor = escapeXml(labelColor)
|
||||
color = escapeXml(color)
|
||||
|
||||
const labelMargin = totalLogoWidth + 1
|
||||
|
||||
const { renderedText: renderedLabel, width: labelWidth } = renderText({
|
||||
leftMargin: labelMargin,
|
||||
horizPadding,
|
||||
content: label,
|
||||
link: !shouldWrapBodyWithLink({ links }) && leftLink,
|
||||
height: this.constructor.height,
|
||||
verticalMargin: this.constructor.verticalMargin,
|
||||
shadow: this.constructor.shadow,
|
||||
color: labelColor,
|
||||
})
|
||||
|
||||
const leftWidth = hasLabel
|
||||
? labelWidth + 2 * horizPadding + totalLogoWidth
|
||||
: 0
|
||||
|
||||
let messageMargin = leftWidth - (message.length ? 1 : 0)
|
||||
if (!hasLabel) {
|
||||
if (hasLogo) {
|
||||
messageMargin = messageMargin + totalLogoWidth + horizPadding
|
||||
} else {
|
||||
messageMargin = messageMargin + 1
|
||||
}
|
||||
}
|
||||
|
||||
const { renderedText: renderedMessage, width: messageWidth } = renderText({
|
||||
leftMargin: messageMargin,
|
||||
horizPadding,
|
||||
content: message,
|
||||
link: rightLink,
|
||||
height: this.constructor.height,
|
||||
verticalMargin: this.constructor.verticalMargin,
|
||||
shadow: this.constructor.shadow,
|
||||
color,
|
||||
})
|
||||
|
||||
let rightWidth = messageWidth + 2 * horizPadding
|
||||
if (hasLogo && !hasLabel) {
|
||||
rightWidth += totalLogoWidth + horizPadding - 1
|
||||
}
|
||||
|
||||
const width = leftWidth + rightWidth
|
||||
|
||||
const accessibleText = createAccessibleText({ label, message })
|
||||
|
||||
this.links = links
|
||||
this.leftWidth = leftWidth
|
||||
this.rightWidth = rightWidth
|
||||
this.width = width
|
||||
this.labelColor = labelColor
|
||||
this.color = color
|
||||
this.label = label
|
||||
this.message = message
|
||||
this.accessibleText = accessibleText
|
||||
this.renderedLogo = renderedLogo
|
||||
this.renderedLabel = renderedLabel
|
||||
this.renderedMessage = renderedMessage
|
||||
}
|
||||
|
||||
static render(params) {
|
||||
return new this(params).render()
|
||||
}
|
||||
|
||||
render() {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
}
|
||||
|
||||
class Plastic extends Badge {
|
||||
static get fontFamily() {
|
||||
return fontFamily
|
||||
}
|
||||
|
||||
static get height() {
|
||||
return 18
|
||||
}
|
||||
|
||||
static get verticalMargin() {
|
||||
return -10
|
||||
}
|
||||
|
||||
static get shadow() {
|
||||
return true
|
||||
}
|
||||
|
||||
render() {
|
||||
return renderBadge(
|
||||
{
|
||||
links: this.links,
|
||||
leftWidth: this.leftWidth,
|
||||
rightWidth: this.rightWidth,
|
||||
accessibleText: this.accessibleText,
|
||||
height: this.constructor.height,
|
||||
},
|
||||
`
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#fff" stop-opacity=".7"/>
|
||||
<stop offset=".1" stop-color="#aaa" stop-opacity=".1"/>
|
||||
<stop offset=".9" stop-color="#000" stop-opacity=".3"/>
|
||||
<stop offset="1" stop-color="#000" stop-opacity=".5"/>
|
||||
</linearGradient>
|
||||
|
||||
<clipPath id="r">
|
||||
<rect width="${this.width}" height="${this.constructor.height}" rx="4" fill="#fff"/>
|
||||
</clipPath>
|
||||
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="${this.leftWidth}" height="${this.constructor.height}" fill="${this.labelColor}"/>
|
||||
<rect x="${this.leftWidth}" width="${this.rightWidth}" height="${this.constructor.height}" fill="${this.color}"/>
|
||||
<rect width="${this.width}" height="${this.constructor.height}" fill="url(#s)"/>
|
||||
</g>
|
||||
|
||||
<g fill="#fff" text-anchor="middle" ${this.constructor.fontFamily} text-rendering="geometricPrecision" font-size="110">
|
||||
${this.renderedLogo}
|
||||
${this.renderedLabel}
|
||||
${this.renderedMessage}
|
||||
</g>`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Flat extends Badge {
|
||||
static get fontFamily() {
|
||||
return fontFamily
|
||||
}
|
||||
|
||||
static get height() {
|
||||
return 20
|
||||
}
|
||||
|
||||
static get verticalMargin() {
|
||||
return 0
|
||||
}
|
||||
|
||||
static get shadow() {
|
||||
return true
|
||||
}
|
||||
|
||||
render() {
|
||||
return renderBadge(
|
||||
{
|
||||
links: this.links,
|
||||
leftWidth: this.leftWidth,
|
||||
rightWidth: this.rightWidth,
|
||||
accessibleText: this.accessibleText,
|
||||
height: this.constructor.height,
|
||||
},
|
||||
`
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset="1" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
|
||||
<clipPath id="r">
|
||||
<rect width="${this.width}" height="${this.constructor.height}" rx="3" fill="#fff"/>
|
||||
</clipPath>
|
||||
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="${this.leftWidth}" height="${this.constructor.height}" fill="${this.labelColor}"/>
|
||||
<rect x="${this.leftWidth}" width="${this.rightWidth}" height="${this.constructor.height}" fill="${this.color}"/>
|
||||
<rect width="${this.width}" height="${this.constructor.height}" fill="url(#s)"/>
|
||||
</g>
|
||||
|
||||
<g fill="#fff" text-anchor="middle" ${this.constructor.fontFamily} text-rendering="geometricPrecision" font-size="110">
|
||||
${this.renderedLogo}
|
||||
${this.renderedLabel}
|
||||
${this.renderedMessage}
|
||||
</g>`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class FlatSquare extends Badge {
|
||||
static get fontFamily() {
|
||||
return fontFamily
|
||||
}
|
||||
|
||||
static get height() {
|
||||
return 20
|
||||
}
|
||||
|
||||
static get verticalMargin() {
|
||||
return 0
|
||||
}
|
||||
|
||||
static get shadow() {
|
||||
return false
|
||||
}
|
||||
|
||||
render() {
|
||||
return renderBadge(
|
||||
{
|
||||
links: this.links,
|
||||
leftWidth: this.leftWidth,
|
||||
rightWidth: this.rightWidth,
|
||||
accessibleText: this.accessibleText,
|
||||
height: this.constructor.height,
|
||||
},
|
||||
`
|
||||
<g shape-rendering="crispEdges">
|
||||
<rect width="${this.leftWidth}" height="${this.constructor.height}" fill="${this.labelColor}"/>
|
||||
<rect x="${this.leftWidth}" width="${this.rightWidth}" height="${this.constructor.height}" fill="${this.color}"/>
|
||||
</g>
|
||||
|
||||
<g fill="#fff" text-anchor="middle" ${this.constructor.fontFamily} text-rendering="geometricPrecision" font-size="110">
|
||||
${this.renderedLogo}
|
||||
${this.renderedLabel}
|
||||
${this.renderedMessage}
|
||||
</g>`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function social({
|
||||
label,
|
||||
message,
|
||||
links = [],
|
||||
logo,
|
||||
logoWidth,
|
||||
logoPadding,
|
||||
color = '#4c1',
|
||||
labelColor = '#555',
|
||||
}) {
|
||||
// Social label is styled with a leading capital. Convert to caps here so
|
||||
// width can be measured using the correct characters.
|
||||
label = capitalize(label)
|
||||
|
||||
const externalHeight = 20
|
||||
const internalHeight = 19
|
||||
const labelHorizPadding = 5
|
||||
const messageHorizPadding = 4
|
||||
const horizGutter = 6
|
||||
const { totalLogoWidth, renderedLogo } = renderLogo({
|
||||
logo,
|
||||
badgeHeight: externalHeight,
|
||||
horizPadding: labelHorizPadding,
|
||||
logoWidth,
|
||||
logoPadding,
|
||||
})
|
||||
const hasMessage = message.length
|
||||
|
||||
const font = 'bold 11px Helvetica'
|
||||
const labelTextWidth = preferredWidthOf(label, { font })
|
||||
const messageTextWidth = preferredWidthOf(message, { font })
|
||||
const labelRectWidth = labelTextWidth + totalLogoWidth + 2 * labelHorizPadding
|
||||
const messageRectWidth = messageTextWidth + 2 * messageHorizPadding
|
||||
|
||||
let [leftLink, rightLink] = links
|
||||
leftLink = escapeXml(leftLink)
|
||||
rightLink = escapeXml(rightLink)
|
||||
const { hasLeftLink, hasRightLink, hasLink } = hasLinks({ links })
|
||||
|
||||
const accessibleText = createAccessibleText({ label, message })
|
||||
|
||||
function renderMessageBubble() {
|
||||
const messageBubbleMainX = labelRectWidth + horizGutter + 0.5
|
||||
const messageBubbleNotchX = labelRectWidth + horizGutter
|
||||
return `
|
||||
<rect x="${messageBubbleMainX}" y="0.5" width="${messageRectWidth}" height="${internalHeight}" rx="2" fill="#fafafa"/>
|
||||
<rect x="${messageBubbleNotchX}" y="7.5" width="0.5" height="5" stroke="#fafafa"/>
|
||||
<path d="M${messageBubbleMainX} 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/>
|
||||
`
|
||||
}
|
||||
|
||||
function renderLabelText() {
|
||||
const labelTextX =
|
||||
10 * (totalLogoWidth + labelTextWidth / 2 + labelHorizPadding)
|
||||
const labelTextLength = 10 * labelTextWidth
|
||||
const escapedLabel = escapeXml(label)
|
||||
const shouldWrapWithLink = hasLeftLink && !shouldWrapBodyWithLink({ links })
|
||||
|
||||
const rect = `<rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="${labelRectWidth}" height="${internalHeight}" rx="2" />`
|
||||
const shadow = `<text aria-hidden="true" x="${labelTextX}" y="150" fill="#fff" transform="scale(.1)" textLength="${labelTextLength}">${escapedLabel}</text>`
|
||||
const text = `<text x="${labelTextX}" y="140" transform="scale(.1)" textLength="${labelTextLength}">${escapedLabel}</text>`
|
||||
|
||||
return shouldWrapWithLink
|
||||
? `
|
||||
<a target="_blank" xlink:href="${leftLink}">
|
||||
${shadow}
|
||||
${text}
|
||||
${rect}
|
||||
</a>
|
||||
`
|
||||
: `
|
||||
${rect}
|
||||
${shadow}
|
||||
${text}
|
||||
`
|
||||
}
|
||||
|
||||
function renderMessageText() {
|
||||
const messageTextX =
|
||||
10 * (labelRectWidth + horizGutter + messageRectWidth / 2)
|
||||
const messageTextLength = 10 * messageTextWidth
|
||||
const escapedMessage = escapeXml(message)
|
||||
|
||||
const rect = `<rect width="${messageRectWidth + 1}" x="${
|
||||
labelRectWidth + horizGutter
|
||||
}" height="${internalHeight + 1}" fill="rgba(0,0,0,0)" />`
|
||||
const shadow = `<text aria-hidden="true" x="${messageTextX}" y="150" fill="#fff" transform="scale(.1)" textLength="${messageTextLength}">${escapedMessage}</text>`
|
||||
const text = `<text id="rlink" x="${messageTextX}" y="140" transform="scale(.1)" textLength="${messageTextLength}">${escapedMessage}</text>`
|
||||
|
||||
return hasRightLink
|
||||
? `
|
||||
<a target="_blank" xlink:href="${rightLink}">
|
||||
${rect}
|
||||
${shadow}
|
||||
${text}
|
||||
</a>
|
||||
`
|
||||
: `
|
||||
${shadow}
|
||||
${text}
|
||||
`
|
||||
}
|
||||
|
||||
return renderBadge(
|
||||
{
|
||||
links,
|
||||
leftWidth: labelRectWidth + 1,
|
||||
rightWidth: hasMessage ? horizGutter + messageRectWidth : 0,
|
||||
accessibleText,
|
||||
height: externalHeight,
|
||||
},
|
||||
`
|
||||
<style>a:hover #llink{fill:url(#b);stroke:#ccc}a:hover #rlink{fill:#4183c4}</style>
|
||||
<linearGradient id="a" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/>
|
||||
<stop offset="1" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#ccc" stop-opacity=".1"/>
|
||||
<stop offset="1" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
<g stroke="#d5d5d5">
|
||||
<rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="${labelRectWidth}" height="${internalHeight}" rx="2"/>
|
||||
${hasMessage ? renderMessageBubble() : ''}
|
||||
</g>
|
||||
${renderedLogo}
|
||||
<g aria-hidden="${!hasLink}" fill="#333" text-anchor="middle" ${socialFontFamily} text-rendering="geometricPrecision" font-weight="700" font-size="110px" line-height="14px">
|
||||
${renderLabelText()}
|
||||
${hasMessage ? renderMessageText() : ''}
|
||||
</g>
|
||||
`
|
||||
)
|
||||
}
|
||||
|
||||
function forTheBadge({
|
||||
label,
|
||||
message,
|
||||
links,
|
||||
logo,
|
||||
logoWidth,
|
||||
color = '#4c1',
|
||||
labelColor,
|
||||
}) {
|
||||
const FONT_SIZE = 10
|
||||
const BADGE_HEIGHT = 28
|
||||
const LOGO_HEIGHT = 14
|
||||
const TEXT_MARGIN = 12
|
||||
const LOGO_MARGIN = 9
|
||||
const LOGO_TEXT_GUTTER = 6
|
||||
const LETTER_SPACING = 1.25
|
||||
|
||||
// Prepare content. For the Badge is styled in all caps. It's important to to
|
||||
// convert to uppercase first so the widths can be measured using the correct
|
||||
// symbols.
|
||||
label = label.toUpperCase()
|
||||
message = message.toUpperCase()
|
||||
|
||||
const [leftLink, rightLink] = links
|
||||
const { hasLeftLink, hasRightLink } = hasLinks({ links })
|
||||
|
||||
const outLabelColor = labelColor || '#555'
|
||||
|
||||
// Compute text width.
|
||||
// TODO: This really should count the symbols rather than just using `.length`.
|
||||
// https://mathiasbynens.be/notes/javascript-unicode
|
||||
// This is not using `preferredWidthOf()` as it tends to produce larger
|
||||
// inconsistencies in the letter spacing. The badges look fine, however if you
|
||||
// replace `textLength` with `letterSpacing` in the rendered SVG, you can see
|
||||
// the discrepancy. Ideally, swapping out `textLength` for `letterSpacing`
|
||||
// should not affect the appearance.
|
||||
const labelTextWidth = label.length
|
||||
? (anafanafo(label, { font: `${FONT_SIZE}px Verdana` }) | 0) +
|
||||
LETTER_SPACING * label.length
|
||||
: 0
|
||||
const messageTextWidth = message.length
|
||||
? (anafanafo(message, { font: `bold ${FONT_SIZE}px Verdana` }) | 0) +
|
||||
LETTER_SPACING * message.length
|
||||
: 0
|
||||
|
||||
// Compute horizontal layout.
|
||||
// If a `labelColor` is set, the logo is always set against it, even when
|
||||
// there is no label. When `needsLabelRect` is true, render a label rect and a
|
||||
// message rect; when false, only a message rect.
|
||||
const hasLabel = Boolean(label.length)
|
||||
const needsLabelRect = hasLabel || (logo && labelColor)
|
||||
let logoMinX, labelTextMinX
|
||||
if (logo) {
|
||||
logoMinX = LOGO_MARGIN
|
||||
labelTextMinX = logoMinX + logoWidth + LOGO_TEXT_GUTTER
|
||||
} else {
|
||||
labelTextMinX = TEXT_MARGIN
|
||||
}
|
||||
let labelRectWidth, messageTextMinX, messageRectWidth
|
||||
if (needsLabelRect) {
|
||||
if (hasLabel) {
|
||||
labelRectWidth = labelTextMinX + labelTextWidth + TEXT_MARGIN
|
||||
} else {
|
||||
labelRectWidth = 2 * LOGO_MARGIN + logoWidth
|
||||
}
|
||||
messageTextMinX = labelRectWidth + TEXT_MARGIN
|
||||
messageRectWidth = 2 * TEXT_MARGIN + messageTextWidth
|
||||
} else {
|
||||
if (logo) {
|
||||
messageTextMinX = TEXT_MARGIN + logoWidth + LOGO_TEXT_GUTTER
|
||||
messageRectWidth =
|
||||
2 * TEXT_MARGIN + logoWidth + LOGO_TEXT_GUTTER + messageTextWidth
|
||||
} else {
|
||||
messageTextMinX = TEXT_MARGIN
|
||||
messageRectWidth = 2 * TEXT_MARGIN + messageTextWidth
|
||||
}
|
||||
}
|
||||
|
||||
const logoElement = new XmlElement({
|
||||
name: 'image',
|
||||
attrs: {
|
||||
x: logoMinX,
|
||||
y: 0.5 * (BADGE_HEIGHT - LOGO_HEIGHT),
|
||||
width: logoWidth,
|
||||
height: LOGO_HEIGHT,
|
||||
'xlink:href': logo,
|
||||
},
|
||||
})
|
||||
|
||||
function getLabelElement() {
|
||||
const { textColor } = colorsForBackground(outLabelColor)
|
||||
const midX = labelTextMinX + 0.5 * labelTextWidth
|
||||
const text = new XmlElement({
|
||||
name: 'text',
|
||||
content: [label],
|
||||
attrs: {
|
||||
transform: FONT_SCALE_DOWN_VALUE,
|
||||
x: FONT_SCALE_UP_FACTOR * midX,
|
||||
y: 175,
|
||||
textLength: FONT_SCALE_UP_FACTOR * labelTextWidth,
|
||||
fill: textColor,
|
||||
},
|
||||
})
|
||||
|
||||
if (hasLeftLink && !shouldWrapBodyWithLink({ links })) {
|
||||
const rect = new XmlElement({
|
||||
name: 'rect',
|
||||
attrs: {
|
||||
width: labelRectWidth,
|
||||
height: BADGE_HEIGHT,
|
||||
fill: 'rgba(0,0,0,0)',
|
||||
},
|
||||
})
|
||||
return new XmlElement({
|
||||
name: 'a',
|
||||
content: [rect, text],
|
||||
attrs: {
|
||||
target: '_blank',
|
||||
'xlink:href': leftLink,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
function getMessageElement() {
|
||||
const { textColor } = colorsForBackground(color)
|
||||
const midX = messageTextMinX + 0.5 * messageTextWidth
|
||||
const text = new XmlElement({
|
||||
name: 'text',
|
||||
content: [message],
|
||||
attrs: {
|
||||
transform: FONT_SCALE_DOWN_VALUE,
|
||||
x: FONT_SCALE_UP_FACTOR * midX,
|
||||
y: 175,
|
||||
textLength: FONT_SCALE_UP_FACTOR * messageTextWidth,
|
||||
fill: textColor,
|
||||
'font-weight': 'bold',
|
||||
},
|
||||
})
|
||||
|
||||
if (hasRightLink) {
|
||||
const rect = new XmlElement({
|
||||
name: 'rect',
|
||||
attrs: {
|
||||
width: messageRectWidth,
|
||||
height: BADGE_HEIGHT,
|
||||
x: labelRectWidth || 0,
|
||||
fill: 'rgba(0,0,0,0)',
|
||||
},
|
||||
})
|
||||
return new XmlElement({
|
||||
name: 'a',
|
||||
content: [rect, text],
|
||||
attrs: {
|
||||
target: '_blank',
|
||||
'xlink:href': rightLink,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
let backgroundContent
|
||||
if (needsLabelRect) {
|
||||
backgroundContent = [
|
||||
new XmlElement({
|
||||
name: 'rect',
|
||||
attrs: {
|
||||
width: labelRectWidth,
|
||||
height: BADGE_HEIGHT,
|
||||
fill: outLabelColor,
|
||||
},
|
||||
}),
|
||||
new XmlElement({
|
||||
name: 'rect',
|
||||
attrs: {
|
||||
x: labelRectWidth,
|
||||
width: messageRectWidth,
|
||||
height: BADGE_HEIGHT,
|
||||
fill: color,
|
||||
},
|
||||
}),
|
||||
]
|
||||
} else {
|
||||
backgroundContent = [
|
||||
new XmlElement({
|
||||
name: 'rect',
|
||||
attrs: {
|
||||
width: messageRectWidth,
|
||||
height: BADGE_HEIGHT,
|
||||
fill: color,
|
||||
},
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
const backgroundGroup = new XmlElement({
|
||||
name: 'g',
|
||||
content: backgroundContent,
|
||||
attrs: {
|
||||
'shape-rendering': 'crispEdges',
|
||||
},
|
||||
})
|
||||
const foregroundGroup = new XmlElement({
|
||||
name: 'g',
|
||||
content: [
|
||||
logo ? logoElement : '',
|
||||
hasLabel ? getLabelElement() : '',
|
||||
getMessageElement(),
|
||||
],
|
||||
attrs: {
|
||||
fill: '#fff',
|
||||
'text-anchor': 'middle',
|
||||
'font-family': FONT_FAMILY,
|
||||
'text-rendering': 'geometricPrecision',
|
||||
'font-size': FONT_SCALE_UP_FACTOR * FONT_SIZE,
|
||||
},
|
||||
})
|
||||
|
||||
// Render.
|
||||
return renderBadge(
|
||||
{
|
||||
links,
|
||||
leftWidth: labelRectWidth || 0,
|
||||
rightWidth: messageRectWidth,
|
||||
accessibleText: createAccessibleText({ label, message }),
|
||||
height: BADGE_HEIGHT,
|
||||
},
|
||||
[backgroundGroup.render(), foregroundGroup.render()].join('')
|
||||
)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
plastic: params => Plastic.render(params),
|
||||
flat: params => Flat.render(params),
|
||||
'flat-square': params => FlatSquare.render(params),
|
||||
social,
|
||||
'for-the-badge': forTheBadge,
|
||||
}
|
||||
88
node_modules/badge-maker/lib/color.js
generated
vendored
Normal file
88
node_modules/badge-maker/lib/color.js
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
'use strict'
|
||||
|
||||
const { fromString } = require('css-color-converter')
|
||||
|
||||
// When updating these, be sure also to update the list in `badge-maker/README.md`.
|
||||
const namedColors = {
|
||||
brightgreen: '#4c1',
|
||||
green: '#97ca00',
|
||||
yellow: '#dfb317',
|
||||
yellowgreen: '#a4a61d',
|
||||
orange: '#fe7d37',
|
||||
red: '#e05d44',
|
||||
blue: '#007ec6',
|
||||
grey: '#555',
|
||||
lightgrey: '#9f9f9f',
|
||||
}
|
||||
|
||||
const aliases = {
|
||||
gray: 'grey',
|
||||
lightgray: 'lightgrey',
|
||||
critical: 'red',
|
||||
important: 'orange',
|
||||
success: 'brightgreen',
|
||||
informational: 'blue',
|
||||
inactive: 'lightgrey',
|
||||
}
|
||||
|
||||
const resolvedAliases = {}
|
||||
Object.entries(aliases).forEach(([alias, original]) => {
|
||||
resolvedAliases[alias] = namedColors[original]
|
||||
})
|
||||
|
||||
// This function returns false for `#ccc`. However `isCSSColor('#ccc')` is
|
||||
// true.
|
||||
const hexColorRegex = /^([\da-f]{3}){1,2}$/i
|
||||
function isHexColor(s = '') {
|
||||
return hexColorRegex.test(s)
|
||||
}
|
||||
|
||||
function isCSSColor(color) {
|
||||
return typeof color === 'string' && fromString(color.trim())
|
||||
}
|
||||
|
||||
function normalizeColor(color) {
|
||||
if (color === undefined) {
|
||||
return undefined
|
||||
} else if (color in namedColors) {
|
||||
return color
|
||||
} else if (color in aliases) {
|
||||
return aliases[color]
|
||||
} else if (isHexColor(color)) {
|
||||
return `#${color.toLowerCase()}`
|
||||
} else if (isCSSColor(color)) {
|
||||
return color.toLowerCase()
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function toSvgColor(color) {
|
||||
const normalized = normalizeColor(color)
|
||||
if (normalized in namedColors) {
|
||||
return namedColors[normalized]
|
||||
} else if (normalized in resolvedAliases) {
|
||||
return resolvedAliases[normalized]
|
||||
} else {
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
function brightness(color) {
|
||||
if (color) {
|
||||
const cssColor = fromString(color)
|
||||
if (cssColor) {
|
||||
const rgb = cssColor.toRgbaArray()
|
||||
return +((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 255000).toFixed(2)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
namedColors,
|
||||
isHexColor,
|
||||
normalizeColor,
|
||||
toSvgColor,
|
||||
brightness,
|
||||
}
|
||||
81
node_modules/badge-maker/lib/index.js
generated
vendored
Normal file
81
node_modules/badge-maker/lib/index.js
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
'use strict'
|
||||
/**
|
||||
* @module badge-maker
|
||||
*/
|
||||
|
||||
const _makeBadge = require('./make-badge')
|
||||
|
||||
class ValidationError extends Error {}
|
||||
|
||||
function _validate(format) {
|
||||
if (format !== Object(format)) {
|
||||
throw new ValidationError('makeBadge takes an argument of type object')
|
||||
}
|
||||
|
||||
if (!('message' in format)) {
|
||||
throw new ValidationError('Field `message` is required')
|
||||
}
|
||||
|
||||
const stringFields = ['labelColor', 'color', 'message', 'label']
|
||||
stringFields.forEach(function (field) {
|
||||
if (field in format && typeof format[field] !== 'string') {
|
||||
throw new ValidationError(`Field \`${field}\` must be of type string`)
|
||||
}
|
||||
})
|
||||
|
||||
const styleValues = [
|
||||
'plastic',
|
||||
'flat',
|
||||
'flat-square',
|
||||
'for-the-badge',
|
||||
'social',
|
||||
]
|
||||
if ('style' in format && !styleValues.includes(format.style)) {
|
||||
throw new ValidationError(
|
||||
`Field \`style\` must be one of (${styleValues.toString()})`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function _clean(format) {
|
||||
const expectedKeys = ['label', 'message', 'labelColor', 'color', 'style']
|
||||
|
||||
const cleaned = {}
|
||||
Object.keys(format).forEach(key => {
|
||||
if (format[key] != null && expectedKeys.includes(key)) {
|
||||
cleaned[key] = format[key]
|
||||
} else {
|
||||
throw new ValidationError(
|
||||
`Unexpected field '${key}'. Allowed values are (${expectedKeys.toString()})`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// Legacy.
|
||||
cleaned.label = cleaned.label || ''
|
||||
|
||||
return cleaned
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a badge
|
||||
*
|
||||
* @param {object} format Object specifying badge data
|
||||
* @param {string} format.label (Optional) Badge label (e.g: 'build')
|
||||
* @param {string} format.message (Required) Badge message (e.g: 'passing')
|
||||
* @param {string} format.labelColor (Optional) Label color
|
||||
* @param {string} format.color (Optional) Message color
|
||||
* @param {string} format.style (Optional) Visual style e.g: 'flat'
|
||||
* @returns {string} Badge in SVG format
|
||||
* @see https://github.com/badges/shields/tree/master/badge-maker/README.md
|
||||
*/
|
||||
function makeBadge(format) {
|
||||
_validate(format)
|
||||
const cleanedFormat = _clean(format)
|
||||
return _makeBadge(cleanedFormat)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
makeBadge,
|
||||
ValidationError,
|
||||
}
|
||||
63
node_modules/badge-maker/lib/make-badge.js
generated
vendored
Normal file
63
node_modules/badge-maker/lib/make-badge.js
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
'use strict'
|
||||
|
||||
const { normalizeColor, toSvgColor } = require('./color')
|
||||
const badgeRenderers = require('./badge-renderers')
|
||||
const { stripXmlWhitespace } = require('./xml')
|
||||
|
||||
/*
|
||||
note: makeBadge() is fairly thinly wrapped so if we are making changes here
|
||||
it is likely this will impact on the package's public interface in index.js
|
||||
*/
|
||||
module.exports = function makeBadge({
|
||||
format,
|
||||
style = 'flat',
|
||||
label,
|
||||
message,
|
||||
color,
|
||||
labelColor,
|
||||
logo,
|
||||
logoPosition,
|
||||
logoWidth,
|
||||
links = ['', ''],
|
||||
}) {
|
||||
// String coercion and whitespace removal.
|
||||
label = `${label}`.trim()
|
||||
message = `${message}`.trim()
|
||||
|
||||
// This ought to be the responsibility of the server, not `makeBadge`.
|
||||
if (format === 'json') {
|
||||
return JSON.stringify({
|
||||
label,
|
||||
message,
|
||||
logoWidth,
|
||||
// Only call normalizeColor for the JSON case: this is handled
|
||||
// internally by toSvgColor in the SVG case.
|
||||
color: normalizeColor(color),
|
||||
labelColor: normalizeColor(labelColor),
|
||||
link: links,
|
||||
name: label,
|
||||
value: message,
|
||||
})
|
||||
}
|
||||
|
||||
const render = badgeRenderers[style]
|
||||
if (!render) {
|
||||
throw new Error(`Unknown badge style: '${style}'`)
|
||||
}
|
||||
|
||||
logoWidth = +logoWidth || (logo ? 14 : 0)
|
||||
|
||||
return stripXmlWhitespace(
|
||||
render({
|
||||
label,
|
||||
message,
|
||||
links,
|
||||
logo,
|
||||
logoPosition,
|
||||
logoWidth,
|
||||
logoPadding: logo && label.length ? 3 : 0,
|
||||
color: toSvgColor(color),
|
||||
labelColor: toSvgColor(labelColor),
|
||||
})
|
||||
)
|
||||
}
|
||||
76
node_modules/badge-maker/lib/xml.js
generated
vendored
Normal file
76
node_modules/badge-maker/lib/xml.js
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @module
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
function stripXmlWhitespace(xml) {
|
||||
return xml.replace(/>\s+/g, '>').replace(/<\s+/g, '<').trim()
|
||||
}
|
||||
|
||||
function escapeXml(s) {
|
||||
if (typeof s === 'number') {
|
||||
return s
|
||||
} else if (s === undefined || typeof s !== 'string') {
|
||||
return undefined
|
||||
} else {
|
||||
return s
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Representation of an XML element
|
||||
*/
|
||||
class XmlElement {
|
||||
/**
|
||||
* Xml Element Constructor
|
||||
*
|
||||
* @param {object} attrs Refer to individual attrs
|
||||
* @param {string} attrs.name
|
||||
* Name of the XML tag
|
||||
* @param {Array.<string|module:badge-maker/lib/xml-element~XmlElement>} [attrs.content=[]]
|
||||
* Array of objects to render inside the tag. content may contain a mix of
|
||||
* string and XmlElement objects. If content is `[]` or ommitted the
|
||||
* element will be rendered as a self-closing element.
|
||||
* @param {object} [attrs.attrs={}]
|
||||
* Object representing the tag's attributes as name/value pairs
|
||||
*/
|
||||
constructor({ name, content = [], attrs = {} }) {
|
||||
this.name = name
|
||||
this.content = content
|
||||
this.attrs = attrs
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the XML element to a string, applying appropriate escaping
|
||||
*
|
||||
* @returns {string} String representation of the XML element
|
||||
*/
|
||||
render() {
|
||||
const attrsStr = Object.entries(this.attrs)
|
||||
.map(([k, v]) => ` ${k}="${escapeXml(v)}"`)
|
||||
.join('')
|
||||
if (this.content.length > 0) {
|
||||
const content = this.content
|
||||
.map(function (el) {
|
||||
if (el instanceof XmlElement) {
|
||||
return el.render()
|
||||
} else {
|
||||
return escapeXml(el)
|
||||
}
|
||||
})
|
||||
.join(' ')
|
||||
return stripXmlWhitespace(
|
||||
`<${this.name}${attrsStr}>${content}</${this.name}>`
|
||||
)
|
||||
}
|
||||
return stripXmlWhitespace(`<${this.name}${attrsStr}/>`)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { escapeXml, stripXmlWhitespace, XmlElement }
|
||||
44
node_modules/badge-maker/package.json
generated
vendored
Normal file
44
node_modules/badge-maker/package.json
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "badge-maker",
|
||||
"version": "3.3.1",
|
||||
"description": "Shields.io badge library",
|
||||
"keywords": [
|
||||
"GitHub",
|
||||
"badge",
|
||||
"SVG",
|
||||
"image",
|
||||
"shields.io"
|
||||
],
|
||||
"types": "index.d.ts",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/badges/shields.git",
|
||||
"directory": "badge-maker"
|
||||
},
|
||||
"author": "Thaddée Tyl <thaddee.tyl@gmail.com>",
|
||||
"license": "CC0-1.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/badges/shields/issues"
|
||||
},
|
||||
"homepage": "http://shields.io",
|
||||
"bin": {
|
||||
"badge": "lib/badge-cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10",
|
||||
"npm": ">= 5"
|
||||
},
|
||||
"collective": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/shields",
|
||||
"logo": "https://opencollective.com/opencollective/logo.txt"
|
||||
},
|
||||
"dependencies": {
|
||||
"anafanafo": "2.0.0",
|
||||
"css-color-converter": "^2.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo 'Run tests from parent dir'; false"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user