Full plugins and server redesign

- Languages, issues and pr are now plugins (but enabled by default for retro-compatibility)
- Query parameters are now parsed correctly
- Redesigned server index with vue.js
This commit is contained in:
lowlighter
2020-10-20 21:56:05 +02:00
parent 69e63541dc
commit 9bd7da1740
20 changed files with 718 additions and 394 deletions

BIN
.github/readme/imgs/plugin_followup.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
.github/readme/imgs/plugin_languages.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -501,6 +501,74 @@ Add the following to your `settings.json` and pass `?habits=1` in url when gener
</details> </details>
#### ✒️ Follow-up
The *follow-up* plugin allows you to compute the ratio of opened/closed issues and the ratio of opened/merged pull requests on your repositories, which shows whether most of them are maintened or not.
![Follow-up plugin](https://github.com/lowlighter/metrics/blob/master/.github/readme/imgs/plugin_followup.png)
<details>
<summary>💬 About</summary>
This plugin is enabled by default. To disable it, explicitly opt-out.
##### Setup with GitHub actions
Add the following to your workflow :
```yaml
- uses: lowlighter/metrics@latest
with:
# ... other options
plugin_followup: yes
```
##### Setup in your own instance
Add the following to your `settings.json` and pass `?followup=1` in url when generating metrics.
```json
"plugins":{
"followup":{
"enabled":true,
}
}
```
</details>
#### 🈷️ Languages
The *languages* plugin allows you to compute which languages you use the most in your repositories.
![Languages plugin](https://github.com/lowlighter/metrics/blob/master/.github/readme/imgs/plugin_languages.png)
<details>
<summary>💬 About</summary>
This plugin is enabled by default. To disable it, explicitly opt-out.
##### Setup with GitHub actions
Add the following to your workflow :
```yaml
- uses: lowlighter/metrics@latest
with:
# ... other options
plugin_languages: yes
```
##### Setup in your own instance
Add the following to your `settings.json` and pass `?languages=1` in url when generating metrics.
```json
"plugins":{
"languages":{
"enabled":true,
}
}
```
</details>
#### ⏭️ Selfskip #### ⏭️ Selfskip
The *selfskip* plugin allows you to count out all commits tagged with `[Skip GitHub Action]` you authored on your personal repository from your reported commit counts. The *selfskip* plugin allows you to count out all commits tagged with `[Skip GitHub Action]` you authored on your personal repository from your reported commit counts.
@@ -612,6 +680,8 @@ This way you'll be able to rapidly test SVG renders with your browser.
* To make HTTP/S requests * To make HTTP/S requests
* [actions/toolkit](https://github.com/actions/toolkit/tree/master) and [vercel/ncc](https://github.com/vercel/ncc) * [actions/toolkit](https://github.com/actions/toolkit/tree/master) and [vercel/ncc](https://github.com/vercel/ncc)
* To build the GitHub Action * To build the GitHub Action
* [vuejs/vue](https://github.com/vuejs/vue)
* To display server application
All icons were ripped across GitHub's site, but still remains the intellectual property of GitHub. All icons were ripped across GitHub's site, but still remains the intellectual property of GitHub.
See [GitHub Logos and Usage](https://github.com/logos) for more information. See [GitHub Logos and Usage](https://github.com/logos) for more information.

View File

@@ -41,6 +41,12 @@ inputs:
plugin_selfskip: plugin_selfskip:
description: Skip commits flagged with [Skip GitHub Action] from commits count description: Skip commits flagged with [Skip GitHub Action] from commits count
default: no default: no
plugin_languages:
description: Enable most used languages metrics
default: yes
plugin_followup:
description: Enable owned repositories issues and pull requests metrics
default: yes
debug: debug:
description: Enable debug logs description: Enable debug logs
default: no default: no

File diff suppressed because one or more lines are too long

5
package-lock.json generated
View File

@@ -1566,6 +1566,11 @@
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
}, },
"vue": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz",
"integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg=="
},
"wide-align": { "wide-align": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",

View File

@@ -7,7 +7,7 @@
"start": "node index.mjs", "start": "node index.mjs",
"build": "node utils/build.mjs", "build": "node utils/build.mjs",
"test": "node tests/metrics.mjs", "test": "node tests/metrics.mjs",
"upgrade": "npm install @actions/core@latest @actions/github@latest @octokit/graphql@latest @octokit/rest@latest axios@latest compression@latest ejs@latest express@latest express-rate-limit@latest image-to-base64@latest memory-cache@latest svgo@latest @vercel/ncc@latest libxmljs@latest" "upgrade": "npm install @actions/core@latest @actions/github@latest @octokit/graphql@latest @octokit/rest@latest axios@latest compression@latest ejs@latest express@latest express-rate-limit@latest image-to-base64@latest memory-cache@latest svgo@latest vue@latest @vercel/ncc@latest libxmljs@latest"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -31,7 +31,8 @@
"express-rate-limit": "^5.1.3", "express-rate-limit": "^5.1.3",
"image-to-base64": "^2.1.1", "image-to-base64": "^2.1.1",
"memory-cache": "^0.2.0", "memory-cache": "^0.2.0",
"svgo": "^1.3.2" "svgo": "^1.3.2",
"vue": "^2.6.12"
}, },
"devDependencies": { "devDependencies": {
"@vercel/ncc": "^0.24.1", "@vercel/ncc": "^0.24.1",

View File

@@ -27,6 +27,12 @@
"habits":{ "//":"Habits plugin", "habits":{ "//":"Habits plugin",
"enabled":true, "//":"Enable or disable coding habits metrics", "enabled":true, "//":"Enable or disable coding habits metrics",
"from":50, "//":"Number of activity events to base habits on (up to 100)" "from":50, "//":"Number of activity events to base habits on (up to 100)"
},
"languages":{ "//":"Languages plugins",
"enabled":true, "//":"Enable or disable most used languages metrics (*this plugin is enabled by default)"
},
"followup":{ "//":"Follow-up plugin",
"enabled":true, "//":"Enable owned repositories issues and pull requests metrics (*this plugin is enabled by default)"
} }
} }
} }

