Version 2.2 (new additions)

- Smarter placeholders
- Merged placeholders with real templates to avoid maintening duplicates
- Moved style/js away from index.html and refactor
- Svg size is now computed again from templates
- Base metrics is now contained in special plugin "base", which can be disabled  part by part if you just want to include a plugin instead
- Reformatted a bit terminal template
- Test now look templates directory
This commit is contained in:
lowlighter
2020-10-24 17:31:37 +02:00
parent 997deed60b
commit 60d02a6d90
22 changed files with 713 additions and 956 deletions

View File

@@ -26,6 +26,9 @@ inputs:
repositories:
description: Number of repositories to use to compute metrics
default: 100
base:
description: Base content to include in metrics (list of comma-separated sections name as string)
default: "header, activity, community, repositories, metadata"
plugin_pagespeed:
description: Enable Google PageSpeed metrics for account attached website
default: no

File diff suppressed because one or more lines are too long

View File

@@ -63,6 +63,14 @@
console.debug = () => null
console.log(`Debug mode | ${debug}`)
//Base elements
let base = (core.getInput("base")||"").split(",").map(part => part.trim())
if (!base.length)
base = conf.settings.plugins.base.parts
for (const part of base)
q[`base.${part}`] = true
console.log(`Base elements | ${base.join(", ")}`)
//Additional plugins
const plugins = {
lines:{enabled:bool(core.getInput("plugin_lines"))},

View File

@@ -46,9 +46,12 @@
const actions = {flush:new Map()}
app.get("/", limiter, (req, res) => res.sendFile(`${conf.statics}/index.html`))
app.get("/index.html", limiter, (req, res) => res.sendFile(`${conf.statics}/index.html`))
app.get("/app.js", limiter, (req, res) => res.sendFile(`${conf.statics}/app.js`))
app.get("/style.css", limiter, (req, res) => res.sendFile(`${conf.statics}/style.css`))
app.get("/favicon.ico", limiter, (req, res) => res.sendStatus(204))
app.get("/plugins.list", limiter, (req, res) => res.status(200).json(enabled))
app.get("/templates.list", limiter, (req, res) => res.status(200).json(templates))
app.get("/plugins.base.parts.list", limiter, (req, res) => res.status(200).json(conf.settings.plugins.base.parts))
app.get("/ejs.min.js", limiter, (req, res) => res.sendFile(`${conf.node_modules}/ejs/ejs.min.js`))
app.get("/axios.min.js", limiter, (req, res) => res.sendFile(`${conf.node_modules}/axios/dist/axios.min.js`))
app.get("/axios.min.map", limiter, (req, res) => res.sendFile(`${conf.node_modules}/axios/dist/axios.min.map`))
@@ -57,8 +60,8 @@
const template = req.query.template || conf.settings.templates.default
if (!(template in Templates))
return res.sendStatus(404)
const {style, placeholder} = conf.templates[template]
res.status(200).json({style, placeholder})
const {style, image} = conf.templates[template]
res.status(200).json({style, image})
})
app.get("/action.flush", limiter, async (req, res) => {
const {token, user} = req.query

151
src/html/app.js Normal file
View File

@@ -0,0 +1,151 @@
;(async function() {
//Init
const url = new URLSearchParams(window.location.search)
const {data:templates} = await axios.get("/templates.list")
const {data:plugins} = await axios.get("/plugins.list")
const {data:base} = await axios.get("/plugins.base.parts.list")
//App
return new Vue({
//Initialization
el:"main",
async mounted() {
await this.load()
},
//Data initialization
data:{
user:url.get("user") || "",
palette:url.get("palette") || "light",
plugins:{
base,
list:plugins,
enabled:{base:Object.fromEntries(base.map(key => [key, true]))},
descriptions:{
pagespeed:"Website performances",
languages:"Most used languages",
followup:"Issues and pull requests",
traffic:"Pages views",
lines:"Lines of code changed",
habits:"Coding habits",
selfskip:"Skip metrics commits",
"base.header":"Header",
"base.activity":"Account activity",
"base.community":"Community stats",
"base.repositories":"Repositories metrics",
"base.metadata":"Metadata",
},
},
templates:{
list:templates,
selected:url.get("template") || templates[0],
loaded:{},
placeholder:"",
descriptions:{
classic:"Classic template",
terminal:"Terminal template",
},
},
generated:{
pending:false,
content:"",
error:false,
},
},
//Computed data
computed:{
//User's repository
repo() {
return `https://github.com/${this.user}/${this.user}`
},
//Endpoint to use for computed metrics
url() {
const plugins = Object.entries(this.plugins.enabled)
.flatMap(([key, value]) => key === "base" ? Object.entries(value).map(([key, value]) => [`base.${key}`, value]) : [[key, value]])
.filter(([key, value]) => /^base[.]\w+$/.test(key) ? !value : value)
.map(([key, value]) => `${key}=${+value}`)
const params = [...(this.templates.selected !== templates[0] ? [`template=${this.templates.selected}`] : []), ...plugins].join("&")
return `${window.location.protocol}//${window.location.host}/${this.user}${params.length ? `?${params}` : ""}`
},
},
//Methods
methods:{
//Load and render image
async load() {
//Load template
const template = this.templates.selected
if (!this.templates.loaded[template]) {
const {data:{image, style}} = await axios.get(`/placeholder.svg?template=${template}`)
this.templates.loaded[template] = {image, style}
}
const {image = "", style = {}} = this.templates.loaded[this.templates.selected] || {}
if (!image)
return this.templates.placeholder = "#"
//Proxifier
const proxify = (target) => typeof target === "object" ? new Proxy(target, {
get(target, property) {
//Primitive conversion
if (property === Symbol.toPrimitive)
return () => "▇"
//Iterables
if (property === Symbol.iterator)
return Reflect.get(target, property)
//Plugins should not be proxified by default as they can be toggled by user
if (/^plugins$/.test(property))
return Reflect.get(target, property)
//Consider no errors on plugins
if (/^error/.test(property))
return undefined
//Proxify recursively
return proxify(property in target ? Reflect.get(target, property) : {})
}
}) : target
//Placeholder data
const data = {
style,
s(_, letter) { return letter === "y" ? "ies" : "s" },
base:this.plugins.enabled.base,
meta:{version:"0.0.0", author:"lowlighter", placeholder:true},
user:proxify({name:`▇▇▇▇`, websiteUrl:`▇▇▇▇▇▇▇▇▇▇▇▇`}),
computed:proxify({
avatar:"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg==",
registration:"▇ years ago",
calendar:new Array(14).fill({color:"#ebedf0"}),
licenses:{favorite:`▇▇▇▇`},
plugins:Object.fromEntries(Object.entries(this.plugins.enabled).filter(([key, enabled]) => (key !== "base")&&(enabled)).map(([key]) => {
return [key, proxify({
pagespeed:{scores:["Performance", "Accessibility", "Best Practices", "SEO"].map(title => ({title, score:NaN}))},
followup:{issues:{count:0}, pr:{count:0}},
habits:{indents:{style:`▇▇▇`}},
languages:{favorites:new Array(7).fill(null).map((_, x) => ({x, name:`▇▇▇▇`, color:"#ebedf0", value:1/(x+1)}))},
}[key]||{})]
})),
token:{scopes:[]},
}),
}
//Render placeholder
this.templates.placeholder = this.serialize(ejs.render(image, data))
this.generated.content = ""
},
//Generate metrics and flush cache
async generate() {
//Avoid requests spamming
if (this.generated.pending)
return
this.generated.pending = true
//Compute metrics
try {
await axios.get(`/action.flush?&token=${(await axios.get(`/action.flush?user=${this.user}`)).data.token}`)
this.generated.content = this.serialize((await axios.get(this.url)).data)
} catch {
this.generated.error = true
}
finally {
this.generated.pending = false
}
},
//Serialize svg
serialize(svg) {
return `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svg)))}`
},
},
})
})()

View File

@@ -6,65 +6,78 @@
<meta name="description" content="A SVG image generator which includes activity, community and repositories metrics about your GitHub account that you can includes on your profile">
<meta name="author" content="lowlighter">
<link rel="icon" href="data:,">
<link rel="stylesheet" href="/style.css">
</head>
<body>
<!-- Vue app -->
<main :class="[palette]">
<!-- Title -->
<h1><a href="https://github.com/lowlighter/metrics">Generate your metrics !</a></h1>
<!-- Content -->
<template>
<section class="generator">
<!-- Steps panel -->
<section class="steps">
<div class="step">
<h2>1. Enter your GitHub username</h2>
<label>
<input type="text" v-model="user" maxlength="39" placeholder="GitHub username" :disabled="generated.pending">
</label>
</div>
<div class="step">
<h2>2. Select a template {{ plugins.list.length ? "and enable additional plugins" : "" }}</h2>
<h2>2. Select a template</h2>
<div class="templates">
<label v-for="template in templates.list" :key="template">
<input type="radio" v-model="templates.selected" :value="template" @change="load" :disabled="generated.pending">
{{ templates.descriptions[template] || template }}
</label>
</div>
<template v-if="plugins.base.length">
<h3>2.1 Configure base content</h3>
<div class="plugins">
<label v-for="part in plugins.base" :key="part">
<input type="checkbox" v-model="plugins.enabled.base[part]" @change="load" :disabled="generated.pending">
{{ plugins.descriptions[`base.${part}`] || `base.${part}` }}
</label>
</div>
</template>
<template v-if="plugins.list.length">
<h3>2.2 Enable additional plugins</h3>
<div class="plugins">
<label v-for="plugin in plugins.list" :key="plugin">
<input type="checkbox" v-model="plugins.enabled[plugin]" @change="load" :disabled="generated.pending">
{{ plugins.descriptions[plugin] || plugin }}
</label>
</div>
<div class="cache-notice" v-if="plugins.list.length">
*To reduce server overhead, metrics are cached. Changes may not be reflected until cache expiration.
</div>
</template>
<div class="palette">
Generated metrics use transparency and colors which matches both light and dark modes
Generated metrics use transparency and colors which can be read on both light and dark modes, so everyone can see your stats whatever their preferred color scheme !
<div class="palettes">
<label>
<input type="radio" v-model="palette" value="light">
☀️ Light mode
<input type="radio" v-model="palette" value="light"> ☀️ Light mode
</label>
<label>
<input type="radio" v-model="palette" value="dark">
🌙 Night mode
<input type="radio" v-model="palette" value="dark"> 🌙 Night mode
</label>
</div>
</div>
</div>
<div class="step">
<h2>3. Generate your metrics</h2>
<template v-if="!user">
Set your username to generate your metrics 🦑
</template>
<div class="preview-inliner">
<template v-if="generated.content">
<img class="metrics" :src="generated.content" alt="metrics">
<img class="metrics preview-inline" :src="generated.content" alt="metrics">
</template>
<template v-else>
<img class="metrics" :src="templates.placeholder" alt="metrics">
<button @click="generate" :disabled="(!user)||(generated.pending)">{{ generated.pending ? "Generating your metrics..." : user ? "Generate your metrics" : "Enter your GitHub username first" }}</button>
<img class="metrics preview-inline" :src="templates.placeholder" alt="metrics">
</template>
</div>
<template v-if="user">
<button @click="generate" :disabled="generated.pending">{{ generated.pending ? "Working on it :)" : "Generate your metrics !" }}</button>
</template>
<div class="error" v-if="generated.error">An error occurred. Please try again later.</div>
</div>
<div class="step">
<h2>4. Embed these metrics on your GitHub profile</h2>
<template v-if="user">
@@ -73,207 +86,27 @@
![<span class="md-alt">GitHub metrics</span>]({{ url }})
</div>
</template>
For even more features, setup <a href="https://github.com/lowlighter/metrics">lowlighter/metrics</a> as a <a href="https://github.com/marketplace/actions/github-metrics-as-svg-image">GitHub action</a> !
For even more features, setup <a href="https://github.com/lowlighter/metrics">lowlighter/metrics</a> as a <a href="https://github.com/marketplace/actions/github-metrics-as-svg-image">GitHub action</a> !<br>
Enjoying <a href="https://github.com/lowlighter/metrics">metrics</a> ? Consider starring it, a little bit of support is always appreciated 💖 !
</div>
</section>
<!-- Metrics preview -->
<section class="preview">
<template v-if="generated.content">
<img class="metrics" :src="generated.content" alt="metrics">
</template>
<template v-else>
<img class="metrics" :src="templates.placeholder" alt="metrics">
</template>
<div class="error" v-if="generated.error">An error occurred. Please try again later.</div>
</section>
</section>
</template>
</main>
<!-- Scripts -->
<script src="/axios.min.js"></script>
<script src="/ejs.min.js"></script>
<script src="/vue.min.js"></script>
<script>
;(async function() {
const url = new URLSearchParams(window.location.search)
new Vue({
el:"main",
async mounted() {
await this.load()
},
data:{
user:url.get("user") || "",
palette:url.get("palette") || "light",
plugins:{
list:(await axios.get("/plugins.list")).data,
enabled:{},
descriptions:{
pagespeed:"Website performances",
languages:"Most used languages",
followup:"Owned repositories issues and pull requests",
traffic:"Pages views",
lines:"Lines of code changed",
habits:"Coding habits",
selfskip:"Skip metrics commits",
},
},
templates:{
list:(await axios.get("/templates.list")).data,
loaded:{},
selected:url.get("template") || (await axios.get("/templates.list")).data[0],
placeholder:"",
descriptions:{
classic:"Classic template",
terminal:"Terminal template"
},
},
generated:{
pending:false,
content:"",
},
},
computed:{
repo() {
return `https://github.com/${this.user}/${this.user}`
},
url() {
const plugins = Object.entries(this.plugins.enabled)
.filter(([key, value]) => value)
.map(([key, value]) => `${key}=${+value}`)
.join("&")
return `${window.location.protocol}//${window.location.host}/${this.user}?template=${this.templates.selected}${plugins.length ? `&${plugins}` : ""}`
},
},
methods:{
async load() {
const template = this.templates.selected
if (!this.templates.loaded[template]) {
const {data:{placeholder, style}} = await axios.get(`/placeholder.svg?template=${template}`)
this.templates.loaded[template] = {placeholder, style}
}
const {placeholder = "", style = {}} = this.templates.loaded[this.templates.selected] || {}
this.templates.placeholder = placeholder ? this.serialize(ejs.render(placeholder, {plugins:this.plugins.enabled, style})) : "#"
},
async generate() {
this.generated.pending = true
await axios.get(`/action.flush?&token=${(await axios.get(`/action.flush?user=${this.user}`)).data.token}`)
this.generated.content = this.serialize((await axios.get(this.url)).data)
},
serialize(svg) {
return `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svg)))}`
},
},
})
})()
</script>
<style>
body {
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
padding: 0;
margin: 0;
display: flex;
justify-content: center;
}
main {
background-color: #FFFFFF;
color: #1B1F23;
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 2rem;
width: 100%;
transition: background-color .3s;
}
h1 {
font-size: 1.6rem;
margin: 1rem 0;
}
h2 {
margin: 1.5rem 0 1rem;
font-size: 1.3rem;
}
a, a:hover, a:visited {
color: #0366D6;
text-decoration: none;
font-style: normal;
outline: none;
}
a:hover {
color: #79B8FF;
transition: color .4s;
cursor: pointer;
}
input, button, select {
border-radius: .5rem;
padding: .25rem .5rem;
outline: none;
border: 1px solid #E1E4E8;
background-color: #FAFBFC;
color: #1B1F23;
text-align: center;
cursor: pointer;
}
input:focus {
outline: none;
}
input[type=text], select, button {
min-width: 50%;
font-size: 1.1rem;
}
option {
text-align: center;
}
label, button {
margin: 1rem;
}
input[disabled], button[disabled], select[disabled] {
opacity: .5;
cursor: not-allowed;
}
.step {
margin-bottom: 1rem;
text-align: center;
max-width: 800px;
}
.plugins, .palettes {
margin-top: 1rem;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.plugins label, .palettes label {
margin: 0 1rem;
}
.code {
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
padding: 1rem;
margin: .5rem 0;
border-radius: .5rem;
background-color: #FAFBFC;
}
.code .md-alt {
color: #6F42C1;
}
.cache-notice {
margin-top: .5rem;
font-size: .9rem;
opacity: .8;
}
img.metrics {
width: 100%;
max-width: 480px;
}
.palette {
margin-top: 1rem;
}
main.dark {
background-color: #181A1B;
color: #D4D1C5;
}
.dark a, .dark a:visited {
color: #4CACEE;
}
.dark input, .dark button {
color: #D4D1C5;
background-color: #1A1C1E;
border-color: #373C3E;
}
.dark .code {
background-color: #1A1C1E;
}
</style>
<script src="/app.js"></script>
</body>
</html>

173
src/html/style.css Normal file
View File

@@ -0,0 +1,173 @@
/* General */
body {
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
padding: 0;
margin: 0;
display: flex;
justify-content: center;
}
main {
background-color: #FFFFFF;
color: #1B1F23;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
transition: background-color .3s;
}
/* Headlines */
h1 {
font-size: 1.6rem;
margin: 1rem 0;
}
h2 {
margin: 1.5rem 0 1rem;
font-size: 1.3rem;
}
h3 {
margin: .5rem 0 .25rem;
font-size: 1.1rem;
}
/* Links */
a, a:hover, a:visited {
color: #0366D6;
text-decoration: none;
font-style: normal;
outline: none;
}
a:hover {
color: #79B8FF;
transition: color .4s;
cursor: pointer;
}
/* Inputs */
input, button, select {
border-radius: .5rem;
padding: .25rem .5rem;
outline: none;
border: 1px solid #E1E4E8;
background-color: #FAFBFC;
color: #1B1F23;
text-align: center;
cursor: pointer;
}
input:focus {
outline: none;
}
input[type=text], select, button {
min-width: 50%;
font-size: 1.1rem;
}
option {
text-align: center;
}
label, button {
margin: 1rem;
}
label {
padding-right: .25rem;
padding-bottom: .125rem;
}
input[disabled], button[disabled], select[disabled] {
opacity: .5;
cursor: not-allowed;
}
label:hover {
border-radius: .25rem;
background-color: #79B8FF50;
transition: background-color .4s;
cursor: pointer;
}
/* Generator */
.generator {
display: flex;
flex-grow: 1;
width: 100%;
height: 100%;
}
.generator .step {
margin-bottom: 1rem;
text-align: center;
max-width: 800px;
}
.generator .steps {
margin: .5rem 1rem 2rem;
flex-grow: 1;
}
.generator .preview {
display: none;
margin: .5rem 1rem 2rem;
flex-shrink: 0;
}
.generator .preview .metrics {
width: 480px;
}
.generator .preview-inliner {
display: flex;
justify-content: center;
}
.generator .preview-inline .metrics {
width: 100%;
max-width: 480px;
}
@media only screen and (min-width: 1180px) {
.generator .preview-inline {
display: none;
}
.generator .preview {
display: block;
}
}
/* Plugins */
.plugins, .palettes {
margin-top: 1rem;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.plugins label, .palettes label {
margin: 0 1rem;
}
/* Markdown code */
.code {
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
padding: 1rem;
margin: .5rem 0;
border-radius: .5rem;
background-color: #FAFBFC;
word-wrap: break-word;
}
.code .md-alt {
color: #6F42C1;
}
/* Color palette */
.palette {
margin-top: 1rem;
}
main.dark {
background-color: #181A1B;
color: #D4D1C5;
}
.dark a, .dark a:visited {
color: #4CACEE;
}
.dark input, .dark button {
color: #D4D1C5;
background-color: #1A1C1E;
border-color: #373C3E;
}
.dark .code {
background-color: #1A1C1E;
}
/* Error */
.error {
color: #721c24;
background-color: #f8d7da;
padding: .75rem 1.25rem;
border: 1px solid #f5c6cb;
border-radius: .25rem;
display: flex;
justify-content: center;
align-items: center;
}

View File

@@ -31,6 +31,11 @@
)
console.debug(`metrics/compute/${login} > query > success`)
//Base parts
data.base = {}
for (const part of conf.settings.plugins.base.parts)
data.base[part] = (`base.${part}` in q) ? !!q[`base.${part}`] : true
//Template
console.debug(`metrics/compute/${login} > compute`)
const computer = Templates[template].default || Templates[template]

View File

@@ -6,7 +6,6 @@
if (!q.followup)
return computed.plugins.followup = null
console.debug(`metrics/compute/${login}/plugins > followup`)
computed.svg.height += 70
//Plugin execution
pending.push(new Promise(async solve => {

View File

@@ -6,7 +6,6 @@
if (!q.habits)
return computed.plugins.habits = null
console.debug(`metrics/compute/${login}/plugins > habits`)
computed.svg.height += 70
//Parameter override
if (typeof q["habits.from"] === "number") {

View File

@@ -6,7 +6,6 @@
if (!q.languages)
return computed.plugins.languages = null
console.debug(`metrics/compute/${login}/plugins > languages`)
computed.svg.height += 90
//Plugin execution
pending.push(new Promise(async solve => {

View File

@@ -14,7 +14,6 @@
if (!q.lines)
return computed.plugins.lines = null
console.debug(`metrics/compute/${login}/plugins > lines`)
computed.svg.height += 20
//Plugin execution
pending.push(new Promise(async solve => {

View File

@@ -11,7 +11,6 @@
if (!q.pagespeed)
return computed.plugins.pagespeed = null
console.debug(`metrics/compute/${login}/plugins > pagespeed`)
computed.svg.height += 130
//Plugin execution
pending.push(new Promise(async solve => {

View File

@@ -14,7 +14,6 @@
if (!q.traffic)
return computed.plugins.traffic = null
console.debug(`metrics/compute/${login}/plugins > traffic`)
computed.svg.height += !q.lines ? 20 : 0
//Plugin execution
pending.push(new Promise(async solve => {

View File

@@ -28,6 +28,7 @@
conf.settings.templates = {default:"classic", enabled:[]}
if (!conf.settings.plugins)
conf.settings.plugins = {}
conf.settings.plugins.base = {parts:["header", "activity", "community", "repositories", "metadata"]}
if (conf.settings.debug)
logger(conf.settings)
@@ -39,7 +40,7 @@
}
else {
logger(`metrics/setup > load package.json > (missing)`)
conf.package = {version:"<#version>"}
conf.package = {version:"<#version>", author:"lowlighter"}
}
//Load templates
@@ -52,20 +53,19 @@
const files = [
`${templates}/${name}/query.graphql`,
`${templates}/${name}/image.svg`,
`${templates}/${name}/placeholder.svg`,
`${templates}/${name}/style.css`,
]
const [query, image, placeholder, style] = await Promise.all(files.map(async file => `${await fs.promises.readFile(path.resolve(file))}`))
conf.templates[name] = {query, image, placeholder, style}
const [query, image, style] = await Promise.all(files.map(async file => `${await fs.promises.readFile(path.resolve(file))}`))
conf.templates[name] = {query, image, style}
logger(`metrics/setup > load template [${name}] > success`)
//Debug
if (conf.settings.debug) {
Object.defineProperty(conf.templates, name, {
get() {
logger(`metrics/setup > reload template [${name}]`)
const [query, image, placeholder, style] = files.map(file => `${fs.readFileSync(path.resolve(file))}`)
const [query, image, style] = files.map(file => `${fs.readFileSync(path.resolve(file))}`)
logger(`metrics/setup > reload template [${name}] > success`)
return {query, image, placeholder, style}
return {query, image, style}
}
})
}

View File

@@ -1,4 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="<%= computed.svg.width %>" height="<%= computed.svg.height %>">
<svg xmlns="http://www.w3.org/2000/svg" width="480" height="<%= 0
+ (!!base.header)*(16*6)
+ (!!base.metadata)*(16*3)
+ ((!!base.activity)||(!!base.community))*(16*10)
+ (!!base.repositories)*(16*7)
+ ((!!computed.plugins.traffic)||(!!computed.plugins.lines))*(16*2)
+ (!!computed.plugins.followup)*(16*5)
+ (!!computed.plugins.languages)*(16*6)
+ (!!computed.plugins.pagespeed)*(16*9)
+ (!!computed.plugins.habits)*(16*5)
%>">
<style>
<%= style %>
</style>
@@ -6,14 +16,13 @@
<foreignObject x="0" y="0" width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml" xmlns:xlink="http://www.w3.org/1999/xlink">
<% if (base.header) { %>
<section>
<h1 class="field">
<img class="avatar" src="data:image/png;base64,<%= computed.avatar %>" width="20" height="20" />
<span><%= user.name || user.login %></span>
</h1>
<div class="row">
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zm.5 4.75a.75.75 0 00-1.5 0v3.5a.75.75 0 00.471.696l2.5 1a.75.75 0 00.557-1.392L8.5 7.742V4.75z"></path></svg>
@@ -24,7 +33,6 @@
Followed by <%= user.followers.totalCount %> user<%= s(user.followers.totalCount) %>
</div>
</section>
<section>
<div class="field calendar">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 <%= computed.calendar.length*15 %> 11" width="<%= computed.calendar.length*15 %>" height="16">
@@ -40,13 +48,12 @@
Contributed to <%= user.repositoriesContributedTo.totalCount %> repositor<%= s(user.repositoriesContributedTo.totalCount, "y") %>
</div>
</section>
</div>
</section>
<% } %>
<div class="row">
<% if (base.activity) { %>
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 1.75a.75.75 0 00-1.5 0v12.5c0 .414.336.75.75.75h14.5a.75.75 0 000-1.5H1.5V1.75zm14.28 2.53a.75.75 0 00-1.06-1.06L10 7.94 7.53 5.47a.75.75 0 00-1.06 0L3.22 8.72a.75.75 0 001.06 1.06L7 7.06l2.47 2.47a.75.75 0 001.06 0l5.25-5.25z"></path></svg>
@@ -73,7 +80,8 @@
<%= user.issueComments.totalCount %> issue comment<%= s(user.issueComments.totalCount) %>
</div>
</section>
<% } %>
<% if (base.community) { %>
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.75 0A1.75 1.75 0 000 1.75v12.5C0 15.216.784 16 1.75 16h12.5A1.75 1.75 0 0016 14.25V1.75A1.75 1.75 0 0014.25 0H1.75zM1.5 1.75a.25.25 0 01.25-.25h12.5a.25.25 0 01.25.25v12.5a.25.25 0 01-.25.25H1.75a.25.25 0 01-.25-.25V1.75zM11.75 3a.75.75 0 00-.75.75v7.5a.75.75 0 001.5 0v-7.5a.75.75 0 00-.75-.75zm-8.25.75a.75.75 0 011.5 0v5.5a.75.75 0 01-1.5 0v-5.5zM8 3a.75.75 0 00-.75.75v3.5a.75.75 0 001.5 0v-3.5A.75.75 0 008 3z"></path></svg>
@@ -97,12 +105,13 @@
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 14.25c0 .138.112.25.25.25H4v-1.25a.75.75 0 01.75-.75h2.5a.75.75 0 01.75.75v1.25h2.25a.25.25 0 00.25-.25V1.75a.25.25 0 00-.25-.25h-8.5a.25.25 0 00-.25.25v12.5zM1.75 16A1.75 1.75 0 010 14.25V1.75C0 .784.784 0 1.75 0h8.5C11.216 0 12 .784 12 1.75v12.5c0 .085-.006.168-.018.25h2.268a.25.25 0 00.25-.25V8.285a.25.25 0 00-.111-.208l-1.055-.703a.75.75 0 11.832-1.248l1.055.703c.487.325.779.871.779 1.456v5.965A1.75 1.75 0 0114.25 16h-3.5a.75.75 0 01-.197-.026c-.099.017-.2.026-.303.026h-3a.75.75 0 01-.75-.75V14h-1v1.25a.75.75 0 01-.75.75h-3zM3 3.75A.75.75 0 013.75 3h.5a.75.75 0 010 1.5h-.5A.75.75 0 013 3.75zM3.75 6a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM3 9.75A.75.75 0 013.75 9h.5a.75.75 0 010 1.5h-.5A.75.75 0 013 9.75zM7.75 9a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM7 6.75A.75.75 0 017.75 6h.5a.75.75 0 010 1.5h-.5A.75.75 0 017 6.75zM7.75 3a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5z"></path></svg>
Member of <%= user.organizations.totalCount %> organization <%= s(user.organizations.totalCount) %>
Member of <%= user.organizations.totalCount %> organization<%= s(user.organizations.totalCount) %>
</div>
</section>
<% } %>
</div>
<% if (base.repositories) { %>
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"></path></svg>
@@ -120,11 +129,11 @@
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8.878.392a1.75 1.75 0 00-1.756 0l-5.25 3.045A1.75 1.75 0 001 4.951v6.098c0 .624.332 1.2.872 1.514l5.25 3.045a1.75 1.75 0 001.756 0l5.25-3.045c.54-.313.872-.89.872-1.514V4.951c0-.624-.332-1.2-.872-1.514L8.878.392zM7.875 1.69a.25.25 0 01.25 0l4.63 2.685L8 7.133 3.245 4.375l4.63-2.685zM2.5 5.677v5.372c0 .09.047.171.125.216l4.625 2.683V8.432L2.5 5.677zm6.25 8.271l4.625-2.683a.25.25 0 00.125-.216V5.677L8.75 8.432v5.516z"></path></svg>
<%= user.packages.totalCount %> Package <%= s(user.packages.totalCount) %>
<%= user.packages.totalCount %> Package<%= s(user.packages.totalCount) %>
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" ><path fill-rule="evenodd" d="M1.75 1.5a.25.25 0 00-.25.25v12.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25V1.75a.25.25 0 00-.25-.25H1.75zM0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0114.25 16H1.75A1.75 1.75 0 010 14.25V1.75zm9.22 3.72a.75.75 0 000 1.06L10.69 8 9.22 9.47a.75.75 0 101.06 1.06l2-2a.75.75 0 000-1.06l-2-2a.75.75 0 00-1.06 0zM6.78 6.53a.75.75 0 00-1.06-1.06l-2 2a.75.75 0 000 1.06l2 2a.75.75 0 101.06-1.06L5.31 8l1.47-1.47z"></path></svg>
<%= user.gists.totalCount %> Gist <%= s(user.gists.totalCount) %>
<%= user.gists.totalCount %> Gist<%= s(user.gists.totalCount) %>
</div>
<% if (computed.plugins.lines) { %>
<div class="field <%= computed.plugins.lines.error ? 'error' : '' %>">
@@ -137,7 +146,6 @@
</div>
<% } %>
</section>
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"></path></svg>
@@ -164,6 +172,7 @@
</section>
</div>
</section>
<% } %>
<% if (computed.plugins.followup) { %>
<div class="row">
@@ -293,10 +302,14 @@
<section class="categories">
<% for (const {score, title} of computed.plugins.pagespeed.scores) { %>
<div class="categorie column">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="50" height="50" class="gauge <%= score >= 0.9 ? 'high' : score >= 0.5 ? 'average' : 'low' %>">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="50" height="50" class="gauge <%= !Number.isNaN(score) ? (score >= 0.9 ? 'high' : score >= 0.5 ? 'average' : 'low') : '' %>">
<circle class="gauge-base" r="53" cx="60" cy="60"></circle>
<% if (!Number.isNaN(score)) { %>
<circle class="gauge-arc" transform="rotate(-90 60 60)" r="53" cx="60" cy="60" stroke-dasharray="<%= score * 329 %> 329"></circle>
<text x="60" y="60" dominant-baseline="central" ><%= Math.round(score*100) %></text>
<% } else { %>
<text x="60" y="60" dominant-baseline="central" >-</text>
<% } %>
</svg>
<span class="title"><%= title %></span>
</div>
@@ -335,10 +348,12 @@
</section>
<% } %>
<% if (base.metadata) { %>
<footer>
<span>These metrics <%= !computed.token.scopes.includes("repo") ? "does not include" : "includes" %> private contributions</span>
<span>Last updated <%= new Date() %></span>
</footer>
<% } %>
</div>
</foreignObject>

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -1,353 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="480" height="<%= 355 + (!!plugins.followup)*70 + (!!plugins.habits)*70 + (!!plugins.languages)*90 + ((!!plugins.lines)+(!!plugins.traffic))*20 + (!!plugins.pagespeed)*130 %>">
<style>
<%= style %>
/* Avatar */
.avatar {
background-color: rgba(119,119,119,.62);
border-radius: 50%;
margin: 0 6px;
height: 20px;
width: 20px;
}
/* Placeholder */
.placeholder {
background-color: rgba(119,119,119,.62);
margin: 2px 4px 0px;
height: 12px;
width: 24px;
border-radius: 6px;
}
.placeholder.large {
width: 48px;
}
.placeholder.xlarge {
width: 64px;
}
.placeholder.xxlarge {
margin: 7.2px 0;
width: 96px;
}
.placeholder.inline {
display: inline-block;
}
h2 .placeholder {
background-color: rgba(3,102,214,.62);
}
</style>
<foreignObject x="0" y="0" width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml" xmlns:xlink="http://www.w3.org/1999/xlink">
<section>
<h1 class="field">
<div class="avatar"></div>
<span><div class="placeholder xxlarge"></div></span>
</h1>
<div class="row">
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0zM8 0a8 8 0 100 16A8 8 0 008 0zm.5 4.75a.75.75 0 00-1.5 0v3.5a.75.75 0 00.471.696l2.5 1a.75.75 0 00.557-1.392L8.5 7.742V4.75z"/></svg>
Joined GitHub <div class="placeholder"></div> years ago
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M5.5 3.5a2 2 0 100 4 2 2 0 000-4zM2 5.5a3.5 3.5 0 115.898 2.549 5.507 5.507 0 013.034 4.084.75.75 0 11-1.482.235 4.001 4.001 0 00-7.9 0 .75.75 0 01-1.482-.236A5.507 5.507 0 013.102 8.05 3.49 3.49 0 012 5.5zM11 4a.75.75 0 100 1.5 1.5 1.5 0 01.666 2.844.75.75 0 00-.416.672v.352a.75.75 0 00.574.73c1.2.289 2.162 1.2 2.522 2.372a.75.75 0 101.434-.44 5.01 5.01 0 00-2.56-3.012A3 3 0 0011 4z"/></svg>
Followed by <div class="placeholder"></div> users
</div>
</section>
<section>
<div class="field calendar">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 210 11" width="210" height="16">
<g>
<rect class="day" x="0" y="0" width="11" height="11" fill="#ebedf0" rx="2" ry="2"/>
<rect class="day" x="15" y="0" width="11" height="11" fill="#ebedf0" rx="2" ry="2"/>
<rect class="day" x="30" y="0" width="11" height="11" fill="#ebedf0" rx="2" ry="2"/>
<rect class="day" x="45" y="0" width="11" height="11" fill="#ebedf0" rx="2" ry="2"/>
<rect class="day" x="60" y="0" width="11" height="11" fill="#ebedf0" rx="2" ry="2"/>
<rect class="day" x="75" y="0" width="11" height="11" fill="#ebedf0" rx="2" ry="2"/>
<rect class="day" x="90" y="0" width="11" height="11" fill="#ebedf0" rx="2" ry="2"/>
<rect class="day" x="105" y="0" width="11" height="11" fill="#ebedf0" rx="2" ry="2"/>
<rect class="day" x="120" y="0" width="11" height="11" fill="#ebedf0" rx="2" ry="2"/>
<rect class="day" x="135" y="0" width="11" height="11" fill="#ebedf0" rx="2" ry="2"/>
<rect class="day" x="150" y="0" width="11" height="11" fill="#ebedf0" rx="2" ry="2"/>
<rect class="day" x="165" y="0" width="11" height="11" fill="#ebedf0" rx="2" ry="2"/>
<rect class="day" x="180" y="0" width="11" height="11" fill="#ebedf0" rx="2" ry="2"/>
<rect class="day" x="195" y="0" width="11" height="11" fill="#ebedf0" rx="2" ry="2"/>
</g>
</svg>
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1 2.5A2.5 2.5 0 013.5 0h8.75a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0V1.5h-8a1 1 0 00-1 1v6.708A2.492 2.492 0 013.5 9h3.25a.75.75 0 010 1.5H3.5a1 1 0 100 2h5.75a.75.75 0 010 1.5H3.5A2.5 2.5 0 011 11.5v-9zm13.23 7.79a.75.75 0 001.06-1.06l-2.505-2.505a.75.75 0 00-1.06 0L9.22 9.229a.75.75 0 001.06 1.061l1.225-1.224v6.184a.75.75 0 001.5 0V9.066l1.224 1.224z"/></svg>
Contributed to <div class="placeholder"></div> repositories
</div>
</section>
</div>
</section>
<div class="row">
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 1.75a.75.75 0 00-1.5 0v12.5c0 .414.336.75.75.75h14.5a.75.75 0 000-1.5H1.5V1.75zm14.28 2.53a.75.75 0 00-1.06-1.06L10 7.94 7.53 5.47a.75.75 0 00-1.06 0L3.22 8.72a.75.75 0 001.06 1.06L7 7.06l2.47 2.47a.75.75 0 001.06 0l5.25-5.25z"/></svg>
Activity
</h2>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M10.5 7.75a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zm1.43.75a4.002 4.002 0 01-7.86 0H.75a.75.75 0 110-1.5h3.32a4.001 4.001 0 017.86 0h3.32a.75.75 0 110 1.5h-3.32z"/></svg>
<div class="placeholder"></div> Commits
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.5 1.75a.25.25 0 01.25-.25h8.5a.25.25 0 01.25.25v7.736a.75.75 0 101.5 0V1.75A1.75 1.75 0 0011.25 0h-8.5A1.75 1.75 0 001 1.75v11.5c0 .966.784 1.75 1.75 1.75h3.17a.75.75 0 000-1.5H2.75a.25.25 0 01-.25-.25V1.75zM4.75 4a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-4.5zM4 7.75A.75.75 0 014.75 7h2a.75.75 0 010 1.5h-2A.75.75 0 014 7.75zm11.774 3.537a.75.75 0 00-1.048-1.074L10.7 14.145 9.281 12.72a.75.75 0 00-1.062 1.058l1.943 1.95a.75.75 0 001.055.008l4.557-4.45z"/></svg>
<div class="placeholder"></div> Pull requests reviewed
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"/></svg>
<div class="placeholder"></div> Pull requests opened
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"/></svg>
<div class="placeholder"></div> Issues opened
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.75 2.5a.25.25 0 00-.25.25v7.5c0 .138.112.25.25.25h2a.75.75 0 01.75.75v2.19l2.72-2.72a.75.75 0 01.53-.22h4.5a.25.25 0 00.25-.25v-7.5a.25.25 0 00-.25-.25H2.75zM1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0113.25 12H9.06l-2.573 2.573A1.457 1.457 0 014 13.543V12H2.75A1.75 1.75 0 011 10.25v-7.5z"></path></svg>
<div class="placeholder"></div> issue comments
</div>
</section>
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.75 0A1.75 1.75 0 000 1.75v12.5C0 15.216.784 16 1.75 16h12.5A1.75 1.75 0 0016 14.25V1.75A1.75 1.75 0 0014.25 0H1.75zM1.5 1.75a.25.25 0 01.25-.25h12.5a.25.25 0 01.25.25v12.5a.25.25 0 01-.25.25H1.75a.25.25 0 01-.25-.25V1.75zM11.75 3a.75.75 0 00-.75.75v7.5a.75.75 0 001.5 0v-7.5a.75.75 0 00-.75-.75zm-8.25.75a.75.75 0 011.5 0v5.5a.75.75 0 01-1.5 0v-5.5zM8 3a.75.75 0 00-.75.75v3.5a.75.75 0 001.5 0v-3.5A.75.75 0 008 3z"/></svg>
Community stats
</h2>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M5.5 3.5a2 2 0 100 4 2 2 0 000-4zM2 5.5a3.5 3.5 0 115.898 2.549 5.507 5.507 0 013.034 4.084.75.75 0 11-1.482.235 4.001 4.001 0 00-7.9 0 .75.75 0 01-1.482-.236A5.507 5.507 0 013.102 8.05 3.49 3.49 0 012 5.5zM11 4a.75.75 0 100 1.5 1.5 1.5 0 01.666 2.844.75.75 0 00-.416.672v.352a.75.75 0 00.574.73c1.2.289 2.162 1.2 2.522 2.372a.75.75 0 101.434-.44 5.01 5.01 0 00-2.56-3.012A3 3 0 0011 4z"/></svg>
Following <div class="placeholder"></div> users
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z"/></svg>
Sponsoring <div class="placeholder"></div> repositories
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"/></svg>
Starred <div class="placeholder"></div> repositories
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.679 7.932c.412-.621 1.242-1.75 2.366-2.717C5.175 4.242 6.527 3.5 8 3.5c1.473 0 2.824.742 3.955 1.715 1.124.967 1.954 2.096 2.366 2.717a.119.119 0 010 .136c-.412.621-1.242 1.75-2.366 2.717C10.825 11.758 9.473 12.5 8 12.5c-1.473 0-2.824-.742-3.955-1.715C2.92 9.818 2.09 8.69 1.679 8.068a.119.119 0 010-.136zM8 2c-1.981 0-3.67.992-4.933 2.078C1.797 5.169.88 6.423.43 7.1a1.619 1.619 0 000 1.798c.45.678 1.367 1.932 2.637 3.024C4.329 13.008 6.019 14 8 14c1.981 0 3.67-.992 4.933-2.078 1.27-1.091 2.187-2.345 2.637-3.023a1.619 1.619 0 000-1.798c-.45-.678-1.367-1.932-2.637-3.023C11.671 2.992 9.981 2 8 2zm0 8a2 2 0 100-4 2 2 0 000 4z"/></svg>
Watching <div class="placeholder"></div> repositories
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.5 14.25c0 .138.112.25.25.25H4v-1.25a.75.75 0 01.75-.75h2.5a.75.75 0 01.75.75v1.25h2.25a.25.25 0 00.25-.25V1.75a.25.25 0 00-.25-.25h-8.5a.25.25 0 00-.25.25v12.5zM1.75 16A1.75 1.75 0 010 14.25V1.75C0 .784.784 0 1.75 0h8.5C11.216 0 12 .784 12 1.75v12.5c0 .085-.006.168-.018.25h2.268a.25.25 0 00.25-.25V8.285a.25.25 0 00-.111-.208l-1.055-.703a.75.75 0 11.832-1.248l1.055.703c.487.325.779.871.779 1.456v5.965A1.75 1.75 0 0114.25 16h-3.5a.75.75 0 01-.197-.026c-.099.017-.2.026-.303.026h-3a.75.75 0 01-.75-.75V14h-1v1.25a.75.75 0 01-.75.75h-3zM3 3.75A.75.75 0 013.75 3h.5a.75.75 0 010 1.5h-.5A.75.75 0 013 3.75zM3.75 6a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM3 9.75A.75.75 0 013.75 9h.5a.75.75 0 010 1.5h-.5A.75.75 0 013 9.75zM7.75 9a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5zM7 6.75A.75.75 0 017.75 6h.5a.75.75 0 010 1.5h-.5A.75.75 0 017 6.75zM7.75 3a.75.75 0 000 1.5h.5a.75.75 0 000-1.5h-.5z"></path></svg>
Member of <div class="placeholder"></div> organizations
</div>
</section>
</div>
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"/></svg>
<div class="placeholder"></div> Repositories
</h2>
<div class="row">
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8.75.75a.75.75 0 00-1.5 0V2h-.984c-.305 0-.604.08-.869.23l-1.288.737A.25.25 0 013.984 3H1.75a.75.75 0 000 1.5h.428L.066 9.192a.75.75 0 00.154.838l.53-.53-.53.53v.001l.002.002.002.002.006.006.016.015.045.04a3.514 3.514 0 00.686.45A4.492 4.492 0 003 11c.88 0 1.556-.22 2.023-.454a3.515 3.515 0 00.686-.45l.045-.04.016-.015.006-.006.002-.002.001-.002L5.25 9.5l.53.53a.75.75 0 00.154-.838L3.822 4.5h.162c.305 0 .604-.08.869-.23l1.289-.737a.25.25 0 01.124-.033h.984V13h-2.5a.75.75 0 000 1.5h6.5a.75.75 0 000-1.5h-2.5V3.5h.984a.25.25 0 01.124.033l1.29.736c.264.152.563.231.868.231h.162l-2.112 4.692a.75.75 0 00.154.838l.53-.53-.53.53v.001l.002.002.002.002.006.006.016.015.045.04a3.517 3.517 0 00.686.45A4.492 4.492 0 0013 11c.88 0 1.556-.22 2.023-.454a3.512 3.512 0 00.686-.45l.045-.04.01-.01.006-.005.006-.006.002-.002.001-.002-.529-.531.53.53a.75.75 0 00.154-.838L13.823 4.5h.427a.75.75 0 000-1.5h-2.234a.25.25 0 01-.124-.033l-1.29-.736A1.75 1.75 0 009.735 2H8.75V.75zM1.695 9.227c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L3 6.327l-1.305 2.9zm10 0c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L13 6.327l-1.305 2.9z"/></svg>
Prefer <div class="placeholder large"></div> license
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8.878.392a1.75 1.75 0 00-1.756 0l-5.25 3.045A1.75 1.75 0 001 4.951v6.098c0 .624.332 1.2.872 1.514l5.25 3.045a1.75 1.75 0 001.756 0l5.25-3.045c.54-.313.872-.89.872-1.514V4.951c0-.624-.332-1.2-.872-1.514L8.878.392zM7.875 1.69a.25.25 0 01.25 0l4.63 2.685L8 7.133 3.245 4.375l4.63-2.685zM2.5 5.677v5.372c0 .09.047.171.125.216l4.625 2.683V8.432L2.5 5.677zm6.25 8.271l4.625-2.683a.25.25 0 00.125-.216V5.677L8.75 8.432v5.516z"/></svg>
<div class="placeholder"></div> Packages
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.75 1.5a.25.25 0 00-.25.25v12.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25V1.75a.25.25 0 00-.25-.25H1.75zM0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0114.25 16H1.75A1.75 1.75 0 010 14.25V1.75zm9.22 3.72a.75.75 0 000 1.06L10.69 8 9.22 9.47a.75.75 0 101.06 1.06l2-2a.75.75 0 000-1.06l-2-2a.75.75 0 00-1.06 0zM6.78 6.53a.75.75 0 00-1.06-1.06l-2 2a.75.75 0 000 1.06l2 2a.75.75 0 101.06-1.06L5.31 8l1.47-1.47z"/></svg>
<div class="placeholder"></div> Gists
</div>
<% if (plugins.lines) { %>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.72 3.22a.75.75 0 011.06 1.06L2.06 8l3.72 3.72a.75.75 0 11-1.06 1.06L.47 8.53a.75.75 0 010-1.06l4.25-4.25zm6.56 0a.75.75 0 10-1.06 1.06L13.94 8l-3.72 3.72a.75.75 0 101.06 1.06l4.25-4.25a.75.75 0 000-1.06l-4.25-4.25z"></path></svg>
<div class="placeholder"></div> added, <div class="placeholder"></div> removed
</div>
<% } %>
</section>
<section>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"/></svg>
<div class="placeholder"></div> Stargazers
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25v-.878a2.25 2.25 0 10-1.5 0v.878a.75.75 0 01-.75.75h-4.5A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"/></svg>
<div class="placeholder"></div> Forks
</div>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M1.679 7.932c.412-.621 1.242-1.75 2.366-2.717C5.175 4.242 6.527 3.5 8 3.5c1.473 0 2.824.742 3.955 1.715 1.124.967 1.954 2.096 2.366 2.717a.119.119 0 010 .136c-.412.621-1.242 1.75-2.366 2.717C10.825 11.758 9.473 12.5 8 12.5c-1.473 0-2.824-.742-3.955-1.715C2.92 9.818 2.09 8.69 1.679 8.068a.119.119 0 010-.136zM8 2c-1.981 0-3.67.992-4.933 2.078C1.797 5.169.88 6.423.43 7.1a1.619 1.619 0 000 1.798c.45.678 1.367 1.932 2.637 3.024C4.329 13.008 6.019 14 8 14c1.981 0 3.67-.992 4.933-2.078 1.27-1.091 2.187-2.345 2.637-3.023a1.619 1.619 0 000-1.798c-.45-.678-1.367-1.932-2.637-3.023C11.671 2.992 9.981 2 8 2zm0 8a2 2 0 100-4 2 2 0 000 4z"/></svg>
<div class="placeholder"></div> Watchers
</div>
<% if (plugins.traffic) { %>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M0 1.75A.75.75 0 01.75 1h4.253c1.227 0 2.317.59 3 1.501A3.744 3.744 0 0111.006 1h4.245a.75.75 0 01.75.75v10.5a.75.75 0 01-.75.75h-4.507a2.25 2.25 0 00-1.591.659l-.622.621a.75.75 0 01-1.06 0l-.622-.621A2.25 2.25 0 005.258 13H.75a.75.75 0 01-.75-.75V1.75zm8.755 3a2.25 2.25 0 012.25-2.25H14.5v9h-3.757c-.71 0-1.4.201-1.992.572l.004-7.322zm-1.504 7.324l.004-5.073-.002-2.253A2.25 2.25 0 005.003 2.5H1.5v9h3.757a3.75 3.75 0 011.994.574z"></path></svg>
<div class="placeholder"></div> views in last two weeks
</div>
<% } %>
</section>
</div>
</section>
<% if (plugins.followup) { %>
<div class="row">
<section class="column">
<h3>Issues</h3>
<svg class="bar" xmlns="http://www.w3.org/2000/svg" width="220" height="8">
<mask id="issues-bar">
<rect x="0" y="0" width="220" height="8" fill="white" rx="5"/>
</mask>
<rect mask="url(#issues-bar)" x="0" y="0" width="220" height="8" fill="#d1d5da"/>
</svg>
<div class="field horizontal fill-width">
<div class="field center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#d73a49" fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 0110.65-5.003.75.75 0 00.959-1.153 8 8 0 102.592 8.33.75.75 0 10-1.444-.407A6.5 6.5 0 011.5 8zM8 12a1 1 0 100-2 1 1 0 000 2zm0-8a.75.75 0 01.75.75v3.5a.75.75 0 11-1.5 0v-3.5A.75.75 0 018 4zm4.78 4.28l3-3a.75.75 0 00-1.06-1.06l-2.47 2.47-.97-.97a.749.749 0 10-1.06 1.06l1.5 1.5a.75.75 0 001.06 0z"/></svg>
<div class="placeholder"></div> Closed
</div>
<div class="field center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#28a745" fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"/></svg>
<div class="placeholder"></div> Open
</div>
</div>
</section>
<section class="column">
<h3>Pull requests</h3>
<svg class="bar" xmlns="http://www.w3.org/2000/svg" width="220" height="8">
<mask id="pr-bar">
<rect x="0" y="0" width="220" height="8" fill="white" rx="5"/>
</mask>
<rect mask="url(#pr-bar)" x="0" y="0" width="220" height="8" fill="#d1d5da"/>
</svg>
<div class="field horizontal fill-width">
<div class="field center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#6f42c1" fill-rule="evenodd" d="M5 3.254V3.25v.005a.75.75 0 110-.005v.004zm.45 1.9a2.25 2.25 0 10-1.95.218v5.256a2.25 2.25 0 101.5 0V7.123A5.735 5.735 0 009.25 9h1.378a2.251 2.251 0 100-1.5H9.25a4.25 4.25 0 01-3.8-2.346zM12.75 9a.75.75 0 100-1.5.75.75 0 000 1.5zm-8.5 4.5a.75.75 0 100-1.5.75.75 0 000 1.5z"/></svg>
<div class="placeholder"></div> Merged
</div>
<div class="field center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#28a745" fill-rule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"/></svg>
<div class="placeholder"></div> Open
</div>
</div>
</section>
</div>
<% } %>
<% if (plugins.languages) { %>
<section class="column">
<h3>Most used languages</h3>
<svg class="bar" xmlns="http://www.w3.org/2000/svg" width="460" height="8">
<mask id="languages-bar">
<rect x="0" y="0" width="460" height="8" fill="white" rx="5"/>
</mask>
<rect mask="url(#languages-bar)" x="0" y="0" width="460" height="8" fill="#d1d5da"/>
</svg>
<div class="field horizontal horizontal-wrap fill-width">
<div class="field center no-wrap language">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#d1d5da" fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8z"/></svg>
<div class="placeholder xlarge"></div>
</div>
<div class="field center no-wrap language">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#d1d5da" fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8z"/></svg>
<div class="placeholder xlarge"></div>
</div>
<div class="field center no-wrap language">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#d1d5da" fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8z"/></svg>
<div class="placeholder xlarge"></div>
</div>
<div class="field center no-wrap language">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#d1d5da" fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8z"/></svg>
<div class="placeholder xlarge"></div>
</div>
<div class="field center no-wrap language">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#d1d5da" fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8z"/></svg>
<div class="placeholder xlarge"></div>
</div>
<div class="field center no-wrap language">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#d1d5da" fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8z"/></svg>
<div class="placeholder xlarge"></div>
</div>
<div class="field center no-wrap language">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#d1d5da" fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8z"/></svg>
<div class="placeholder xlarge"></div>
</div>
</div>
</section>
<% } %>
<% if (plugins.pagespeed) { %>
<div class="row">
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M14.064 0a8.75 8.75 0 00-6.187 2.563l-.459.458c-.314.314-.616.641-.904.979H3.31a1.75 1.75 0 00-1.49.833L.11 7.607a.75.75 0 00.418 1.11l3.102.954c.037.051.079.1.124.145l2.429 2.428c.046.046.094.088.145.125l.954 3.102a.75.75 0 001.11.418l2.774-1.707a1.75 1.75 0 00.833-1.49V9.485c.338-.288.665-.59.979-.904l.458-.459A8.75 8.75 0 0016 1.936V1.75A1.75 1.75 0 0014.25 0h-.186zM10.5 10.625c-.088.06-.177.118-.266.175l-2.35 1.521.548 1.783 1.949-1.2a.25.25 0 00.119-.213v-2.066zM3.678 8.116L5.2 5.766c.058-.09.117-.178.176-.266H3.309a.25.25 0 00-.213.119l-1.2 1.95 1.782.547zm5.26-4.493A7.25 7.25 0 0114.063 1.5h.186a.25.25 0 01.25.25v.186a7.25 7.25 0 01-2.123 5.127l-.459.458a15.21 15.21 0 01-2.499 2.02l-2.317 1.5-2.143-2.143 1.5-2.317a15.25 15.25 0 012.02-2.5l.458-.458h.002zM12 5a1 1 0 11-2 0 1 1 0 012 0zm-8.44 9.56a1.5 1.5 0 10-2.12-2.12c-.734.73-1.047 2.332-1.15 3.003a.23.23 0 00.265.265c.671-.103 2.273-.416 3.005-1.148z"></path></svg>
PageSpeed Insights
</h2>
<div class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z"></path></svg>
<div class="placeholder xlarge"></div>
</div>
</section>
</div>
<section>
<div class="row fill-width">
<section class="categories">
<div class="categorie column">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="50" height="50" class="gauge">
<circle class="gauge-base" r="53" cx="60" cy="60"></circle>
<text x="60" y="60" dominant-baseline="central" >-</text>
</svg>
<span class="title">Performance</span>
</div>
<div class="categorie column">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="50" height="50" class="gauge">
<circle class="gauge-base" r="53" cx="60" cy="60"></circle>
<text x="60" y="60" dominant-baseline="central" >-</text>
</svg>
<span class="title">Accessibility</span>
</div>
<div class="categorie column">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="50" height="50" class="gauge">
<circle class="gauge-base" r="53" cx="60" cy="60"></circle>
<text x="60" y="60" dominant-baseline="central" >-</text>
</svg>
<span class="title">Best Practices</span>
</div>
<div class="categorie column">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" width="50" height="50" class="gauge">
<circle class="gauge-base" r="53" cx="60" cy="60"></circle>
<text x="60" y="60" dominant-baseline="central" >-</text>
</svg>
<span class="title">SEO</span>
</div>
</section>
</div>
</section>
<% } %>
<% if (plugins.habits) { %>
<section>
<h2 class="field">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 01-1.484.211c-.04-.282-.163-.547-.37-.847a8.695 8.695 0 00-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.75.75 0 01-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75zM6 15.25a.75.75 0 01.75-.75h2.5a.75.75 0 010 1.5h-2.5a.75.75 0 01-.75-.75zM5.75 12a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-4.5z"></path></svg>
Coding habits
</h2>
<div class="row">
<ul class="habits">
<li>Use <div class="placeholder inline large"></div> for indents</li>
<li>Mostly push code around <div class="placeholder inline"></div></li>
</ul>
</div>
</section>
<% } %>
</div>
</foreignObject>
</svg>

Before

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -2,7 +2,7 @@
export default async function ({login, q}, {conf, data, rest, graphql, plugins}, {s, pending, imports}) {
//Init
const computed = data.computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, svg:{height:355, width:480}, token:{}, repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_merged:0, forks:0}, plugins:{}}
const computed = data.computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, token:{}, repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_merged:0, forks:0}, plugins:{}}
const avatar = imports.imgb64(data.user.avatarUrl)
//Plugins
@@ -49,4 +49,7 @@
//Token scopes
computed.token.scopes = (await rest.request("HEAD /")).headers["x-oauth-scopes"].split(", ")
//Meta
data.meta = {version:conf.package.version, author:conf.package.author}
}

View File

@@ -1,30 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" width="<%= computed.svg.width %>" height="<%= computed.svg.height %>">
<svg xmlns="http://www.w3.org/2000/svg" width="480" height="<%= 48
+ (!!base.header)*(16*5)
+ (!!base.metadata)*(16*9)
+ (!!base.activity)*(16*10)
+ (!!base.community)*(16*9)
+ (!!base.repositories)*(16*13)
+ (!!computed.plugins.traffic)*(16*1)
+ (!!computed.plugins.followup)*(16*6)
+ (!!computed.plugins.lines)*(16*3)
+ (!!computed.plugins.languages)*(16*12)
+ (!!computed.plugins.pagespeed)*(16*10)
%>">
<%
meta.$ = `<span class="ps1-path">${user.login}@metrics</span>:<span class="ps1-location">~</span>${computed.token.scopes.includes("repo") ? "#" : "$"}`
meta.animations = !meta.placeholder ? {stdin:.16, stdout:.28, length:(2+Object.keys(base).length+Object.keys(computed.plugins).length)} : {stdin:0, stdout:0, length:0}
%>
<style>
<%= style %>
.stdin, .stdout {
animation-duration: .2s;
animation-duration: <%= meta.animations.stdin %>s;
}
.stdout {
animation-duration: .2s;
animation-duration: <%= meta.animations.stdout %>s;
}
<% for (let i = 0; i < 12; i++) { %>
<% for (let i = 0, d = 0; i < meta.animations.length; i++, d+=meta.animations.stdin+meta.animations.stdout) { %>
.stdin:nth-of-type(<%= i+1 %>) {
animation-delay: <%= i*.2 %>s;
animation-delay: <%= d %>s;
}
.stdout:nth-of-type(<%= i+2 %>) {
animation-delay: <%= (i+1)*.2 %>s;
animation-delay: <%= d+meta.animations.stdin %>s;
}
<% if (i === meta.animations.length-1) { %>
footer {
animation-delay: <%= d %>s;
}
<% } %>
footer {
animation-delay: <%= 12*.2 %>s;
}
<% } %>
</style>
<foreignObject x="0" y="0" width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml" xmlns:xlink="http://www.w3.org/1999/xlink">
<div class="header">
<span class="title">GitHub metrics</span>
<span class="title">GitHub metrics v<%= meta.version %></span>
<div class="buttons">
<div class="button"></div>
<div class="button"></div>
@@ -32,40 +50,51 @@
</div>
</div>
<pre><span class="banner">GitHub metrics generator <%= meta.version %>
<pre><div style="margin-top:-16px"></div><%# -%>
<% if (base.metadata) { %>
<span class="banner"><%# -%>
GitHub metrics generator <%= meta.version %>
These generated metrics comes with ABSOLUTELY NO WARRANTY,
to the extent permitted by applicable law.
Last generated: <%= new Date().toGMTString() %>
</span>
<div class="stdin"><%- meta.$ %> whoami</div><!--
--><div class="stdout"><!--
--><b><%= user.name || user.login %></b> registered=<%= computed.registration %>, uid=<%= `${user.databaseId}`.substr(-4) %>, gid=<%= user.organizations.totalCount %>, followers=<%= user.followers.totalCount %>
</span><% } -%>
<%# ============================================================= -%>
<% if (base.header) { %>
<div class="stdin"><%- meta.$ %> whoami</div><%# -%>
<div class="stdout"><%# -%>
<b><%= user.name || user.login %></b> registered=<%= computed.registration.match(/^.+? [ym]/)[0].replace(/ /g, "") %>, uid=<%= `${user.databaseId}`.substr(-4) %>, gid=<%= user.organizations.totalCount %>, followers=<%= user.followers.totalCount %>
contributed to <%= user.repositoriesContributedTo.totalCount %> repositor<%= s(user.repositoriesContributedTo.totalCount, "y") %> <b><% for (const [x, {color}] of Object.entries(computed.calendar)) { -%><span style="color:<%= color %>">#</span><% } %></b>
</div>
<div class="stdin"><%- meta.$ %> git status</div><!--
--><div class="stdout"><b>Recent activity</b><!--
--><b><%= `${computed.commits}`.padStart(5) %></b> commit<%= s(computed.commits) %>
</div><% } -%>
<%# ============================================================= -%>
<% if ((base.activity)||(base.community)) { %>
<div class="stdin"><%- meta.$ %> git status</div><%# -%>
<div class="stdout"><%# -%>
<% if (base.activity) { -%>
<b>Recent activity</b>
<b><%= `${computed.commits}`.padStart(5) %></b> commit<%= s(computed.commits) %>
<b><%= `${user.contributionsCollection.totalPullRequestReviewContributions}`.padStart(5) %></b> pull request<%= s(user.contributionsCollection.totalPullRequestReviewContributions) %> reviewed
<b><%= `${user.contributionsCollection.totalPullRequestContributions}`.padStart(5) %></b> pull request<%= s(user.contributionsCollection.totalPullRequestContributions) %> opened
<b><%= `${user.contributionsCollection.totalIssueContributions}`.padStart(5) %></b> issue<%= s(user.contributionsCollection.totalIssueContributions) %> opened
<b><%= `${user.issueComments.totalCount}`.padStart(5) %></b> issue comment<%= s(user.issueComments.totalCount) %>
<% } -%>
<% if ((base.activity)&&(base.community)) { -%>
<% } -%>
<% if (base.community) { -%>
<b>Tracked activity</b>
<b><%= `${user.following.totalCount}`.padStart(5) %></b> user<%= s(user.followers.totalCount) %> followed
<b><%= `${computed.sponsorships}`.padStart(5) %></b> repositor<%= s(computed.sponsorships, "y") %> sponsored
<b><%= `${user.starredRepositories.totalCount}`.padStart(5) %></b> repositor<%= s(user.starredRepositories.totalCount, "y") %> starred
<b><%= `${user.watching.totalCount}`.padStart(5) %></b> repositor<%= s(user.watching.totalCount, "y") %> watched
<% if (computed.plugins.lines) { -%><% if (computed.plugins.lines.error) { -%>
<span class="diff error">@@ <%= computed.plugins.lines.error %> @@</span><% } else { -%>
<span class="diff">@@ -<%= computed.plugins.lines.deleted %> +<%= computed.plugins.lines.added %> @@</span>
<% }} -%></div>
<div class="stdin"><%- meta.$ %> ls -lh github/repositories</div><!--
--><div class="stdout"><!--
-->Total <%= user.repositories.totalCount %> repositor<%= s(user.repositories.totalCount, "y") %>
<% if (computed.plugins.traffic) { -%><% if (computed.plugins.traffic.error) { -%>
<% } -%>
</div><% } -%>
<%# ============================================================= -%>
<% if (base.repositories) { %>
<div class="stdin"><%- meta.$ %> ls -lh github/repositories</div><%# -%>
<div class="stdout"><%# -%>
Total <%= user.repositories.totalCount %> repositor<%= s(user.repositories.totalCount, "y") %>
<% if (computed.plugins.traffic) { if (computed.plugins.traffic.error) { -%>
---- <b> </b> views <span class="error">(<%= computed.plugins.traffic.error %>)</span>
<% } else { -%>
-r-- <b><%= `${computed.plugins.traffic.views.count}`.padStart(5) %></b> views
@@ -75,7 +104,7 @@ Last generated: <%= new Date().toGMTString() %>
-r-- <b><%= `${computed.repositories.watchers}`.padStart(5) %></b> watcher<%= s(computed.repositories.watchers) %>
dr-x <b><%= `${user.packages.totalCount}`.padStart(5) %></b> package<%= s(user.packages.totalCount) %>
dr-x <b><%= `${user.gists.totalCount}`.padStart(5) %></b> gist<%= s(user.gists.totalCount) %>
<% if (computed.plugins.followup) { -%><% if (computed.plugins.followup.error) { -%>
<% if (computed.plugins.followup) { if (computed.plugins.followup.error) { -%>
d--- <b> </b> ISSUES <span class="error">(<%= computed.plugins.followup.error %>)</span>
d--- <b> </b> PULL_REQUESTS <span class="error">(<%= computed.plugins.followup.error %>)</span>
<% } else { -%>
@@ -90,33 +119,39 @@ dr-x <b><%= `${computed.plugins.followup.issues.count}`.padStart(5) %></b> PUL
dr-x LICENSE
-r-- └── <%= computed.licenses.favorite %>
<% } -%>
</div><% if (computed.plugins.languages) { -%><% if (computed.plugins.languages.error) { -%>
<div class="stdin"><%- meta.$ %> locale</div><!--
--><div class="stdout"><!--
--><span class="error"><%= computed.plugins.languages.error %></span>
<% } else { -%>
<div class="stdin"><%- meta.$ %> locale</div><!--
--><div class="stdout"><!--
--><% for (const {name, value} of computed.plugins.languages.favorites) { -%>
<% if (computed.plugins.lines) { if (computed.plugins.lines.error) { %>
<span class="diff error">@@ <%= computed.plugins.lines.error %> @@</span><% } else { %>
<span class="diff">@@ -<%= computed.plugins.lines.deleted %> +<%= computed.plugins.lines.added %> @@</span>
<% }} -%>
</div><% } -%>
<%# ============================================================= -%>
<% if (computed.plugins.languages) { %>
<div class="stdin"><%- meta.$ %> locale</div><%# -%>
<div class="stdout"><%# -%>
<% if (computed.plugins.languages.error) { -%>
<span class="error"><%= computed.plugins.languages.error %></span><%# -%>
<% } else { for (const {name, value} of computed.plugins.languages.favorites) { -%>
<b><%= name.toLocaleUpperCase().padEnd(12) %></b> [<%= "#".repeat(Math.ceil(100*value/5)).padEnd(20) %>] <%= (100*value).toFixed(2).padEnd(5) %>%
<% }} -%></div><% } -%><% if (computed.plugins.pagespeed) { -%><% if (computed.plugins.pagespeed.error) { -%>
<div class="stdin"><%- meta.$ %> curl -I <%= user.websiteUrl %></div><!--
--><div class="stdout"><!--
--><span class="error"><%= computed.plugins.pagespeed.error %></span>
<% } else { -%>
<div class="stdin"><%- meta.$ %> curl -I <%= user.websiteUrl %></div><!--
--><div class="stdout"><!--
--><b>User-Agent</b>: Google PageSpeed API
<% }} -%>
</div><% } -%>
<%# ============================================================= -%>
<% if (computed.plugins.pagespeed) { %>
<div class="stdin"><%- meta.$ %> curl -I <%= user.websiteUrl %></div><%# -%>
<div class="stdout"><%# -%>
<% if (computed.plugins.pagespeed.error) { -%>
<span class="error"><%= computed.plugins.pagespeed.error %></span><% } else { -%>
<b>User-Agent</b>: Google PageSpeed API
<b>Location</b>: <%= user.websiteUrl %>
<% for (const {score, title} of computed.plugins.pagespeed.scores) { -%>
<b><%= `X-${title.replace(/ /g, "-")}` %></b>: <%= Math.round(score*100) %>%
<% }} -%></div><% } -%>
<b><%= `X-${title.replace(/ /g, "-")}` %></b>: <%= !Number.isNaN(score) ? Math.round(score*100) : "-" %>%
<% }} -%>
</div><% } -%>
<%# ============================================================= -%>
<% if (base.metadata) { -%>
<footer>Connection reset by 127.0.0.1</footer><%# -%>
<% } -%></pre>
<footer>Connection reset by 127.0.0.1</footer></pre>
</div>
</foreignObject>
</svg>

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -1,88 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="480" height="<%= 640 + 80 + (!!plugins.followup)*(100+32) + (!!plugins.languages)*(170+32) + (!!plugins.lines)*(30+32) + (!!plugins.traffic)*(16+32) + (!!plugins.pagespeed)*(136+32) %>">
<style>
<%= style %>
</style>
<foreignObject x="0" y="0" width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml" xmlns:xlink="http://www.w3.org/1999/xlink">
<div class="header">
<span class="title">GitHub metrics</span>
<div class="buttons">
<div class="button"></div>
<div class="button"></div>
<div class="button exit"></div>
</div>
</div>
<pre><span class="banner">GitHub metrics generator X.Y.Z
These generated metrics comes with ABSOLUTELY NO WARRANTY,
to the extent permitted by applicable law.
Last generated: <%= new Date().toGMTString() %>
</span>
<div class="stdin"><span class="ps1-path">▇▇▇▇@metrics</span>:<span class="ps1-location">~</span># whoami</div><!--
--><div class="stdout"><!--
--><b>▇▇▇▇</b> registered=▇▇, uid=▇▇, gid=▇▇, followers=▇▇
contributed to ▇▇ repositories <b style="color:#ebedf0">##############</b>
</div>
<div class="stdin"><span class="ps1-path">▇▇▇▇@metrics</span>:<span class="ps1-location">~</span># git status</div><!--
--><div class="stdout"><b>Recent activity</b><!--
--><b><%= `▇▇`.padStart(5) %></b> commits
<b><%= `▇▇`.padStart(5) %></b> pull requests reviewed
<b><%= `▇▇`.padStart(5) %></b> pull requests opened
<b><%= `▇▇`.padStart(5) %></b> issues opened
<b><%= `▇▇`.padStart(5) %></b> issue comments
<b>Tracked activity</b>
<b><%= `▇▇`.padStart(5) %></b> users followed
<b><%= `▇▇`.padStart(5) %></b> repositories sponsored
<b><%= `▇▇`.padStart(5) %></b> repositories starred
<b><%= `▇▇`.padStart(5) %></b> repositories watched
<% if (plugins.lines) { -%>
<span class="diff">@@ -▇▇ +▇▇ @@</span>
<% } -%></div>
<div class="stdin"><span class="ps1-path">▇▇▇▇@metrics</span>:<span class="ps1-location">~</span># ls -lh github/repositories</div><!--
--><div class="stdout"><!--
-->Total ▇▇ repositories
<% if (plugins.traffic) { -%>
-r-- <b><%= `▇▇`.padStart(5) %></b> views
<% } -%>
-r-- <b><%= `▇▇`.padStart(5) %></b> stargazers
-r-- <b><%= `▇▇`.padStart(5) %></b> forks
-r-- <b><%= `▇▇`.padStart(5) %></b> watchers
dr-x <b><%= `▇▇`.padStart(5) %></b> packages
dr-x <b><%= `▇▇`.padStart(5) %></b> gists
<% if (plugins.followup) { -%>
dr-x <b><%= `▇▇`.padStart(5) %></b> ISSUES
-r-- <b><%= `▇▇`.padStart(5) %></b> ├── open
-r-- <b><%= `▇▇`.padStart(5) %></b> └── closed
dr-x <b><%= `▇▇`.padStart(5) %></b> PULL_REQUESTS
-r-- <b><%= `▇▇`.padStart(5) %></b> ├── open
-r-- <b><%= `▇▇`.padStart(5) %></b> └── merged
<% } -%>
dr-x LICENSE
-r-- └── ▇▇▇▇
</div><% if (plugins.languages) { -%>
<div class="stdin"><span class="ps1-path">▇▇▇▇@metrics</span>:<span class="ps1-location">~</span># locale</div><!--
--><div class="stdout"><!--
--><% for (const value of [0.6, 0.2, 0.1, 0.05, 0.05, 0, 0, 0]) { -%>
<b><%= `▇▇▇▇▇▇`.padEnd(12) %></b> [<%= "#".repeat(Math.ceil(100*value/5)).padEnd(20) %>] ▇%
<% } -%></div><% } -%><% if (plugins.pagespeed) { -%>
<div class="stdin"><span class="ps1-path">▇▇▇▇@metrics</span>:<span class="ps1-location">~</span># curl -I ▇▇▇▇▇▇</div><!--
--><div class="stdout"><!--
--><b>User-Agent</b>: Google PageSpeed API
<b>Location</b>: ▇▇▇▇▇▇
<b>X-Performance</b>: ▇%
<b>X-Accessibility</b>: ▇%
<b>X-Best-Practices</b>: ▇%
<b>X-SEO</b>: ▇%</div><% } -%>
<footer>Connection reset by 127.0.0.1</footer></pre>
</div>
</foreignObject>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -3,33 +3,5 @@
/** Template processor */
export default async function ({login, q}, {conf, data, rest, graphql, plugins}, {s, pending, imports}) {
//Common
await common(...arguments)
const computed = data.computed
await Promise.all(pending)
//Compute image size
computed.svg = {height:640, width:480}
if (computed.plugins.followup)
computed.svg.height += 100
if (computed.plugins.lines)
computed.svg.height += 30
if (computed.plugins.traffic)
computed.svg.height += 16
if (computed.plugins.pagespeed)
computed.svg.height += 136
if (computed.plugins.languages)
computed.svg.height += 170
//Compute registration date
const diff = (Date.now()-(new Date(data.user.createdAt)).getTime())/(365*24*60*60*1000)
const years = Math.floor(diff)
const months = Math.ceil((diff-years)*12)
computed.registration = years ? `${years}y` : `${months}m`
//Meta
data.meta = {
version:conf.package.version,
$:`<span class="ps1-path">${data.user.login}@metrics</span>:<span class="ps1-location">~</span>${computed.token.scopes.includes("repo") ? "#" : "$"}`,
}
}

View File

@@ -12,6 +12,7 @@
//Initialization
const __dirname = path.join(path.dirname(url.fileURLToPath(import.meta.url)), "..", "action")
process.on("unhandledRejection", error => { throw error })
const templates = (await fs.promises.readdir(path.join(__dirname, "..", "src/templates"))).filter(name => !/.*[.]mjs$/.test(name))
/** Test function */
export default async function test() {
@@ -22,12 +23,14 @@
//Perform tests
await test.build()
for (const template of [
"classic",
"terminal",
]) {
for (const template of templates) {
for (const q of [
{},
{"base.header":0},
{"base.activity":0},
{"base.community":0},
{"base.repositories":0},
{"base.metadata":0},
{followup:1},
{languages:1},
{followup:1, languages:1},
@@ -36,7 +39,7 @@
{traffic:1},
{selfskip:1},
{pagespeed:1},
{followup:1, languages:1, habits:1, "habits.events":1, lines:1, traffic:1, selfskip:1, pagespeed:1}
{followup:1, languages:1, habits:1, "habits.events":1, lines:1, traffic:1, selfskip:1, pagespeed:1},
]) {
await test.metrics({graphql, rest, q:{template, repositories:1, ...q}})
}
@@ -46,7 +49,7 @@
/** Metrics tests */
test.metrics = async function ({graphql, rest, q}) {
//Preparation
console.log(`### Checking metrics with plugins [${Object.keys(q).filter(key => /^\w+$/.test(key)).join(", ")}]`)
console.log(`### Checking metrics with [${Object.entries(q).map(([key, value]) => `${key}=${value}`).join(", ")}]`)
const plugins = {
lines:{enabled:true},
traffic:{enabled:true},