View File

@@ -7,6 +7,7 @@
import compression from "compression" import compression from "compression"
import setup from "./setup.mjs" import setup from "./setup.mjs"
import metrics from "./metrics.mjs" import metrics from "./metrics.mjs"
import Templates from "./templates/index.mjs"
/** App */ /** App */
export default async function () { export default async function () {
@@ -40,10 +41,24 @@
//Base routes //Base routes
const limiter = ratelimit({max:60, windowMs:60*1000}) const limiter = ratelimit({max:60, windowMs:60*1000})
const templates = [...new Set([conf.settings.templates.default, ...(conf.settings.templates.enabled.length ? Object.keys(Templates).filter(key => conf.settings.templates.enabled.includes(key)) : Object.keys(Templates))])]
const enabled = Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => key)
app.get("/", limiter, (req, res) => res.sendFile(`${conf.statics}/index.html`)) 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("/index.html", limiter, (req, res) => res.sendFile(`${conf.statics}/index.html`))
app.get("/placeholder.svg", limiter, (req, res) => res.sendFile(`${conf.statics}/placeholder.svg`))
app.get("/favicon.ico", limiter, (req, res) => res.sendStatus(204)) 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("/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`))
app.get("/vue.min.js", limiter, (req, res) => res.sendFile(`${conf.node_modules}/vue/dist/vue.min.js`))
app.get("/placeholder.svg", limiter, async (req, res) => {
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})
})
//Metrics //Metrics
app.get("/:login", ...middlewares, async (req, res) => { app.get("/:login", ...middlewares, async (req, res) => {
@@ -69,7 +84,7 @@
//Compute rendering //Compute rendering
try { try {
//Render //Render
const rendered = await metrics({login, q:req.query}, {graphql, rest, plugins, conf}) const rendered = await metrics({login, q:parse(req.query)}, {graphql, rest, plugins, conf})
//Cache //Cache
if ((!debug)&&(cached)) if ((!debug)&&(cached))
cache.put(login, rendered, cached) cache.put(login, rendered, cached)
@@ -103,6 +118,22 @@
`Cached time | ${cached} seconds`, `Cached time | ${cached} seconds`,
`Rate limiter | ${ratelimiter ? JSON.stringify(ratelimiter) : "(enabled)"}`, `Rate limiter | ${ratelimiter ? JSON.stringify(ratelimiter) : "(enabled)"}`,
`Max simultaneous users | ${maxusers ? `${maxusers} users` : "(unrestricted)"}`, `Max simultaneous users | ${maxusers ? `${maxusers} users` : "(unrestricted)"}`,
`Plugins enabled | ${Object.entries(plugins).filter(([key, plugin]) => plugin.enabled).map(([key]) => key).join(", ")}` `Plugins enabled | ${enabled.join(", ")}`
].join("\n"))) ].join("\n")))
} }
/** Query parser */
function parse(query) {
for (const [key, value] of Object.entries(query)) {
//Parse number
if (/^\d+$/.test(value))
query[key] = Number(value)
//Parse boolean
if (/^true|false$/.test(value))
query[key] = !!value
//Parse null
if (/^null$/.test(value))
query[key] = null
}
return query
}

View File

@@ -9,93 +9,172 @@
</head> </head>
<body> <body>
<h1><a href="https://github.com/lowlighter/metrics">GitHub metrics</a></h1> <main :class="[palette]">
<p> <h1><a href="https://github.com/lowlighter/metrics">Generate your metrics !</a></h1>
Enter your GitHub username below to generate your metrics.
</p>
<label> <div class="step">
<input type="text" name="user" placeholder="@username" value=""> <h2>1. Enter your GitHub username</h2>
</label> <label>
<input type="text" v-model="user" maxlength="39" placeholder="GitHub username" :disabled="generated.pending">
<div id="metrics"> </label>
<img class="placeholder" src="/placeholder.svg">
<img class="generated" src="/placeholder.svg">
</div>
<aside>
Embed these metrics on your GitHub profile by adding the markdown below in your <i>README.md</i> at <a id="user-repo" href="#"><span class="user"></span>/<span class="user"></span></a>
<br>
<div class="code">
![<span class="md-alt">GitHub metrics</span>](<span class="url">https://metrics.lecoq.io/my-github-user</span>)
</div> </div>
For even more metrics (coding habits, PageSpeed performances, number of line of code you wrote, page views, etc.), setup this as a <a href="https://github.com/marketplace/actions/github-metrics-as-svg-image">GitHub action</a> on your repository !<br>
Check out <a href="https://github.com/lowlighter/metrics">lowlighter/metrics</a> for more informations
</aside>
<div class="step">
<h2>2. Select a template and enable additional plugins</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>
<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">
*To reduce server overhead, metrics are cached. Changes may not be reflected until cache expiration.
</div>
<div class="palette">
Generated metrics use transparency and colors which matches both light and dark modes
<div>
<label>
<input type="radio" v-model="palette" value="light">
☀️ Light mode
</label>
<label>
<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="generated.content">
<img :src="generated.content" alt="metrics">
</template>
<template v-else>
<img :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>
</template>
</div>
<div class="step">
<h2>4. Embed these metrics on your GitHub profile</h2>
<template v-if="user">
Add the markdown below in your <i>README.md</i> at <a :href="repo">{{ user }}/{{ user }}</a>
<div class="code">
![<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> !
</div>
</main>
<script src="/axios.min.js"></script>
<script src="/ejs.min.js"></script>
<script src="/vue.min.js"></script>
<script> <script>
window.onload = function () { ;(async function() {
//User updater new Vue({
let timeout = null el:"main",
document.querySelector("input[name=user]").onkeyup = function (event) { async mounted() {
//Retrieve user value await this.load()
clearTimeout(timeout) },
const user = event.target.value data:{
//Display placeholder user:"",
document.querySelector("#metrics .placeholder").style.opacity = 1 palette:"light",
document.querySelector("#metrics .generated").style.opacity = 0 plugins:{
document.querySelector("aside").style.opacity = 0 list:(await axios.get("/plugins.list")).data,
//Update github user enabled:{languages:true, followup:true},
document.querySelector(".code .url").innerText = `${window.location.href}${user}` descriptions:{
document.querySelector("#user-repo").href = `https://github.com/${user}/${user}` pagespeed:"Website performances",
document.querySelectorAll(".user").forEach(node => node.innerText = user) languages:"Most used languages",
//Update metrics followup:"Owned repositories issues and pull requests",
if (event.key === "Enter") traffic:"Pages views",
metrics(user) lines:"Lines of code changed",
else habits:"Coding habits",
timeout = setTimeout(() => metrics(user), 1800) selfskip:"Skip metrics commits",
} },
//Metrics updater },
let current = null templates:{
function metrics(user) { list:(await axios.get("/templates.list")).data,
if (!user.trim().length) loaded:{},
return selected:(await axios.get("/templates.list")).data[0],
if (current === user) { placeholder:"",
document.querySelector("#metrics .placeholder").style.opacity = 0 descriptions:{
document.querySelector("#metrics .generated").style.opacity = 1 classic:"Classic template",
document.querySelector("aside").style.opacity = 1 },
} },
else { generated:{
current = user pending:false,
document.querySelector("#metrics .generated").src = `${window.location.href}${user}` content:"",
document.querySelector("#metrics .generated").onload = function () { },
document.querySelector("#metrics .placeholder").style.opacity = 0 },
document.querySelector("#metrics .generated").style.opacity = 1 computed:{
document.querySelector("aside").style.opacity = 1 repo() {
return `https://github.com/${this.user}/${this.user}`
},
url() {
const plugins = Object.entries(this.plugins.enabled)
.filter(([key, value]) => /^languages|followup$/.test(key) ? !value : value)
.map(([key, value]) => `${key}=${+value}`)
.join("&")
return `${window.location.href}${this.user}${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
this.generated.content = this.serialize((await axios.get(this.url)).data)
},
serialize(svg) {
return `data:image/svg+xml;base64,${btoa(svg)}`
},
},
})
})()
</script> </script>
<style> <style>
body { body {
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; 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; background-color: #FFFFFF;
color: #1B1F23; color: #1B1F23;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center;
align-items: center; align-items: center;
min-height: 100vh; padding-bottom: 2rem;
width: 100vw; width: 100%;
padding: 0; transition: background-color .3s;
margin: 0;
overflow-x: hidden;
} }
h1 { h1 {
margin: 4rem 0 0; font-size: 1.6rem;
margin: 1rem 0;
}
h2 {
margin: 1.5rem 0 1rem;
font-size: 1.3rem;
} }
a, a:hover, a:visited { a, a:hover, a:visited {
color: #0366D6; color: #0366D6;
@@ -108,39 +187,47 @@
transition: color .4s; transition: color .4s;
cursor: pointer; cursor: pointer;
} }
input { input, button, select {
border-radius: .5rem; border-radius: .5rem;
padding: .5rem 1rem; padding: .25rem .5rem;
outline: none; outline: none;
border: 1px solid #E1E4E8; border: 1px solid #E1E4E8;
background-color: #FAFBFC; background-color: #FAFBFC;
color: #1B1F23; color: #1B1F23;
font-size: 1.2rem;
text-align: center; text-align: center;
margin: 0 0 1.5rem; cursor: pointer;
} }
input:focus { input:focus {
outline: none; outline: none;
} }
#metrics { input[type=text], select, button {
position: relative; min-width: 50%;
max-width: 100%; font-size: 1.1rem;
width: 480px;
height: 485px;
} }
#metrics img { option {
position: absolute;
top: 0;
left: 0;
width: 100%;
transition: opacity .4s;
}
aside {
opacity: 0;
padding: .25rem;
transition: opacity .4s;
text-align: center; text-align: center;
margin: 1rem 0 2rem; }
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 {
margin-top: 1rem;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.plugins label {
margin: 0 1rem;
} }
.code { .code {
font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace; font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;
@@ -152,6 +239,29 @@
.code .md-alt { .code .md-alt {
color: #6F42C1; color: #6F42C1;
} }
.cache-notice {
margin-top: .5rem;
font-size: .9rem;
opacity: .8;
}
.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> </style>
</body> </body>

View File

@@ -11,6 +11,8 @@
try { try {
//Init //Init
console.debug(`metrics/compute/${login} > start`)
console.debug(JSON.stringify(q))
const template = q.template || conf.settings.templates.default const template = q.template || conf.settings.templates.default
const pending = [] const pending = []
const s = (value, end = "") => value > 1 ? {y:"ies", "":"s"}[end] : end const s = (value, end = "") => value > 1 ? {y:"ies", "":"s"}[end] : end

View File

@@ -0,0 +1,41 @@
//Setup
export default function ({login, data, computed, pending, q}, {enabled = true} = {}) {
//Check if plugin is enabled and requirements are met
if (!enabled)
return computed.plugins.followup = null
if (("followup" in q)&&(!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 => {
try {
//Define getters
const followup = {
issues:{
get count() { return this.open + this.closed },
get open() { return computed.repositories.issues_open },
get closed() { return computed.repositories.issues_closed },
},
pr:{
get count() { return this.open + this.merged },
get open() { return computed.repositories.pr_open },
get merged() { return computed.repositories.pr_merged }
}
}
//Save results
computed.plugins.followup = followup
console.debug(`metrics/compute/${login}/plugins > followup > success`)
console.debug(JSON.stringify(computed.plugins.followup))
solve()
}
catch (error) {
//Generic error
computed.plugins.followup = {error:`An error occured`}
console.debug(`metrics/compute/${login}/plugins > followup > error`)
console.debug(error)
solve()
}
}))
}

View File

@@ -1,5 +1,7 @@
//Imports //Imports
import followup from "./followup/index.mjs"
import habits from "./habits/index.mjs" import habits from "./habits/index.mjs"
import languages from "./languages/index.mjs"
import lines from "./lines/index.mjs" import lines from "./lines/index.mjs"
import pagespeed from "./pagespeed/index.mjs" import pagespeed from "./pagespeed/index.mjs"
import selfskip from "./selfskip/index.mjs" import selfskip from "./selfskip/index.mjs"
@@ -7,7 +9,9 @@
//Exports //Exports
export default { export default {
followup,
habits, habits,
languages,
lines, lines,
pagespeed, pagespeed,
selfskip, selfskip,

View File

@@ -0,0 +1,42 @@
//Setup
export default function ({login, data, computed, pending, q}, {enabled = true} = {}) {
//Check if plugin is enabled and requirements are met
if (!enabled)
return computed.plugins.languages = null
if (("languages" in q)&&(!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 => {
try {
//Iterate through user's repositories and retrieve languages data
const languages = {colors:{}, total:0, stats:{}}
for (const repository of data.user.repositories.nodes) {
for (const {size, node:{color, name}} of Object.values(repository.languages.edges)) {
languages.stats[name] = (languages.stats[name] || 0) + size
languages.colors[name] = color || "#ededed"
languages.total += size
}
}
//Compute languages stats
Object.keys(languages.stats).map(name => languages.stats[name] /= languages.total)
languages.favorites = Object.entries(languages.stats).sort(([an, a], [bn, b]) => b - a).slice(0, 8).map(([name, value]) => ({name, value, color:languages.colors[name], x:0}))
for (let i = 1; i < languages.favorites.length; i++)
languages.favorites[i].x = languages.favorites[i-1].x + languages.favorites[i-1].value
//Save results
computed.plugins.languages = languages
console.debug(`metrics/compute/${login}/plugins > languages > success`)
console.debug(JSON.stringify(computed.plugins.languages))
solve()
}
catch (error) {
//Generic error
computed.plugins.languages = {error:`An error occured`}
console.debug(`metrics/compute/${login}/plugins > languages > error`)
console.debug(error)
solve()
}
}))
}

View File

@@ -20,6 +20,7 @@
} }
//Save results //Save results
computed.plugins.selfskip = {commits} computed.plugins.selfskip = {commits}
computed.commits -= commits
console.debug(`metrics/compute/${login}/plugins > selfskip > success`) console.debug(`metrics/compute/${login}/plugins > selfskip > success`)
console.debug(JSON.stringify(computed.plugins.selfskip)) console.debug(JSON.stringify(computed.plugins.selfskip))
solve() solve()

View File

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

View File

@@ -11,7 +11,8 @@
const conf = { const conf = {
templates:{}, templates:{},
settings:{}, settings:{},
statics:path.resolve("src/html") statics:path.resolve("src/html"),
node_modules:path.resolve("node_modules"),
} }
//Load settings //Load settings
@@ -37,19 +38,20 @@
const files = [ const files = [
`${templates}/${name}/query.graphql`, `${templates}/${name}/query.graphql`,
`${templates}/${name}/image.svg`, `${templates}/${name}/image.svg`,
`${templates}/${name}/placeholder.svg`,
`${templates}/${name}/style.css`, `${templates}/${name}/style.css`,
] ]
const [query, image, style] = await Promise.all(files.map(async file => `${await fs.promises.readFile(path.resolve(file))}`)) const [query, image, placeholder, style] = await Promise.all(files.map(async file => `${await fs.promises.readFile(path.resolve(file))}`))
conf.templates[name] = {query, image, style} conf.templates[name] = {query, image, placeholder, style}
console.debug(`metrics/setup > load template [${name}] > success`) console.debug(`metrics/setup > load template [${name}] > success`)
//Debug //Debug
if (conf.settings.debug) { if (conf.settings.debug) {
Object.defineProperty(conf.templates, name, { Object.defineProperty(conf.templates, name, {
get() { get() {
console.debug(`metrics/setup > reload template [${name}]`) console.debug(`metrics/setup > reload template [${name}]`)
const [query, image, style] = files.map(file => `${fs.readFileSync(path.resolve(file))}`) const [query, image, placeholder, style] = files.map(file => `${fs.readFileSync(path.resolve(file))}`)
console.debug(`metrics/setup > reload template [${name}] > success`) console.debug(`metrics/setup > reload template [${name}] > success`)
return {query, image, style} return {query, image, placeholder, style}
} }
}) })
} }

View File

@@ -54,7 +54,7 @@
</h2> </h2>
<div class="field"> <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"></path></svg> <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"></path></svg>
<%= computed.commits - (computed.plugins.selfskip ? computed.plugins.selfskip.commits||0 : 0) %> Commit<%= s(computed.commits - (computed.plugins.selfskip ? computed.plugins.selfskip.commits||0 : 0)) %> <%= computed.commits %> Commit<%= s(computed.commits) %>
</div> </div>
<div class="field"> <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"></path></svg> <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"></path></svg>
@@ -165,74 +165,105 @@
</div> </div>
</section> </section>
<div class="row"> <% if (computed.plugins.followup) { %>
<div class="row">
<section class="column"> <section class="column">
<h3>Issues</h3> <h3>Issues</h3>
<svg class="bar" xmlns="http://www.w3.org/2000/svg" width="220" height="8"> <% if (computed.plugins.followup.error) { %>
<mask id="issues-bar"> <section>
<rect x="0" y="0" width="220" height="8" fill="white" rx="5"/> <div class="field error">
</mask> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.47.22A.75.75 0 015 0h6a.75.75 0 01.53.22l4.25 4.25c.141.14.22.331.22.53v6a.75.75 0 01-.22.53l-4.25 4.25A.75.75 0 0111 16H5a.75.75 0 01-.53-.22L.22 11.53A.75.75 0 010 11V5a.75.75 0 01.22-.53L4.47.22zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5H5.31zM8 4a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 018 4zm0 8a1 1 0 100-2 1 1 0 000 2z"></path></svg>
<rect mask="url(#issues-bar)" x="0" y="0" width="<%= computed.repositories.issues_count ? 0 : 220 %>" height="8" fill="#d1d5da"/> <%= computed.plugins.followup.error %>
<rect mask="url(#issues-bar)" x="0" y="0" width="<%= (computed.repositories.issues_closed/computed.repositories.issues_count)*220 || 0 %>" height="8" fill="#d73a49"/> </div>
<rect mask="url(#issues-bar)" x="<%= (computed.repositories.issues_closed/computed.repositories.issues_count)*220 || 0 %>" y="0" width="<%= (1-computed.repositories.issues_closed/computed.repositories.issues_count)*220 || 0 %>" height="8" fill="#28a745"/> </section>
</svg> <% } else { %>
<div class="field horizontal fill-width"> <svg class="bar" xmlns="http://www.w3.org/2000/svg" width="220" height="8">
<div class="field center"> <mask id="issues-bar">
<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"></path></svg> <rect x="0" y="0" width="220" height="8" fill="white" rx="5"/>
<span class="no-wrap"><%= computed.repositories.issues_closed %> Closed</span> </mask>
</div> <rect mask="url(#issues-bar)" x="0" y="0" width="<%= computed.plugins.followup.issues.count ? 0 : 220 %>" height="8" fill="#d1d5da"/>
<div class="field center"> <rect mask="url(#issues-bar)" x="0" y="0" width="<%= (computed.plugins.followup.issues.closed/computed.plugins.followup.issues.count)*220 || 0 %>" height="8" fill="#d73a49"/>
<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"></path></svg> <rect mask="url(#issues-bar)" x="<%= (computed.plugins.followup.issues.closed/computed.plugins.followup.issues.count)*220 || 0 %>" y="0" width="<%= (1-computed.plugins.followup.issues.closed/computed.plugins.followup.issues.count)*220 || 0 %>" height="8" fill="#28a745"/>
<span class="no-wrap"><%= computed.repositories.issues_open %> Open</span> </svg>
</div> <div class="field horizontal fill-width">
</div> <div class="field center">
</section> <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"></path></svg>
<span class="no-wrap"><%= computed.plugins.followup.issues.closed %> Closed</span>
</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"></path></svg>
<span class="no-wrap"><%= computed.plugins.followup.issues.open %> Open</span>
</div>
</div>
<% } %>
</section>
<section class="column"> <section class="column">
<h3>Pull requests</h3> <h3>Pull requests</h3>
<svg class="bar" xmlns="http://www.w3.org/2000/svg" width="220" height="8"> <% if (computed.plugins.followup.error) { %>
<mask id="pr-bar"> <section>
<rect x="0" y="0" width="220" height="8" fill="white" rx="5"/> <div class="field error">
</mask> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.47.22A.75.75 0 015 0h6a.75.75 0 01.53.22l4.25 4.25c.141.14.22.331.22.53v6a.75.75 0 01-.22.53l-4.25 4.25A.75.75 0 0111 16H5a.75.75 0 01-.53-.22L.22 11.53A.75.75 0 010 11V5a.75.75 0 01.22-.53L4.47.22zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5H5.31zM8 4a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 018 4zm0 8a1 1 0 100-2 1 1 0 000 2z"></path></svg>
<rect mask="url(#pr-bar)" x="0" y="0" width="<%= computed.repositories.pr_count ? 0 : 220 %>" height="8" fill="#d1d5da"/> <%= computed.plugins.followup.error %>
<rect mask="url(#pr-bar)" x="0" y="0" width="<%= (computed.repositories.pr_merged/computed.repositories.pr_count)*220 || 0 %>" height="8" fill="#6f42c1"/> </div>
<rect mask="url(#pr-bar)" x="<%= (computed.repositories.pr_merged/computed.repositories.pr_count)*220 || 0 %>" y="0" width="<%= (1-computed.repositories.pr_merged/computed.repositories.pr_count)*220 || 0 %>" height="8" fill="#28a745"/> </section>
</svg> <% } else { %>
<div class="field horizontal fill-width"> <svg class="bar" xmlns="http://www.w3.org/2000/svg" width="220" height="8">
<div class="field center"> <mask id="pr-bar">
<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"></path></svg> <rect x="0" y="0" width="220" height="8" fill="white" rx="5"/>
<span class="no-wrap"><%= computed.repositories.pr_merged %> Merged</span> </mask>
</div> <rect mask="url(#pr-bar)" x="0" y="0" width="<%= computed.plugins.followup.pr.count ? 0 : 220 %>" height="8" fill="#d1d5da"/>
<div class="field center"> <rect mask="url(#pr-bar)" x="0" y="0" width="<%= (computed.plugins.followup.pr.merged/computed.plugins.followup.pr.count)*220 || 0 %>" height="8" fill="#6f42c1"/>
<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"></path></svg> <rect mask="url(#pr-bar)" x="<%= (computed.plugins.followup.pr.merged/computed.plugins.followup.pr.count)*220 || 0 %>" y="0" width="<%= (1-computed.plugins.followup.pr.merged/computed.plugins.followup.pr.count)*220 || 0 %>" height="8" fill="#28a745"/>
<span class="no-wrap"><%= computed.repositories.pr_open %> Open</span> </svg>
</div> <div class="field horizontal fill-width">
</div> <div class="field center">
</section> <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"></path></svg>
<span class="no-wrap"><%= computed.plugins.followup.pr.merged %> Merged</span>
</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"></path></svg>
<span class="no-wrap"><%= computed.plugins.followup.pr.open %> Open</span>
</div>
</div>
<% } %>
</section>
</div>
<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="<%= computed.languages.favorites.length ? 0 : 460 %>" height="8" fill="#d1d5da"/>
<% for (const {name, value, color, x} of computed.languages.favorites) { %>
<rect mask="url(#languages-bar)" x="<%= x*460 %>" y="0" width="<%= value*460 %>" height="8" fill="<%= color %>"/>
<% } %>
</svg>
<div class="field center horizontal-wrap fill-width">
<% for (const {name, value, color} of computed.languages.favorites) { %>
<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="<%= color %>" fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8z"></path></svg>
<%= name %>
</div>
<% } %>
</div> </div>
</section> <% } %>
<% if (computed.plugins.languages) { %>
<section class="column">
<h3>Most used languages</h3>
<% if (computed.plugins.languages.error) { %>
<section>
<div class="field error">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M4.47.22A.75.75 0 015 0h6a.75.75 0 01.53.22l4.25 4.25c.141.14.22.331.22.53v6a.75.75 0 01-.22.53l-4.25 4.25A.75.75 0 0111 16H5a.75.75 0 01-.53-.22L.22 11.53A.75.75 0 010 11V5a.75.75 0 01.22-.53L4.47.22zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5H5.31zM8 4a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 018 4zm0 8a1 1 0 100-2 1 1 0 000 2z"></path></svg>
<%= computed.plugins.languages.error %>
</div>
</section>
<% } else { %>
<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="<%= computed.plugins.languages.favorites.length ? 0 : 460 %>" height="8" fill="#d1d5da"/>
<% for (const {name, value, color, x} of computed.plugins.languages.favorites) { %>
<rect mask="url(#languages-bar)" x="<%= x*460 %>" y="0" width="<%= value*460 %>" height="8" fill="<%= color %>"/>
<% } %>
</svg>
<div class="field center horizontal-wrap fill-width">
<% for (const {name, value, color} of computed.plugins.languages.favorites) { %>
<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="<%= color %>" fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8z"></path></svg>
<%= name %>
</div>
<% } %>
</div>
<% } %>
</section>
<% } %>
<% if (computed.plugins.pagespeed) { %> <% if (computed.plugins.pagespeed) { %>
<div class="row"> <div class="row">

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -1,130 +1,38 @@
<svg xmlns="http://www.w3.org/2000/svg" width="480" height="485"> <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>
/* SVG global context */ <%= style %>
svg { /* Avatar */
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji; .avatar {
font-size: 14px; background-color: rgba(119,119,119,.62);
color: #777777; border-radius: 50%;
} margin: 0 6px;
height: 20px;
/* Headers */ width: 20px;
h1, h2, h3 { }
margin: 8px 0 2px; /* Placeholder */
padding: 0; .placeholder {
color: #0366d6; background-color: rgba(119,119,119,.62);
font-weight: normal; margin: 2px 4px 0px;
} height: 12px;
h1 svg, h2 svg, h3 svg { width: 24px;
fill: currentColor; border-radius: 6px;
} }
h1 { .placeholder.large {
font-size: 20px; width: 48px;
font-weight: bold; }
} .placeholder.xlarge {
h2 { width: 64px;
font-size: 16px; }
} .placeholder.xxlarge {
h3 { margin: 7.2px 0;
font-size: 14px; width: 96px;
} }
.placeholder.inline {
/* Fields */ display: inline-block;
section > .field { }
margin-left: 5px; h2 .placeholder {
margin-right: 5px; background-color: rgba(3,102,214,.62);
} }
.field {
display: flex;
align-items: center;
margin-bottom: 2px;
}
.field svg {
margin: 0 8px;
fill: #959da5;
}
/* Displays */
.row {
display: flex;
}
.row section {
flex: 1 1 0;
}
.column {
display: flex;
flex-direction: column;
align-items: center;
}
.center {
justify-content: center;
}
.horizontal {
justify-content: space-around;
}
.horizontal-wrap {
flex-wrap: wrap;
}
.horizontal .field {
flex: 1 1 0;
}
.no-wrap {
white-space: nowrap;
}
.fill-width {
width: 100%;
}
/* User avatar */
.avatar {
background-color: rgba(119,119,119,.62);
border-radius: 50%;
margin: 0 6px;
height: 20px;
width: 20px;
}
/* Commit calendar */
.calendar.field {
margin: 4px 0;
margin-left: 7px;
}
.calendar .day {
outline: 1px solid rgba(27,31,35,.04);
outline-offset: -1px;
}
/* Progress bars */
svg.bar {
margin: 4px 0;
}
/* Language */
.field.language {
margin: 0 8px;
flex-grow: 0;
}
/* 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;
}
h2 .placeholder {
background-color: rgba(3,102,214,.62);
}
</style> </style>
<foreignObject x="0" y="0" width="100%" height="100%"> <foreignObject x="0" y="0" width="100%" height="100%">
@@ -257,6 +165,12 @@
<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> <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 class="placeholder"></div> Gists
</div> </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>
<section> <section>
@@ -272,93 +186,167 @@
<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> <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 class="placeholder"></div> Watchers
</div> </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> </section>
</div> </div>
</section> </section>
<div class="row"> <% if (plugins.followup) { %>
<div class="row">
<section class="column"> <section class="column">
<h3>Issues</h3> <h3>Issues</h3>
<svg class="bar" xmlns="http://www.w3.org/2000/svg" width="220" height="8"> <svg class="bar" xmlns="http://www.w3.org/2000/svg" width="220" height="8">
<mask id="issues-bar"> <mask id="issues-bar">
<rect x="0" y="0" width="220" height="8" fill="white" rx="5"/> <rect x="0" y="0" width="220" height="8" fill="white" rx="5"/>
</mask> </mask>
<rect mask="url(#issues-bar)" x="0" y="0" width="220" height="8" fill="#d1d5da"/> <rect mask="url(#issues-bar)" x="0" y="0" width="220" height="8" fill="#d1d5da"/>
</svg> </svg>
<div class="field horizontal fill-width"> <div class="field horizontal fill-width">
<div class="field center"> <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> <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 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> </div>
<div class="field center"> </section>
<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"> <section class="column">
<h3>Pull requests</h3> <h3>Pull requests</h3>
<svg class="bar" xmlns="http://www.w3.org/2000/svg" width="220" height="8"> <svg class="bar" xmlns="http://www.w3.org/2000/svg" width="220" height="8">
<mask id="pr-bar"> <mask id="pr-bar">
<rect x="0" y="0" width="220" height="8" fill="white" rx="5"/> <rect x="0" y="0" width="220" height="8" fill="white" rx="5"/>
</mask> </mask>
<rect mask="url(#pr-bar)" x="0" y="0" width="220" height="8" fill="#d1d5da"/> <rect mask="url(#pr-bar)" x="0" y="0" width="220" height="8" fill="#d1d5da"/>
</svg> </svg>
<div class="field horizontal fill-width"> <div class="field horizontal fill-width">
<div class="field center"> <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> <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 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> </div>
<div class="field center"> </section>
<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>
<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> </div>
</section> <% } %>
<% 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> </div>
</foreignObject> </foreignObject>

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -2,9 +2,7 @@
export default async function ({login, q}, {data, rest, graphql, plugins}, {s, pending, imports}) { export default async function ({login, q}, {data, rest, graphql, plugins}, {s, pending, imports}) {
//Init //Init
const languages = {colors:{}, total:0, stats:{}} 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 licenses = {favorite:"", used:{}}
const computed = data.computed = {commits:0, languages, licenses, svg:{height:505, width:480}, 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) const avatar = imports.imgb64(data.user.avatarUrl)
//Plugins //Plugins
@@ -14,6 +12,8 @@
imports.plugins.traffic({login, repositories:data.user.repositories.nodes.map(({name}) => name), rest, computed, pending, q}, plugins.traffic) imports.plugins.traffic({login, repositories:data.user.repositories.nodes.map(({name}) => name), rest, computed, pending, q}, plugins.traffic)
imports.plugins.habits({login, rest, computed, pending, q}, plugins.habits) imports.plugins.habits({login, rest, computed, pending, q}, plugins.habits)
imports.plugins.selfskip({login, rest, computed, pending, q}, plugins.selfskip) imports.plugins.selfskip({login, rest, computed, pending, q}, plugins.selfskip)
imports.plugins.languages({login, data, computed, pending, q}, plugins.languages)
imports.plugins.followup({login, data, computed, pending, q}, plugins.followup)
//Iterate through user's repositories //Iterate through user's repositories
for (const repository of data.user.repositories.nodes) { for (const repository of data.user.repositories.nodes) {
@@ -22,23 +22,16 @@
computed.repositories[property] += repository[property].totalCount computed.repositories[property] += repository[property].totalCount
//Forks //Forks
computed.repositories.forks += repository.forkCount computed.repositories.forks += repository.forkCount
//Languages
for (const {size, node:{color, name}} of Object.values(repository.languages.edges)) {
languages.stats[name] = (languages.stats[name] || 0) + size
languages.colors[name] = color || "#ededed"
languages.total += size
}
//License //License
if (repository.licenseInfo) if (repository.licenseInfo)
licenses.used[repository.licenseInfo.spdxId] = (licenses.used[repository.licenseInfo.spdxId] || 0) + 1 computed.licenses.used[repository.licenseInfo.spdxId] = (computed.licenses.used[repository.licenseInfo.spdxId] || 0) + 1
} }
//Compute count for issues and pull requests //Compute licenses stats
for (const property of ["issues", "pr"]) computed.licenses.favorite = Object.entries(computed.licenses.used).sort(([an, a], [bn, b]) => b - a).slice(0, 1).map(([name, value]) => name) || ""
computed.repositories[`${property}_count`] = computed.repositories[`${property}_open`] + computed.repositories[`${property}_${property === "pr" ? "merged" : "closed"}`]
//Compute total commits and sponsorships //Compute total commits and sponsorships
computed.commits = data.user.contributionsCollection.totalCommitContributions + data.user.contributionsCollection.restrictedContributionsCount computed.commits += data.user.contributionsCollection.totalCommitContributions + data.user.contributionsCollection.restrictedContributionsCount
computed.sponsorships = data.user.sponsorshipsAsSponsor.totalCount + data.user.sponsorshipsAsMaintainer.totalCount computed.sponsorships = data.user.sponsorshipsAsSponsor.totalCount + data.user.sponsorshipsAsMaintainer.totalCount
//Compute registration date //Compute registration date
@@ -47,15 +40,6 @@
const months = Math.ceil((diff-years)*12) const months = Math.ceil((diff-years)*12)
computed.registration = years ? `${years} year${s(years)} ago` : `${months} month${s(months)} ago` computed.registration = years ? `${years} year${s(years)} ago` : `${months} month${s(months)} ago`
//Compute languages stats
Object.keys(languages.stats).map(name => languages.stats[name] /= languages.total)
languages.favorites = Object.entries(languages.stats).sort(([an, a], [bn, b]) => b - a).slice(0, 8).map(([name, value]) => ({name, value, color:languages.colors[name], x:0}))
for (let i = 1; i < languages.favorites.length; i++)
languages.favorites[i].x = languages.favorites[i-1].x + languages.favorites[i-1].value
//Compute licenses stats
licenses.favorite = Object.entries(licenses.used).sort(([an, a], [bn, b]) => b - a).slice(0, 1).map(([name, value]) => name) || ""
//Compute calendar //Compute calendar
computed.calendar = data.user.calendar.contributionCalendar.weeks.flatMap(({contributionDays}) => contributionDays).slice(0, 14).reverse() computed.calendar = data.user.calendar.contributionCalendar.weeks.flatMap(({contributionDays}) => contributionDays).slice(0, 14).reverse()