Merge branch 'master' into patch-rank

This commit is contained in:
François Rozet
2022-09-22 08:56:53 +02:00
committed by GitHub
62 changed files with 2175 additions and 890 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: [anuraghazra] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
+8 -6
View File
@@ -5,11 +5,12 @@ title: ""
labels: ""
assignees: ""
---
<!--PLEASE FIRST READ THE FAQ (#1770) AND COMMON ERROR CODES (#1772)!!!-->
**Describe the bug**
A clear and concise description of what the bug is.
**Expected behavior**
**Expected behaviour**
A clear and concise description of what you expected to happen.
**Screenshots / Live demo link (paste the github-readme-stats link as markdown image)**
@@ -18,9 +19,11 @@ If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
<!--
<!-- FAQ
Below are some questions that are found in the FAQ. The full FAQ can be found in #1770.
PLEASE READ THE FAQs!!!
Q: My card displays an error
Ans: First, check the common error codes (i.e. https://github.com/anuraghazra/github-readme-stats/issues/1772) and existing issues before creating a new one.
Q: How to hide jupyter Notebook?
Ans: &hide=jupyter%20notebook
@@ -31,11 +34,10 @@ Ans:
- YT tutorial by codeSTACKr: https://www.youtube.com/watch?v=n6d4KHSKqGk&feature=youtu.be&t=107
Q: Language Card is incorrect
Ans: Please read all the issues / comments before opening any issues regarding language card stats:
Ans: Please read all the issues/comments before opening any issues regarding language card stats:
- https://github.com/anuraghazra/github-readme-stats/issues/136#issuecomment-665164174
- https://github.com/anuraghazra/github-readme-stats/issues/136#issuecomment-665172181
Q: How to count private stats?
Ans: We can only count private commits & we cannot access any other private info of any users, so it's not possible. only way is to deploy on your own instance & use your own PAT (Personal Access Token)
Ans: We can only count private commits & we cannot access any other private info of any users, so it's impossible. The only way is to deploy on your own instance & use your own PAT (Personal Access Token)
-->
@@ -0,0 +1,30 @@
name: Close empty issues and templates
on:
issues:
types:
- reopened
- opened
- edited
jobs:
closeEmptyIssuesAndTemplates:
name: Close empty issues
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3 # NOTE: Retrieve issue templates.
- name: Run empty issues closer action
uses: rickstaa/empty-issues-closer-action@v1
env:
github_token: ${{ secrets.GITHUB_TOKEN }}
with:
close_comment:
Closing this issue because it appears to be empty. Please update the
issue for it to be reopened.
open_comment:
Reopening this issue because the author provided more information.
check_templates: true
template_close_comment:
Closing this issue since the issue template was not filled in.
Please provide us with more information to have this issue reopened.
template_open_comment:
Reopening this issue because the author provided more information.
+3 -1
View File
@@ -5,8 +5,10 @@ on:
types: [opened, synchronize, reopened]
branches:
- master
- theme-preview-script
- theme_preview_script
paths:
- "themes/index.js"
- "scripts/preview-theme.js"
jobs:
build:
+4 -11
View File
@@ -20,21 +20,14 @@ jobs:
with:
node-version: "12.x"
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key:
${{ runner.os }}-npm-cache-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-cache-
- name: Install & Test
run: |
npm install
npm run test
- name: Run Prettier
run: |
npm run format:check
- name: Code Coverage
uses: codecov/codecov-action@v1
@@ -0,0 +1,22 @@
name: Update top issues dashboard
on:
schedule:
- cron: "0 0 */7 * *"
jobs:
showAndLabelTopIssues:
name: Update top issues Dashboard.
runs-on: ubuntu-latest
steps:
- name: Run top issues action
uses: rickstaa/top-issues-action@v1
env:
github_token: ${{ secrets.GITHUB_TOKEN }}
with:
label: false
dashboard: true
dashboard_show_total_reactions: true
top_issues: true
top_bugs: true
top_features: true
top_pull_requests: true
+3
View File
@@ -8,3 +8,6 @@ package-lock.json
coverage
vercel_token
# IDE
.vscode
*.code-workspace
+49 -40
View File
@@ -2,41 +2,50 @@
We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
- Reporting a issue
- Discussing the current state of the code
- Submitting a fix
- Proposing new features
- Becoming a maintainer
- Reporting an issue
- Discussing the current state of the code
- Submitting a fix
- Proposing new features
- Becoming a maintainer
## All Changes Happen Through Pull Requests
Pull requests are the best way to propose changes. We actively welcome your pull requests:
1. Fork the repo and create your branch from `master`.
1. If you've added code that should be tested, add some tests' example.
1. If you've changed APIs, update the documentation.
1. Issue that pull request!
1. Fork the repo and create your branch from `master`.
2. If you've added code that should be tested, add some tests' examples.
3. If you've changed APIs, update the documentation.
4. Issue that pull request!
## Under the hood of github-readme-stats
Interested in diving deeper into understanding how github-readme-stats works?
[Bohdan](https://github.com/Bogdan-Lyashenko) wrote a fantastic in-depth post about it, check it out:
**[Under the hood of github-readme-stats project](https://codecrumbs.io/library/github-readme-stats)**
## Local Development
To run & test github-readme-stats you need to follow few simple steps :-
_(make sure you already have a [vercel](https://vercel.com/) account)_
To run & test github-readme-stats, you need to follow a few simple steps:-
_(make sure you already have a [Vercel](https://vercel.com/) account)_
1. Install [Vercel CLI](https://vercel.com/download)
1. Fork the repository and clone the code to your local machine
1. Run `npm install` in the repository root
1. Run the command "vercel" in the root and follow the steps there
1. Create a `.env` file in the root of the directory
1. In the .env file add a new variable named "PAT_1" with your [github Personal access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token)
1. Run the command "vercel dev" to start a development server at https://localhost:3000
1. Install [Vercel CLI](https://vercel.com/download).
2. Fork the repository and clone the code to your local machine.
3. Run `npm install` in the repository root.
4. Run the command "vercel" in the root and follow the steps there.
5. Open `vercel.json` and set the maxDuration to 10.
6. Create a `.env` file in the root of the directory.
7. In the .env file add a new variable named "PAT_1" with your [GitHub Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token).
8. Run the command "vercel dev" to start a development server at <https://localhost:3000>.
## Themes Contribution
GitHub Readme Stats supports custom theming and you can also contribute new themes!
GitHub Readme Stats supports custom theming, and you can also contribute new themes!
All you need to do is edit [themes/index.js](./themes/index.js) file and add your theme at the end of the file.
All you need to do is edit the [themes/index.js](./themes/index.js) file and add your theme at the end of the file.
While creating the Pull request to add a new theme **don't forget to add a screenshot of how your theme looks**, you can also test how it looks using custom url parameters like `title_color`, `icon_color`, `bg_color`, `text_color`, `border_color`
While creating the Pull request to add a new theme **don't forget to add a screenshot of how your theme looks**, you can also test how it looks using custom URL parameters like `title_color`, `icon_color`, `bg_color`, `text_color`, `border_color`
> NOTE: If you are contributing your theme just because you are using it personally, then you can [customize the looks](./readme.md#customization) of your card with URL params instead.
@@ -50,41 +59,41 @@ We use GitHub issues to track public bugs. Report a bug by [opening a new issue]
## Frequently Asked Questions (FAQs)
**Q:** How to hide jupyter Notebook?
**Q:** How to hide Jupyter Notebook?
> **Ans:** &hide=jupyter%20notebook
**Q:** I could not figure out how to deploy on my own vercel instance
**Q:** I could not figure out how to deploy on my own Vercel instance
> **Ans:**
>
> - docs: https://github.com/anuraghazra/github-readme-stats/#deploy-on-your-own-vercel-instance
> - YT tutorial by codeSTACKr: https://www.youtube.com/watch?v=n6d4KHSKqGk&feature=youtu.be&t=107
> - docs: <https://github.com/anuraghazra/github-readme-stats/#deploy-on-your-own-vercel-instance>
> - YT tutorial by codeSTACKr: <https://www.youtube.com/watch?v=n6d4KHSKqGk&feature=youtu.be&t=107>
**Q:** Language Card is incorrect
> **Ans:** Please read all the related issues / comments before opening any issues regarding language card stats:
> **Ans:** Please read all the related issues/comments before opening any issues regarding language card stats:
>
> - https://github.com/anuraghazra/github-readme-stats/issues/136#issuecomment-665164174
> - <https://github.com/anuraghazra/github-readme-stats/issues/136#issuecomment-665164174>
>
> - https://github.com/anuraghazra/github-readme-stats/issues/136#issuecomment-665172181
> - <https://github.com/anuraghazra/github-readme-stats/issues/136#issuecomment-665172181>
**Q:** How to count private stats?
> **Ans:** We can only count private commits & we cannot access any other private info of any users, so it's not possible. only way is to deploy on your own instance & use your own PAT (Personal Access Token)
> **Ans:** We can only count public commits & we cannot access any other private info of any users, so it's not possible. The only way to count your personal private stats is to deploy on your own instance & use your own PAT (Personal Access Token)
### Bug Reports
**Great Bug Reports** tend to have:
- A quick summary and/or background
- Steps to reproduce
- Be specific!
- Share the snapshot, if possible.
- GitHub Readme Stats' live link
- What actually happens
- What you expected would happen
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
- A quick summary and/or background
- Steps to reproduce
- Be specific!
- Share the snapshot, if possible.
- GitHub Readme Stats' live link
- What actually happens
- What you expected would happen
- Notes (possibly including why you think this might be happening or stuff you tried that didn't work)
People _love_ thorough bug reports. I'm not even kidding.
@@ -92,9 +101,9 @@ People _love_ thorough bug reports. I'm not even kidding.
**Great Feature Requests** tend to have:
- A quick idea summary
- What & why you wanted to add the specific feature
- Additional Context like images, links to resources to implement the feature etc etc.
- A quick idea summary
- What & why do you want to add the specific feature
- Additional context like images, links to resources to implement the feature, etc.
## License
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 Anurag Hazra
Copyright (c) 2020 Anurag Hazra
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+9 -5
View File
@@ -17,6 +17,7 @@ module.exports = async (req, res) => {
hide,
hide_title,
hide_border,
card_width,
hide_rank,
show_icons,
count_private,
@@ -25,17 +26,17 @@ module.exports = async (req, res) => {
title_color,
icon_color,
text_color,
text_bold,
bg_color,
theme,
cache_seconds,
exclude_repo,
custom_title,
locale,
disable_animations,
border_radius,
border_color,
} = req.query;
let stats;
res.setHeader("Content-Type", "image/svg+xml");
if (blacklist.includes(username)) {
@@ -47,15 +48,16 @@ module.exports = async (req, res) => {
}
try {
stats = await fetchStats(
const stats = await fetchStats(
username,
parseBoolean(count_private),
parseBoolean(include_all_commits),
parseArray(exclude_repo),
);
const cacheSeconds = clampValue(
parseInt(cache_seconds || CONSTANTS.TWO_HOURS, 10),
CONSTANTS.TWO_HOURS,
parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
CONSTANTS.FOUR_HOURS,
CONSTANTS.ONE_DAY,
);
@@ -67,12 +69,14 @@ module.exports = async (req, res) => {
show_icons: parseBoolean(show_icons),
hide_title: parseBoolean(hide_title),
hide_border: parseBoolean(hide_border),
card_width: parseInt(card_width, 10),
hide_rank: parseBoolean(hide_rank),
include_all_commits: parseBoolean(include_all_commits),
line_height,
title_color,
icon_color,
text_color,
text_bold: parseBoolean(text_bold),
bg_color,
theme,
custom_title,
+8 -10
View File
@@ -27,8 +27,6 @@ module.exports = async (req, res) => {
border_color,
} = req.query;
let repoData;
res.setHeader("Content-Type", "image/svg+xml");
if (blacklist.includes(username)) {
@@ -40,20 +38,20 @@ module.exports = async (req, res) => {
}
try {
repoData = await fetchRepo(username, repo);
const repoData = await fetchRepo(username, repo);
let cacheSeconds = clampValue(
parseInt(cache_seconds || CONSTANTS.TWO_HOURS, 10),
CONSTANTS.TWO_HOURS,
parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
CONSTANTS.FOUR_HOURS,
CONSTANTS.ONE_DAY,
);
/*
if star count & fork count is over 1k then we are kFormating the text
and if both are zero we are not showing the stats
so we can just make the cache longer, since there is no need to frequent updates
*/
const stars = repoData.stargazers.totalCount;
if star count & fork count is over 1k then we are kFormating the text
and if both are zero we are not showing the stats
so we can just make the cache longer, since there is no need to frequent updates
*/
const stars = repoData.starCount;
const forks = repoData.forkCount;
const isBothOver1K = stars > 1000 && forks > 1000;
const isBothUnder1 = stars < 1 && forks < 1;
+3 -6
View File
@@ -31,8 +31,6 @@ module.exports = async (req, res) => {
border_radius,
border_color,
} = req.query;
let topLangs;
res.setHeader("Content-Type", "image/svg+xml");
if (blacklist.includes(username)) {
@@ -44,15 +42,14 @@ module.exports = async (req, res) => {
}
try {
topLangs = await fetchTopLanguages(
const topLangs = await fetchTopLanguages(
username,
parseArray(exclude_repo),
parseArray(hide),
);
const cacheSeconds = clampValue(
parseInt(cache_seconds || CONSTANTS.TWO_HOURS, 10),
CONSTANTS.TWO_HOURS,
parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
CONSTANTS.FOUR_HOURS,
CONSTANTS.ONE_DAY,
);
+6 -3
View File
@@ -3,9 +3,10 @@ const {
renderError,
parseBoolean,
clampValue,
parseArray,
CONSTANTS,
isLocaleAvailable,
} = require("../src/common/utils");
const { isLocaleAvailable } = require("../src/translations");
const { fetchWakatimeStats } = require("../src/fetchers/wakatime-fetcher");
const wakatimeCard = require("../src/cards/wakatime-card");
@@ -26,6 +27,7 @@ module.exports = async (req, res) => {
locale,
layout,
langs_count,
hide,
api_domain,
range,
border_radius,
@@ -42,8 +44,8 @@ module.exports = async (req, res) => {
const stats = await fetchWakatimeStats({ username, api_domain, range });
let cacheSeconds = clampValue(
parseInt(cache_seconds || CONSTANTS.TWO_HOURS, 10),
CONSTANTS.TWO_HOURS,
parseInt(cache_seconds || CONSTANTS.FOUR_HOURS, 10),
CONSTANTS.FOUR_HOURS,
CONSTANTS.ONE_DAY,
);
@@ -58,6 +60,7 @@ module.exports = async (req, res) => {
custom_title,
hide_title: parseBoolean(hide_title),
hide_border: parseBoolean(hide_border),
hide: parseArray(hide),
line_height,
title_color,
icon_color,
+2 -2
View File
@@ -290,7 +290,7 @@ _注意:热门语言并不表示我的技能水平或类似的水平,它是
你通常无法将图片靠边显示。为此,您可以使用以下方法:
```md
```html
<a href="https://github.com/anuraghazra/github-readme-stats">
<img align="center" src="https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats" />
</a>
@@ -325,7 +325,7 @@ _注意:热门语言并不表示我的技能水平或类似的水平,它是
1. 选择 `Import Git Repository`
![](https://files.catbox.moe/pqub9q.png)
1. 选择 root 并将所有内容保持不变,并且只需添加名为 PAT_1 的环境变量(如图所示),其中将包含一个个人访问令牌(PAT),你可以在[这里](https://github.com/settings/tokens/new)轻松创建(保留默认,并且只需要命名下,名字随便)
![](https://files.catbox.moe/caem5b.png)
![](https://files.catbox.moe/0ez4g7.png)
1. 点击 deploy,这就完成了,查看你的域名就可使用 API 了!
</details>
+14 -14
View File
@@ -71,7 +71,7 @@
# GitHub Statistiken-Karte
Kopiere folgendes in deine Readme um die Statistiken zu benutzen.
Passe den Wert des URL-Parameters `?username=` so an, dass dort dein GitHub Nutzername steht.
Passe den Wert des URL-Parameters `?username=` so an, dass dort dein GitHub-Nutzername steht.
```md
[![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats)
@@ -91,7 +91,7 @@ Um eine spezifische Statistik auszublenden, kann dem Query-Parameter `?hide=` ei
### Symbole anzeigen
Um Symbole anzuzeigen kann der URL-Paramter `show_icons=true` wie folgt verwendet werden:
Um Symbole anzuzeigen kann der URL-Parameter `show_icons=true` wie folgt verwendet werden:
```md
![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true)
@@ -139,11 +139,11 @@ Du kannst mehrere, mit Kommas separierte, Werte in der bg_color Option angeben,
&bg_color=WINKEL,FARBE1,FARBE2,FARBE3...FARBE10
```
> Hinweis bzgl. des Caches: Wenn die Anzahl der Forks und Stars geringer als 1 Tsd ist, haben die Repo-Cards eine Standard-Cachezeit von 30 Minuten (1800 Sekunden), ansonsten beträgt diese 2 Stunden (7200 Sekunden). Außerdem ist der Cache auf ein Minimum von 30 Minuten und ein Maximum von 24 Stunden begrenzt.
> Hinweis bzgl. des Caches: Wenn die Anzahl der Forks und Stars geringer als 1 Tsd. ist, haben die Repo-Cards eine Standard-Cachezeit von 30 Minuten (1800 Sekunden), ansonsten beträgt diese 2 Stunden (7200 Sekunden). Außerdem ist der Cache auf ein Minimum von 30 Minuten und ein Maximum von 24 Stunden begrenzt.
#### Exklusive Optionen der Statistiken-Karte:
- `hide` - Verbirgt die angegeben Elemente _(mit Komma abgegrenzte Werte)_
- `hide` - Verbirgt die [angegeben Elemente](#verbergen-individueller-statistiken) _(mit Komma abgegrenzte Werte)_
- `hide_title` - _(Boolean)_
- `hide_rank` - _(Boolean)_
- `show_icons` - _(Boolean)_
@@ -170,12 +170,12 @@ Du kannst mehrere, mit Kommas separierte, Werte in der bg_color Option angeben,
- `hide_title` - _(Boolean)_
- `line_height` - Legt die Zeilenhöhe des Texts fest _(Zahl)_
- `hide_progress` - Verbirgt die Fortschrittanzeige und Prozentzahl _(Boolean)_
- `hide_progress` - Verbirgt die Fortschrittsanzeige und Prozentzahl _(Boolean)_
- `custom_title` - Legt einen benutzerdefinierten Titel fest
- `layout` - Wechselt zwischen zwei verschiedenen Layouts: `default` & `compact`
- `langs_count` - Begrenzt die Anzahl der angezeigten Sprachen auf der Karte
- `api_domain` - Legt eine benutzerdefinierte API Domain fest, z.B. für [Hakatime](https://github.com/mujx/hakatime) oder [Wakapi](https://github.com/muety/wakapi)
- `range` Fragt eine eine Zeitspanne an, als die standardmäßig in WakaTime hinterlegte, z.B. `last_7_days`. Siehe [WakaTime API Dokumentation](https://wakatime.com/developers#stats).
- `range` Fragt eine andere Zeitspanne an, als jene, welche standardmäßig in Wakatime hinterlegt ist. Zum Beispiel `last_7_days`. Siehe [WakaTime API Dokumentation](https://wakatime.com/developers#stats).
---
@@ -188,7 +188,7 @@ Und Bääm! Du bist nicht mehr auf 6 angeheftete Repositories limitiert.
### Benutzung
Füge diesen Code in deine Readme-Datei ein und passe die Links an.
Passe den Wert des URL-Parameters `?username=` so an, dass dort dein GitHub Nutzername steht.
Passe den Wert des URL-Parameters `?username=` so an, dass dort dein GitHub-Nutzername steht.
Den Wert des URL-Parameters `?repo=` musst du so anpassen, dass dort der Namen deines Repositorys steht.
Endpunkt: `api/pin?username=anuraghazra&repo=github-readme-stats`
@@ -201,7 +201,7 @@ Endpunkt: `api/pin?username=anuraghazra&repo=github-readme-stats`
[![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats)](https://github.com/anuraghazra/github-readme-stats)
Benutze die [show_owner](#anpassungenpersonalisierung) Variable, um den Nutzernamen des Repo Eigentümers anzuzeigen.
Benutze die [show_owner](#anpassungenpersonalisierung) Variable, um den Nutzernamen des Repository-Eigentümers anzuzeigen.
[![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&show_owner=true)](https://github.com/anuraghazra/github-readme-stats)
@@ -214,7 +214,7 @@ _HINWEIS: Die Top Programmiersprachen treffen keine Aussage über persönliche F
### Benutzung
Füge diesen Code in deine Readme-Datei ein und passe die Links an.
Passe den Wert des URL-Parameters `?username=` so an, dass dort dein GitHub Nutzername steht.
Passe den Wert des URL-Parameters `?username=` so an, dass dort dein GitHub-Nutzername steht.
Endpunkt: `api/top-langs?username=anuraghazra`
@@ -232,7 +232,7 @@ Du kannst den `?hide=language1,language2` URL-Parameter benutzen, um einzelne Sp
### Kompaktes Sprachen-Karte Layout
Du kannst die `&layout=compact` Option nutzen, um das Karten Design zu ändern.
Du kannst die `&layout=compact` Option nutzen, um das Kartendesign zu ändern.
```md
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=compact)](https://github.com/anuraghazra/github-readme-stats)
@@ -248,7 +248,7 @@ Du kannst die `&layout=compact` Option nutzen, um das Karten Design zu ändern.
# Wakatime Wochen-Statistik
Ändere `?username=` in den eigenen [Wakatime](https://wakatime.com) Benutzernamen.
Ändere `?username=` in den eigenen [Wakatime](https://wakatime.com)-Benutzernamen.
```md
[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats)
@@ -312,7 +312,7 @@ Wähle Eines von den [Standard-Themes](#themes)
Üblicherweise ist es in `.md`-Dateien nicht möglich Bilder nebeneinander anzuzeigen. Um dies zu ermöglichen, kannst du folgendes tun:
```md
```html
<a href="https://github.com/anuraghazra/github-readme-stats">
<img align="center" src="https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats" />
</a>
@@ -325,7 +325,7 @@ Wähle Eines von den [Standard-Themes](#themes)
#### [Schritt für Schritt YouTube Tutorial by @codeSTACKr](https://youtu.be/n6d4KHSKqGk?t=107)
Da die GitHub API nur 5 Tsd Aufrufe pro Stunde zulässt, kann es passieren, dass meine `https://github-readme-stats.vercel.app/api` dieses Limit erreicht.
Da die GitHub API nur 5 Tsd. Aufrufe pro Stunde zulässt, kann es passieren, dass meine `https://github-readme-stats.vercel.app/api` dieses Limit erreicht.
Wenn du es auf deinem eigenen Vercel-Server hostest, brauchst du dich darum nicht zu kümmern. Klicke auf den Deploy-Knopf um loszulegen!
Hinweis: Seit [#58](https://github.com/anuraghazra/github-readme-stats/pull/58) sollte es möglich sein, mehr als 5 Tsd Aufrufe pro Stunde ohne Downtimes zu verkraften :D
@@ -349,7 +349,7 @@ Hinweis: Seit [#58](https://github.com/anuraghazra/github-readme-stats/pull/58)
![](https://files.catbox.moe/pqub9q.png)
1. Wähle root und füge eine Umgebungsvariable namens PAT_1 (siehe Abbildung) die als Wert deinen persönlichen Access Token (PAT) hat hinzu, den du einfach [hier](https://github.com/settings/tokens/new) erzeugen kannst (lasse alles wie es ist, vergebe einen beliebigen Namen)
![](https://files.catbox.moe/0ez4g7.png)
1. Klicke deploy, und das wars. Besuche deine Domains um die API zu benutzen!
1. Klicke auf `Deploy`, und das wars. Besuche deine Domains um die API zu benutzen!
</details>
## :sparkling_heart: Unterstütze das Projekt
+3 -3
View File
@@ -64,7 +64,7 @@
- [Tarjeta de estadísticas de GitHub](#tarjeta-de-estadísticas-de-github)
- [Pins adicionales de GitHub](#pines-adicionales-de-github)
- [Top Languages Card](#tarjeta-de-lenguajes-principales)
- [Tarjeta de Lenguajes Principales](#tarjeta-de-lenguajes-principales)
- [Wakatime Week Stats](#estadísticas-de-la-semana-de-wakatime)
- [Temas](#temas)
- [Personalización](#personalización)
@@ -157,7 +157,7 @@ Puedes pasar mútliples valores separados por coma en la opción `bg_color` para
#### Opciones exclusivas de la Tarjeta de Estadísticas:
- `hide` - Oculta de las estadísticas los elementos especificados _ (valores separados por comas) _
- `hide` - Oculta de las estadísticas [los elementos especificados](#ocultar-estadísticas-individualmente) _(valores separados por comas)_
- `hide_title` - _(booleano)_
- `hide_rank` - _(booleano)_
- `show_icons` - _(booleano)_
@@ -351,7 +351,7 @@ Escoja cualquiera de los [temas por defecto](#themes)
Por lo general, no podrás acomodar las imágenes una al lado de la otra. Para hacerlo, puede usar este enfoque:
```md
```html
<a href="https://github.com/anuraghazra/github-readme-stats">
<img align="center" src="https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats" />
</a>
+2 -2
View File
@@ -153,7 +153,7 @@ Vous pouvez fournir plusieurs valeurs (suivie d'une virgule) dans l'option bg_co
#### Stats Card Exclusive Options:
- `hide` - Masquer les éléments spécifiés dans les statistiques _(Comma seperated values)_
- `hide` - Masquer [les éléments spécifiés](#cacher-les-statistiques-individuelles) dans les statistiques _(Comma seperated values)_
- `hide_title` - Masquer le titre _(boolean)_
- `hide_rank` - Masquer le rang _(boolean)_
- `show_icons` - Afficher les icônes _(boolean)_
@@ -290,7 +290,7 @@ Choisissez parmi l'un des [thèmes par défaut](#themes)
En général, vous ne pourrez pas mettre les images côte à côte. Pour ce faire, vous pouvez utiliser cette approche :
```md
```html
<a href="https://github.com/anuraghazra/github-readme-stats">
<img align="center" src="https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats" />
</a>
+1 -1
View File
@@ -288,7 +288,7 @@ Scegli uno dei [temi di default](#themes)
Per allineare le card una accanto all'altra, puoi adottare questo approccio:
```md
```html
<a href="https://github.com/anuraghazra/github-readme-stats">
<img align="center" src="https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats" />
</a>
+1 -1
View File
@@ -296,7 +296,7 @@ _NOTE: Top languages は、ユーザのスキルレベルを示すものでは
通常、画像を並べてレイアウトすることはできません。画像を並べるには、以下のような方法があります。
```md
```html
<a href="https://github.com/anuraghazra/github-readme-stats">
<img align="center" src="https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats" />
</a>
+1 -1
View File
@@ -367,7 +367,7 @@ _참고:
그럴땐, 이렇게 해보세요!
```md
```html
<a href="https://github.com/anuraghazra/github-readme-stats">
<img align="center" src="https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats" />
</a>
+1 -1
View File
@@ -346,7 +346,7 @@ Kies uit de [standaard thema\'s](#themes)
Meestal kan je de afbeeldingen niet naast elkaar zetten, op deze manier wel:
```md
```html
<a href="https://github.com/anuraghazra/github-readme-stats">
<img align="center" src="https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats" />
</a>
+1 -1
View File
@@ -340,7 +340,7 @@ Change the `?username=` value to your [Wakatime](https://wakatime.com) username.
तपाइले इमेजलाई सइद बय सइद अलीग्न गर्न सक्नु हुदैन तेसैले येसरी गर्नु होस् :
```md
```html
<a href="https://github.com/anuraghazra/github-readme-stats">
<img align="center" src="https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats" />
</a>
+3 -3
View File
@@ -27,7 +27,7 @@
</p>
<p align="center">
<a href="#demo">Ver demonstração</a>
<a href="#demonstração">Ver demonstração</a>
·
<a href="https://github.com/anuraghazra/github-readme-stats/issues/new/choose">Reportar erros</a>
·
@@ -147,7 +147,7 @@ Personalize a aparência do seu `Stats Card` ou `Repo Card` da maneira que desej
#### Opções exclusivas do cartão de estatísticas:
- `hide` - Oculta itens específicos das estatísticas _(Valores separados por vírgulas)_
- `hide_title` - Ocutar o título _(boolean)_
- `hide_title` - Ocultar o título _(boolean)_
- `hide_rank` - Ocultar a classificação _(boolean)_
- `show_icons` - Mostrar ícones _(boolean)_
- `include_all_commits` - Contabiliza todos os commits ao invés de apenas os atual ano _(boolean)_
@@ -295,7 +295,7 @@ Escolha entre um dos [temas predefinidos](#temas)
Por padrão, você não poderá organizar as imagens lado a lado. Para fazer isso, você pode usar a seguinte abordagem:
```md
```html
<a href="https://github.com/anuraghazra/github-readme-stats">
<img align="center" src="https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats" />
</a>
+1 -1
View File
@@ -351,7 +351,7 @@ Endpoint: `api/top-langs?username=mustafacagri`
Genellikle resimleri yan yana düzenleyemezsiniz. Bunu yapmak için şu yaklaşımı kullanabilirsiniz:
```md
```html
<a href="https://github.com/anuraghazra/github-readme-stats">
<img align="center" src="https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats" />
</a>
+15 -5
View File
@@ -7,27 +7,37 @@
"test": "jest --coverage",
"test:watch": "jest --watch",
"theme-readme-gen": "node scripts/generate-theme-doc",
"preview-theme": "node scripts/preview-theme"
"preview-theme": "node scripts/preview-theme",
"generate-langs-json": "node scripts/generate-langs-json",
"format": "./node_modules/.bin/prettier --write .",
"format:check": "./node_modules/.bin/prettier --check ."
},
"author": "Anurag Hazra",
"license": "MIT",
"engines": {
"node": ">=13"
},
"devDependencies": {
"@actions/core": "^1.2.4",
"@actions/github": "^4.0.0",
"@testing-library/dom": "^7.20.0",
"@testing-library/jest-dom": "^5.11.0",
"axios": "^0.19.2",
"@uppercod/css-to-object": "^1.1.1",
"axios-mock-adapter": "^1.18.1",
"css-to-object": "^1.1.0",
"color-contrast-checker": "^2.1.0",
"hjson": "^3.2.2",
"husky": "^4.2.5",
"jest": "^26.1.0",
"parse-diff": "^0.7.0"
"js-yaml": "^4.1.0",
"lodash.snakecase": "^4.1.1",
"parse-diff": "^0.7.0",
"prettier": "^2.1.2"
},
"dependencies": {
"axios": "^0.24.0",
"dotenv": "^8.2.0",
"emoji-name-map": "^1.2.8",
"github-username-regex": "^1.0.0",
"prettier": "^2.1.2",
"word-wrap": "^1.2.3"
},
"husky": {
+45 -27
View File
@@ -1,7 +1,7 @@
<p align="center">
<img width="100px" src="https://res.cloudinary.com/anuraghazra/image/upload/v1594908242/logo_ccswme.svg" align="center" alt="GitHub Readme Stats" />
<h2 align="center">GitHub Readme Stats</h2>
<p align="center">Get dynamically generated GitHub stats on your readmes!</p>
<p align="center">Get dynamically generated GitHub stats on your READMEs!</p>
</p>
<p align="center">
<a href="https://github.com/anuraghazra/github-readme-stats/actions">
@@ -32,6 +32,8 @@
<a href="https://github.com/anuraghazra/github-readme-stats/issues/new/choose">Report Bug</a>
·
<a href="https://github.com/anuraghazra/github-readme-stats/issues/new/choose">Request Feature</a>
·
<a href="https://github.com/anuraghazra/github-readme-stats/discussions">Ask Question</a>
</p>
<p align="center">
<a href="/docs/readme_fr.md">Français </a>
@@ -65,13 +67,13 @@
<img src="https://d2wvdrxmr8p0wf.cloudfront.net/static/giveindia.svg" alt="Give india logo" width="200" />
</a>
Are you considering to support the project by donating to me? Please DON'T!!
Are you considering supporting the project by donating? Please DON'T!!
Instead, Help India fight 2nd deadly wave of COVID,
Thousands of people are dying in India for lack of Oxygen & COVID related necessary infrastructure.
Instead, Help India fight the 2nd deadly wave of COVID-19.
Thousands of people are dying in India because of a lack of Oxygen & also COVID-related infrastructure.
Visit [https://indiafightscorona.giveindia.org](https://indiafightscorona.giveindia.org) and make a small donation to help us fight covid and overcome this crisis.
Your small help goes a long way. :heart:
Visit [https://indiafightscorona.giveindia.org](https://indiafightscorona.giveindia.org) and make a small donation to help us fight COVID and overcome this crisis.
A small donation goes a long way. :heart:
</p>
@@ -83,13 +85,18 @@ Your small help goes a long way. :heart:
- [Wakatime Week Stats](#wakatime-week-stats)
- [Themes](#themes)
- [Customization](#customization)
- [Common Options](#common-options)
- [Stats Card Exclusive Options](#stats-card-exclusive-options)
- [Repo Card Exclusive Options](#repo-card-exclusive-options)
- [Language Card Exclusive Options](#language-card-exclusive-options)
- [Wakatime Card Exclusive Option](#wakatime-card-exclusive-options)
- [Deploy Yourself](#deploy-on-your-own-vercel-instance)
# GitHub Stats Card
Copy-paste this into your markdown content, and that's it. Simple!
Change the `?username=` value to your GitHub's username.
Change the `?username=` value to your GitHub username.
```md
[![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats)
@@ -97,11 +104,11 @@ Change the `?username=` value to your GitHub's username.
_Note: Available ranks are S+ (top 1%), S (top 25%), A++ (top 45%), A+ (top 60%), and B+ (everyone).
The values are calculated by using the [cumulative distribution function](https://en.wikipedia.org/wiki/Cumulative_distribution_function) using commits, contributions, issues, stars, pull requests, followers, and owned repositories.
The implementation can be investigated at [src/calculateRank.js](./src/calculateRank.js)_
The implementation can be investigated at [src/calculateRank.js](./src/calculateRank.js)._
### Hiding individual stats
To hide any specific stats, you can pass a query parameter `?hide=` with comma-separated values.
To hide any specific stats, you can pass a query parameter `&hide=` with comma-separated values.
> Options: `&hide=stars,commits,prs,issues,contribs`
@@ -111,9 +118,9 @@ To hide any specific stats, you can pass a query parameter `?hide=` with comma-s
### Adding private contributions count to total commits count
You can add the count of all your private contributions to the total commits count by using the query parameter `?count_private=true`.
You can add the count of all your private contributions to the total commits count by using the query parameter `&count_private=true`.
_Note: If you are deploying this project yourself, the private contributions will be counted by default otherwise you need to chose to share your private contribution counts._
_Note: If you are deploying this project yourself, the private contributions will be counted by default. Otherwise, you need to choose to share your private contribution counts._
> Options: `&count_private=true`
@@ -133,13 +140,13 @@ To enable icons, you can pass `show_icons=true` in the query param, like so:
With inbuilt themes, you can customize the look of the card without doing any [manual customization](#customization).
Use `?theme=THEME_NAME` parameter like so :-
Use `&theme=THEME_NAME` parameter like so :-
```md
![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&theme=radical)
```
#### All inbuilt themes :-
#### All inbuilt themes:-
dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontrast, dracula
@@ -160,13 +167,15 @@ You can customize the appearance of your `Stats Card` or `Repo Card` however you
- `bg_color` - Card's background color _(hex color)_ **or** a gradient in the form of _angle,start,end_
- `hide_border` - Hides the card's border _(boolean)_
- `theme` - name of the theme, choose from [all available themes](./themes/README.md)
- `cache_seconds` - set the cache header manually _(min: 1800, max: 86400)_
- `cache_seconds` - set the cache header manually _(min: 7200, max: 86400)_
- `locale` - set the language in the card _(e.g. cn, de, es, etc.)_
- `border_radius` - Corner rounding on the card_
- `border_radius` - Corner rounding on the card
> Note: The minimum of cache_seconds is currently 4 hours as a temporary fix for PATs exhaustion.
##### Gradient in bg_color
You can provide multiple comma-separated values in bg_color option to render a gradient, the format of the gradient is :-
You can provide multiple comma-separated values in the bg_color option to render a gradient, with the following format:
```
&bg_color=DEG,COLOR1,COLOR2,COLOR3...COLOR10
@@ -176,14 +185,17 @@ You can provide multiple comma-separated values in bg_color option to render a g
#### Stats Card Exclusive Options:
- `hide` - Hides the specified items from stats _(Comma-separated values)_
- `hide` - Hides the [specified items](#hiding-individual-stats) from stats _(Comma-separated values)_
- `hide_title` - _(boolean)_
- `card_width` - Set the card's width manually _(number)_
- `hide_rank` - _(boolean)_ hides the rank and automatically resizes the card width
- `show_icons` - _(boolean)_
- `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_
- `count_private` - Count private commits _(boolean)_
- `line_height` - Sets the line-height between text _(number)_
- `exclude_repo` - Exclude stars from specified repositories _(Comma-separated values)_
- `custom_title` - Sets a custom title for the card
- `text_bold` - Use bold text _(boolean)_
- `disable_animations` - Disables all animations in the card _(boolean)_
#### Repo Card Exclusive Options:
@@ -207,14 +219,15 @@ You can provide multiple comma-separated values in bg_color option to render a g
#### Wakatime Card Exclusive Options:
- `hide` - Hide the languages specified from the card _(Comma-separated values)_
- `hide_title` - _(boolean)_
- `line_height` - Sets the line-height between text _(number)_
- `hide_progress` - Hides the progress bar and percentage _(boolean)_
- `custom_title` - Sets a custom title for the card
- `layout` - Switch between two available layouts `default` & `compact`
- `langs_count` - Limit number of languages on the card, defaults to all reported langauges
- `langs_count` - Limit the number of languages on the card, defaults to all reported languages
- `api_domain` - Set a custom API domain for the card, e.g. to use services like [Hakatime](https://github.com/mujx/hakatime) or [Wakapi](https://github.com/muety/wakapi)
- `range` Request a range different from your WakaTime default, e.g. `last_7_days`. See [WakaTime API docs](https://wakatime.com/developers#stats) for list of available options.
- `range` Request a range different from your WakaTime default, e.g. `last_7_days`. See [WakaTime API docs](https://wakatime.com/developers#stats) for a list of available options.
---
@@ -244,9 +257,9 @@ Use [show_owner](#customization) variable to include the repo's owner username
# Top Languages Card
The top languages card show a GitHub user's most frequently used top language.
The top languages card shows a GitHub user's most frequently used top language.
_NOTE: Top Languages does not indicate my skill level or anything like that, it's a GitHub metric of which languages have the most code on GitHub. It's a new feature of github-readme-stats._
_NOTE: Top Languages does not indicate my skill level or anything like that; it's a GitHub metric to determine which languages have the most code on GitHub. It's a new feature of github-readme-stats._
### Usage
@@ -260,7 +273,7 @@ Endpoint: `api/top-langs?username=anuraghazra`
### Exclude individual repositories
You can use `?exclude_repo=repo1,repo2` parameter to exclude individual repositories.
You can use `&exclude_repo=repo1,repo2` parameter to exclude individual repositories.
```md
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&exclude_repo=github-readme-stats,anuraghazra.github.io)](https://github.com/anuraghazra/github-readme-stats)
@@ -268,7 +281,7 @@ You can use `?exclude_repo=repo1,repo2` parameter to exclude individual reposito
### Hide individual languages
You can use `?hide=language1,language2` parameter to hide individual languages.
You can use `&hide=language1,language2` parameter to hide individual languages.
```md
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide=javascript,html)](https://github.com/anuraghazra/github-readme-stats)
@@ -306,6 +319,8 @@ Change the `?username=` value to your [Wakatime](https://wakatime.com) username.
[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats)
```
> Note: Please be aware that we currently only show data from Wakatime profiles that are public.
### Demo
[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats)
@@ -403,14 +418,16 @@ NOTE: Since [#58](https://github.com/anuraghazra/github-readme-stats/pull/58) we
![](https://files.catbox.moe/tct1wg.png)
1. Sign in with GitHub by pressing `Continue with GitHub`
![](https://files.catbox.moe/btd78j.jpeg)
1. Sign into GitHub and allow access to all repositories, if prompted
1. Sign in to GitHub and allow access to all repositories, if prompted
1. Fork this repo
1. After forking the repo, open the [`vercel.json`](https://github.com/anuraghazra/github-readme-stats/blob/master/vercel.json#L5) file and change the `maxDuration` field to `10`
1. Go back to your [Vercel dashboard](https://vercel.com/dashboard)
1. Select `Import Project`
![](https://files.catbox.moe/qckos0.png)
1. Select `Import Git Repository`
1. Select `Import Git Repository`. Select root and keep everything as is.
![](https://files.catbox.moe/pqub9q.png)
1. Select root and keep everything as is, just add your environment variable named PAT_1 (as shown), which will contain a personal access token (PAT), which you can easily create [here](https://github.com/settings/tokens/new) (leave everything as is, just name it something, it can be anything you want)
1. Create a personal access token (PAT) [here](https://github.com/settings/tokens/new) and enable the `repo` permissions (this allows access to see private repo stats)
1. Add the PAT as an environment variable named `PAT_1` (as shown).
![](https://files.catbox.moe/0ez4g7.png)
1. Click deploy, and you're good to go. See your domains to use the API!
@@ -421,7 +438,7 @@ NOTE: Since [#58](https://github.com/anuraghazra/github-readme-stats/pull/58) we
I open-source almost everything I can, and I try to reply to everyone needing help using these projects. Obviously,
this takes time. You can use this service for free.
However, if you are using this project and are happy with it or just want to encourage me to continue creating stuff, there are few ways you can do it :-
However, if you are using this project and are happy with it or just want to encourage me to continue creating stuff, there are a few ways you can do it:-
- Giving proper credit when you use github-readme-stats on your readme, linking back to it :D
- Starring and sharing the project :rocket:
@@ -437,3 +454,4 @@ Thanks! :heart:
Contributions are welcome! <3
Made with :heart: and JavaScript.
+30
View File
@@ -0,0 +1,30 @@
const fs = require("fs");
const jsYaml = require("js-yaml");
const axios = require("axios");
const LANGS_FILEPATH = "./src/common/languageColors.json";
//Retrieve languages from github linguist repository yaml file
//@ts-ignore
axios
.get(
"https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml",
)
.then((response) => {
//and convert them to a JS Object
const languages = jsYaml.load(response.data);
const languageColors = {};
//Filter only language colors from the whole file
Object.keys(languages).forEach((lang) => {
languageColors[lang] = languages[lang].color;
});
//Debug Print
//console.dir(languageColors);
fs.writeFileSync(
LANGS_FILEPATH,
JSON.stringify(languageColors, null, " "),
);
});
+2 -2
View File
@@ -12,7 +12,7 @@ const THEME_TEMPLATE = `## Available Themes
<!-- DO NOT EDIT THIS FILE DIRECTLY -->
With inbuilt themes you can customize the look of the card without doing any manual customization.
With inbuilt themes, you can customize the look of the card without doing any manual customization.
Use \`?theme=THEME_NAME\` parameter like so :-
@@ -43,7 +43,7 @@ ${REPO_CARD_LINKS_FLAG}
[add-theme]: https://github.com/anuraghazra/github-readme-stats/edit/master/themes/index.js
Wanted to add a new theme? Consider reading the [contribution guidelines](../CONTRIBUTING.md#themes-contribution) :D
Want to add a new theme? Consider reading the [contribution guidelines](../CONTRIBUTING.md#themes-contribution) :D
`;
const createRepoMdLink = (theme) => {
+119 -31
View File
@@ -1,8 +1,16 @@
const core = require("@actions/core");
const github = require("@actions/github");
const parse = require("parse-diff");
const Hjson = require("hjson");
const snakeCase = require("lodash.snakecase");
const ColorContrastChecker = require("color-contrast-checker");
require("dotenv").config();
const OWNER = "anuraghazra";
const REPO = "github-readme-stats";
const COMMENT_TITLE = "Automated Theme Preview";
function getPrNumber() {
const pullRequest = github.context.payload.pull_request;
if (!pullRequest) {
@@ -12,17 +20,72 @@ function getPrNumber() {
return pullRequest.number;
}
function findCommentPredicate(inputs, comment) {
return (
(inputs.commentAuthor && comment.user
? comment.user.login === inputs.commentAuthor
: true) &&
(inputs.bodyIncludes && comment.body
? comment.body.includes(inputs.bodyIncludes)
: true)
);
}
async function findComment(octokit, issueNumber) {
const parameters = {
owner: OWNER,
repo: REPO,
issue_number: issueNumber,
};
const inputs = {
commentAuthor: OWNER,
bodyIncludes: COMMENT_TITLE,
};
for await (const { data: comments } of octokit.paginate.iterator(
octokit.rest.issues.listComments,
parameters,
)) {
// Search each page for the comment
const comment = comments.find((comment) =>
findCommentPredicate(inputs, comment),
);
if (comment) return comment;
}
}
async function upsertComment(octokit, props) {
if (props.comment_id !== undefined) {
await octokit.issues.updateComment(props);
} else {
await octokit.issues.createComment(props);
}
}
function getWebAimLink(color1, color2) {
return `https://webaim.org/resources/contrastchecker/?fcolor=${color1}&bcolor=${color2}`;
}
function getGrsLink(colors) {
const url = `https://github-readme-stats.vercel.app/api?username=anuraghazra`;
const colorString = Object.keys(colors)
.map((colorKey) => `${colorKey}=${colors[colorKey]}`)
.join("&");
return `${url}&${colorString}&show_icons=true`;
}
const themeContribGuidelines = `
\r> Hi thanks for the theme contribution, please read our theme contribution guidelines
\rHi, thanks for the theme contribution, please read our theme [contribution guidelines](https://github.com/anuraghazra/github-readme-stats/blob/master/CONTRIBUTING.md#themes-contribution).
\rWe are currently only accepting color combinations from any VSCode theme or themes which have good color combination to minimize bloating the themes collection.
\r> We are currently only accepting color combinations from any vscode theme or which has good color combination to minimize bloating the themes collection.
\r> Also note that if this theme is exclusively for your personal use then instead of adding it to our theme collection you can use card [customization options](https://github.com/anuraghazra/github-readme-stats#customization)
\r> Read our [contribution guidelines](https://github.com/anuraghazra/github-readme-stats/blob/master/CONTRIBUTING.md) for more info
\r> Also note that if this theme is exclusively for your personal use, then instead of adding it to our theme collection you can use card [customization options](https://github.com/anuraghazra/github-readme-stats#customization)
`;
async function run() {
try {
const ccc = new ColorContrastChecker();
const warnings = [];
const token = core.getInput("token");
const octokit = github.getOctokit(token || process.env.PERSONAL_TOKEN);
const pullRequestId = getPrNumber();
@@ -32,65 +95,90 @@ async function run() {
return;
}
let res = await octokit.pulls.get({
owner: "anuraghazra",
repo: "github-readme-stats",
const res = await octokit.pulls.get({
owner: OWNER,
repo: REPO,
pull_number: pullRequestId,
mediaType: {
format: "diff",
},
});
const comment = await findComment(octokit, pullRequestId);
let diff = parse(res.data);
let colorStrings = diff
const diff = parse(res.data);
const content = diff
.find((file) => file.to === "themes/index.js")
.chunks[0].changes.filter((c) => c.type === "add")
.map((c) => c.content.replace("+", ""))
.join("");
let matches = colorStrings.match(/(title_color:.*bg_color.*\")/);
let colors = matches && matches[0].split(",");
const themeObject = Hjson.parse(content);
const themeName = Object.keys(themeObject)[0];
const colors = themeObject[themeName];
if (themeName !== snakeCase(themeName)) {
warnings.push("Theme name isn't in snake_case");
}
if (!colors) {
await octokit.issues.createComment({
owner: "anuraghazra",
repo: "github-readme-stats",
await upsertComment({
comment_id: comment?.id,
owner: OWNER,
repo: REPO,
issue_number: pullRequestId,
body: `
\rTheme preview (bot)
\r**${COMMENT_TITLE}**
\rCannot create theme preview
${themeContribGuidelines}
`,
issue_number: pullRequestId,
});
return;
}
colors = colors.map((color) =>
color.replace(/.*\:\s/, "").replace(/\"/g, ""),
);
const titleColor = colors[0];
const iconColor = colors[1];
const textColor = colors[2];
const bgColor = colors[3];
const url = `https://github-readme-stats.vercel.app/api?username=anuraghazra&title_color=${titleColor}&icon_color=${iconColor}&text_color=${textColor}&bg_color=${bgColor}&show_icons=true`;
const titleColor = colors.title_color;
const iconColor = colors.icon_color;
const textColor = colors.text_color;
const bgColor = colors.bg_color;
const url = getGrsLink(colors);
await octokit.issues.createComment({
owner: "anuraghazra",
repo: "github-readme-stats",
const colorPairs = {
title_color: [titleColor, bgColor],
icon_color: [iconColor, bgColor],
text_color: [textColor, bgColor],
};
// check color contrast
Object.keys(colorPairs).forEach((key) => {
const color1 = colorPairs[key][0];
const color2 = colorPairs[key][1];
if (!ccc.isLevelAA(`#${color1}`, `#${color2}`)) {
const permalink = getWebAimLink(color1, color2);
warnings.push(
`\`${key}\` does not pass [AA contrast ratio](${permalink})`,
);
}
});
await upsertComment(octokit, {
comment_id: comment?.id,
issue_number: pullRequestId,
owner: OWNER,
repo: REPO,
body: `
\rTheme preview (bot)
\r**${COMMENT_TITLE}**
\r${warnings.map((warning) => `- :warning: ${warning}\n`).join("")}
\ntitle_color: <code>#${titleColor}</code> | icon_color: <code>#${iconColor}</code> | text_color: <code>#${textColor}</code> | bg_color: <code>#${bgColor}</code>
\rLink: ${url}
\r[Preview Link](${url})
\r[![](${url})](${url})
${themeContribGuidelines}
`,
issue_number: pullRequestId,
});
} catch (error) {
console.log(error);
+8 -14
View File
@@ -36,20 +36,14 @@ function calculateRank({
const RANK_A = 0.50;
const RANK_B_PLUS = 0.75;
let level = "";
if (rank <= RANK_S_PLUS)
level = "S+";
else if (rank <= RANK_S)
level = "S";
else if (rank <= RANK_A_PLUS)
level = "A+";
else if (rank <= RANK_A)
level = "A";
else if (rank <= RANK_B_PLUS)
level = "B+";
else
level = "B";
const level = (() => {
if (rank <= RANK_S_PLUS) return "S+";
if (rank <= RANK_S) return "S";
if (rank <= RANK_A_PLUS) return "A+";
if (rank <= RANK_A) return "A";
if (rank <= RANK_B_PLUS) return "B+";
return "B";
})();
return {level, score: rank * 100};
}
+92 -83
View File
@@ -1,25 +1,85 @@
const toEmoji = require("emoji-name-map");
// @ts-check
const {
kFormatter,
encodeHTML,
getCardColors,
flexLayout,
wrapTextMultiline,
measureText,
parseEmojis,
} = require("../common/utils");
const I18n = require("../common/I18n");
const Card = require("../common/Card");
const icons = require("../common/icons");
const { repoCardLocales } = require("../translations");
/**
* @param {string} label
* @param {string} textColor
* @returns {string}
*/
const getBadgeSVG = (label, textColor) => `
<g data-testid="badge" class="badge" transform="translate(320, -18)">
<rect stroke="${textColor}" stroke-width="1" width="70" height="20" x="-12" y="-14" ry="10" rx="10"></rect>
<text
x="23" y="-5"
alignment-baseline="central"
dominant-baseline="central"
text-anchor="middle"
fill="${textColor}"
>
${label}
</text>
</g>
`;
/**
* @param {string} langName
* @param {string} langColor
* @returns {string}
*/
const createLanguageNode = (langName, langColor) => {
return `
<g data-testid="primary-lang">
<circle data-testid="lang-color" cx="0" cy="-5" r="6" fill="${langColor}" />
<text data-testid="lang-name" class="gray" x="15">${langName}</text>
</g>
`;
};
const ICON_SIZE = 16;
const iconWithLabel = (icon, label, testid) => {
if (label <= 0) return "";
const iconSvg = `
<svg
class="icon"
y="-12"
viewBox="0 0 16 16"
version="1.1"
width="${ICON_SIZE}"
height="${ICON_SIZE}"
>
${icon}
</svg>
`;
const text = `<text data-testid="${testid}" class="gray">${label}</text>`;
return flexLayout({ items: [iconSvg, text], gap: 20 }).join("");
};
/**
* @param {import('../fetchers/types').RepositoryData} repo
* @param {Partial<import("./types").RepoCardOptions>} options
* @returns {string}
*/
const renderRepoCard = (repo, options = {}) => {
const {
name,
nameWithOwner,
description,
primaryLanguage,
stargazers,
isArchived,
isTemplate,
starCount,
forkCount,
} = repo;
const {
@@ -35,22 +95,17 @@ const renderRepoCard = (repo, options = {}) => {
locale,
} = options;
const lineHeight = 10;
const header = show_owner ? nameWithOwner : name;
const langName = (primaryLanguage && primaryLanguage.name) || "Unspecified";
const langColor = (primaryLanguage && primaryLanguage.color) || "#333";
const shiftText = langName.length > 15 ? 0 : 30;
let desc = description || "No description provided";
// parse emojis to unicode
desc = desc.replace(/:\w+:/gm, (emoji) => {
return toEmoji.get(emoji) || "";
});
const desc = parseEmojis(description || "No description provided");
const multiLineDescription = wrapTextMultiline(desc);
const descriptionLines = multiLineDescription.length;
const lineHeight = 10;
const descriptionSvg = multiLineDescription
.map((line) => `<tspan dy="1.2em" x="25">${encodeHTML(line)}</tspan>`)
.join("");
const height =
(descriptionLines > 1 ? 120 : 110) + descriptionLines * lineHeight;
@@ -61,13 +116,7 @@ const renderRepoCard = (repo, options = {}) => {
});
// returns theme based colors with proper overrides and defaults
const {
titleColor,
textColor,
iconColor,
bgColor,
borderColor,
} = getCardColors({
const colors = getCardColors({
title_color,
icon_color,
text_color,
@@ -76,74 +125,41 @@ const renderRepoCard = (repo, options = {}) => {
theme,
});
const totalStars = kFormatter(stargazers.totalCount);
const totalForks = kFormatter(forkCount);
const getBadgeSVG = (label) => `
<g data-testid="badge" class="badge" transform="translate(320, -18)">
<rect stroke="${textColor}" stroke-width="1" width="70" height="20" x="-12" y="-14" ry="10" rx="10"></rect>
<text
x="23" y="-5"
alignment-baseline="central"
dominant-baseline="central"
text-anchor="middle"
fill="${textColor}"
>
${label}
</text>
</g>
`;
const svgLanguage = primaryLanguage
? `
<g data-testid="primary-lang" transform="translate(30, 0)">
<circle data-testid="lang-color" cx="0" cy="-5" r="6" fill="${langColor}" />
<text data-testid="lang-name" class="gray" x="15">${langName}</text>
</g>
`
? createLanguageNode(langName, langColor)
: "";
const iconWithLabel = (icon, label, testid) => {
return `
<svg class="icon" y="-12" viewBox="0 0 16 16" version="1.1" width="16" height="16">
${icon}
</svg>
<text data-testid="${testid}" class="gray" x="25">${label}</text>
`;
};
const svgStars =
stargazers.totalCount > 0 &&
iconWithLabel(icons.star, totalStars, "stargazers");
const svgForks =
forkCount > 0 && iconWithLabel(icons.fork, totalForks, "forkcount");
const totalStars = kFormatter(starCount);
const totalForks = kFormatter(forkCount);
const svgStars = iconWithLabel(icons.star, totalStars, "stargazers");
const svgForks = iconWithLabel(icons.fork, totalForks, "forkcount");
const starAndForkCount = flexLayout({
items: [svgStars, svgForks],
gap: 65,
items: [svgLanguage, svgStars, svgForks],
sizes: [
measureText(langName, 12),
ICON_SIZE + measureText(`${totalStars}`, 12),
ICON_SIZE + measureText(`${totalForks}`, 12),
],
gap: 25,
}).join("");
const card = new Card({
defaultTitle: header,
defaultTitle: header.length > 35 ? `${header.slice(0, 35)}...` : header,
titlePrefixIcon: icons.contribs,
width: 400,
height,
border_radius,
colors: {
titleColor,
textColor,
iconColor,
bgColor,
borderColor,
},
colors,
});
card.disableAnimations();
card.setHideBorder(hide_border);
card.setHideTitle(false);
card.setCSS(`
.description { font: 400 13px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
.gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
.icon { fill: ${iconColor} }
.description { font: 400 13px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} }
.gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} }
.icon { fill: ${colors.iconColor} }
.badge { font: 600 11px 'Segoe UI', Ubuntu, Sans-Serif; }
.badge rect { opacity: 0.2 }
`);
@@ -151,27 +167,20 @@ const renderRepoCard = (repo, options = {}) => {
return card.render(`
${
isTemplate
? getBadgeSVG(i18n.t("repocard.template"))
? // @ts-ignore
getBadgeSVG(i18n.t("repocard.template"), colors.textColor)
: isArchived
? getBadgeSVG(i18n.t("repocard.archived"))
? // @ts-ignore
getBadgeSVG(i18n.t("repocard.archived"), colors.textColor)
: ""
}
<text class="description" x="25" y="-5">
${multiLineDescription
.map((line) => `<tspan dy="1.2em" x="25">${encodeHTML(line)}</tspan>`)
.join("")}
${descriptionSvg}
</text>
<g transform="translate(0, ${height - 75})">
${svgLanguage}
<g
data-testid="star-fork-group"
transform="translate(${primaryLanguage ? 155 - shiftText : 25}, 0)"
>
${starAndForkCount}
</g>
<g transform="translate(30, ${height - 75})">
${starAndForkCount}
</g>
`);
};
+102 -50
View File
@@ -1,3 +1,4 @@
// @ts-check
const I18n = require("../common/I18n");
const Card = require("../common/Card");
const icons = require("../common/icons");
@@ -19,6 +20,7 @@ const createTextNode = ({
index,
showIcons,
shiftValuePos,
bold,
}) => {
const kValue = kFormatter(value);
const staggerDelay = (index + 3) * 150;
@@ -34,17 +36,24 @@ const createTextNode = ({
return `
<g class="stagger" style="animation-delay: ${staggerDelay}ms" transform="translate(25, 0)">
${iconSvg}
<text class="stat bold" ${labelOffset} y="12.5">${label}:</text>
<text
class="stat"
x="${(showIcons ? 140 : 120) + shiftValuePos}"
y="12.5"
<text class="stat ${
bold ? " bold" : "not_bold"
}" ${labelOffset} y="12.5">${label}:</text>
<text
class="stat ${bold ? " bold" : "not_bold"}"
x="${(showIcons ? 140 : 120) + shiftValuePos}"
y="12.5"
data-testid="${id}"
>${kValue}</text>
</g>
`;
};
/**
* @param {Partial<import('../fetchers/types').StatsData>} stats
* @param {Partial<import("./types").StatCardOptions>} options
* @returns {string}
*/
const renderStatsCard = (stats = {}, options = { hide: [] }) => {
const {
name,
@@ -60,12 +69,14 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
show_icons = false,
hide_title = false,
hide_border = false,
card_width,
hide_rank = false,
include_all_commits = false,
line_height = 25,
title_color,
icon_color,
text_color,
text_bold = true,
bg_color,
theme = "default",
custom_title,
@@ -75,23 +86,18 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
disable_animations = false,
} = options;
const lheight = parseInt(line_height, 10);
const lheight = parseInt(String(line_height), 10);
// returns theme based colors with proper overrides and defaults
const {
titleColor,
textColor,
iconColor,
bgColor,
borderColor,
} = getCardColors({
title_color,
icon_color,
text_color,
bg_color,
border_color,
theme,
});
const { titleColor, textColor, iconColor, bgColor, borderColor } =
getCardColors({
title_color,
icon_color,
text_color,
bg_color,
border_color,
theme,
});
const apostrophe = ["x", "s"].includes(name.slice(-1).toLocaleLowerCase())
? ""
@@ -147,6 +153,8 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
"id",
"my",
"pl",
"de",
"nl",
];
const isLongLocale = longLocales.includes(locale) === true;
@@ -160,7 +168,8 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
index,
showIcons: show_icons,
shiftValuePos:
(!include_all_commits ? 50 : 20) + (isLongLocale ? 50 : 0),
(!include_all_commits ? 50 : 35) + (isLongLocale ? 50 : 0),
bold: text_bold,
}),
);
@@ -171,26 +180,6 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
hide_rank ? 0 : 150,
);
// Conditionally rendered elements
const rankCircle = hide_rank
? ""
: `<g data-testid="rank-circle"
transform="translate(400, ${height / 2 - 50})">
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
<circle class="rank-circle" cx="-10" cy="8" r="40" />
<g class="rank-text">
<text
x="${rank.level.length === 1 ? "-4" : "0"}"
y="0"
alignment-baseline="central"
dominant-baseline="central"
text-anchor="middle"
>
${rank.level}
</text>
</g>
</g>`;
// the better user's score the the rank will be closer to zero so
// subtracting 100 to get the progress in 100%
const progress = 100 - rank.score;
@@ -206,13 +195,20 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
return measureText(custom_title ? custom_title : i18n.t("statcard.title"));
};
const width = hide_rank
? clampValue(
50 /* padding */ + calculateTextWidth() * 2,
270 /* min */,
Infinity,
)
: 495;
/*
When hide_rank=true, the minimum card width is 270 px + the title length and padding.
When hide_rank=false, the minimum card_width is 340 px + the icon width (if show_icons=true).
Numbers are picked by looking at existing dimensions on production.
*/
const iconWidth = show_icons ? 16 : 0;
const minCardWidth = hide_rank
? clampValue(50 /* padding */ + calculateTextWidth() * 2, 270, Infinity)
: 340 + iconWidth;
const defaultCardWidth = hide_rank ? 270 : 495;
let width = isNaN(card_width) ? defaultCardWidth : card_width;
if (width < minCardWidth) {
width = minCardWidth;
}
const card = new Card({
customTitle: custom_title,
@@ -235,16 +231,72 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
if (disable_animations) card.disableAnimations();
/**
* Calculates the right rank circle translation values such that the rank circle
* keeps respecting the padding.
*
* width > 450: The default left padding of 50 px will be used.
* width < 450: The left and right padding will shrink equally.
*
* @returns {number} - Rank circle translation value.
*/
const calculateRankXTranslation = () => {
if (width < 450) {
return width - 95 + (45 * (450 - 340)) / 110;
} else {
return width - 95;
}
};
// Conditionally rendered elements
const rankCircle = hide_rank
? ""
: `<g data-testid="rank-circle"
transform="translate(${calculateRankXTranslation()}, ${
height / 2 - 50
})">
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
<circle class="rank-circle" cx="-10" cy="8" r="40" />
<g class="rank-text">
<text
x="-5"
y="3"
alignment-baseline="central"
dominant-baseline="central"
text-anchor="middle"
>
${rank.level}
</text>
</g>
</g>`;
// Accessibility Labels
const labels = Object.keys(STATS)
.filter((key) => !hide.includes(key))
.map((key) => {
if (key === "commits") {
return `${i18n.t("statcard.commits")} ${
include_all_commits ? "" : `in ${new Date().getFullYear()}`
} : ${totalStars}`;
}
return `${STATS[key].label}: ${STATS[key].value}`;
})
.join(", ");
card.setAccessibilityLabel({
title: `${card.title}, Rank: ${rank.level}`,
desc: labels,
});
return card.render(`
${rankCircle}
<svg x="0" y="0">
${flexLayout({
items: statItems,
gap: lheight,
direction: "column",
}).join("")}
</svg>
</svg>
`);
};
+90 -32
View File
@@ -1,16 +1,45 @@
// @ts-check
const Card = require("../common/Card");
const I18n = require("../common/I18n");
const { langCardLocales } = require("../translations");
const { createProgressNode } = require("../common/createProgressNode");
const { clampValue, getCardColors, flexLayout } = require("../common/utils");
const {
clampValue,
getCardColors,
flexLayout,
lowercaseTrim,
measureText,
chunkArray,
} = require("../common/utils");
const DEFAULT_CARD_WIDTH = 300;
const MIN_CARD_WIDTH = 230;
const DEFAULT_LANGS_COUNT = 5;
const DEFAULT_LANG_COLOR = "#858585";
const CARD_PADDING = 25;
const lowercaseTrim = (name) => name.toLowerCase().trim();
/**
* @typedef {import("../fetchers/types").Lang} Lang
*/
/**
* @param {Lang[]} arr
*/
const getLongestLang = (arr) =>
arr.reduce(
(savedLang, lang) =>
lang.name.length > savedLang.name.length ? lang : savedLang,
{ name: "", size: null, color: "" },
);
/**
* @param {{
* width: number,
* color: string,
* name: string,
* progress: string
* }} props
*/
const createProgressTextNode = ({ width, color, name, progress }) => {
const paddingRight = 95;
const progressTextX = width - paddingRight + 10;
@@ -30,12 +59,15 @@ const createProgressTextNode = ({ width, color, name, progress }) => {
`;
};
const createCompactLangNode = ({ lang, totalSize, x, y }) => {
/**
* @param {{ lang: Lang, totalSize: number }} props
*/
const createCompactLangNode = ({ lang, totalSize }) => {
const percentage = ((lang.size / totalSize) * 100).toFixed(2);
const color = lang.color || "#858585";
return `
<g transform="translate(${x}, ${y})">
<g>
<circle cx="5" cy="6" r="5" fill="${color}" />
<text data-testid="lang-name" x="15" y="10" class='lang-name'>
${lang.name} ${percentage}%
@@ -44,30 +76,40 @@ const createCompactLangNode = ({ lang, totalSize, x, y }) => {
`;
};
const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
return langs.map((lang, index) => {
if (index % 2 === 0) {
return createCompactLangNode({
/**
* @param {{ langs: Lang[], totalSize: number }} props
*/
const createLanguageTextNode = ({ langs, totalSize }) => {
const longestLang = getLongestLang(langs);
const chunked = chunkArray(langs, langs.length / 2);
const layouts = chunked.map((array) => {
// @ts-ignore
const items = array.map((lang, index) =>
createCompactLangNode({
lang,
x,
y: 12.5 * index + y,
totalSize,
// @ts-ignore
index,
});
}
return createCompactLangNode({
lang,
x: 150,
y: 12.5 + 12.5 * index,
totalSize,
index,
});
}),
);
return flexLayout({
items,
gap: 25,
direction: "column",
}).join("");
});
const percent = ((longestLang.size / totalSize) * 100).toFixed(2);
const minGap = 150;
const maxGap = 20 + measureText(`${longestLang.name} ${percent}%`, 11);
return flexLayout({
items: layouts,
gap: maxGap < minGap ? minGap : maxGap,
}).join("");
};
/**
*
* @param {any[]} langs
* @param {Lang[]} langs
* @param {number} width
* @param {number} totalLanguageSize
* @returns {string}
@@ -88,8 +130,7 @@ const renderNormalLayout = (langs, width, totalLanguageSize) => {
};
/**
*
* @param {any[]} langs
* @param {Lang[]} langs
* @param {number} width
* @param {number} totalLanguageSize
* @returns {string}
@@ -129,12 +170,13 @@ const renderCompactLayout = (langs, width, totalLanguageSize) => {
<rect x="0" y="0" width="${offsetWidth}" height="8" fill="white" rx="5" />
</mask>
${compactProgressBar}
${createLanguageTextNode({
x: 0,
y: 25,
langs,
totalSize: totalLanguageSize,
}).join("")}
<g transform="translate(0, 25)">
${createLanguageTextNode({
langs,
totalSize: totalLanguageSize,
})}
</g>
`;
};
@@ -154,6 +196,12 @@ const calculateNormalLayoutHeight = (totalLangs) => {
return 45 + (totalLangs + 1) * 40;
};
/**
*
* @param {Record<string, Lang>} topLangs
* @param {string[]} hide
* @param {string} langs_count
*/
const useLanguages = (topLangs, hide, langs_count) => {
let langs = Object.values(topLangs);
let langsToHide = {};
@@ -167,7 +215,7 @@ const useLanguages = (topLangs, hide, langs_count) => {
});
}
// filter out langauges to be hidden
// filter out languages to be hidden
langs = langs
.sort((a, b) => b.size - a.size)
.filter((lang) => {
@@ -180,6 +228,11 @@ const useLanguages = (topLangs, hide, langs_count) => {
return { langs, totalLanguageSize };
};
/**
* @param {import('../fetchers/types').TopLangData} topLangs
* @param {Partial<import("./types").TopLangOptions>} options
* @returns {string}
*/
const renderTopLanguages = (topLangs, options = {}) => {
const {
hide_title,
@@ -206,10 +259,14 @@ const renderTopLanguages = (topLangs, options = {}) => {
const { langs, totalLanguageSize } = useLanguages(
topLangs,
hide,
langs_count,
String(langs_count),
);
let width = isNaN(card_width) ? DEFAULT_CARD_WIDTH : card_width;
let width = isNaN(card_width)
? DEFAULT_CARD_WIDTH
: card_width < MIN_CARD_WIDTH
? MIN_CARD_WIDTH
: card_width;
let height = calculateNormalLayoutHeight(langs.length);
let finalLayout = "";
@@ -255,3 +312,4 @@ const renderTopLanguages = (topLangs, options = {}) => {
};
module.exports = renderTopLanguages;
module.exports.MIN_CARD_WIDTH = MIN_CARD_WIDTH;
+51
View File
@@ -0,0 +1,51 @@
type ThemeNames = keyof typeof import("../../themes");
export type CommonOptions = {
title_color: string;
icon_color: string;
text_color: string;
bg_color: string;
theme: ThemeNames;
border_radius: number;
border_color: string;
locale: string;
};
export type StatCardOptions = CommonOptions & {
hide: string[];
show_icons: boolean;
hide_title: boolean;
hide_border: boolean;
card_width: number;
hide_rank: boolean;
include_all_commits: boolean;
line_height: number | string;
custom_title: string;
disable_animations: boolean;
};
export type RepoCardOptions = CommonOptions & {
hide_border: boolean;
show_owner: boolean;
};
export type TopLangOptions = CommonOptions & {
hide_title: boolean;
hide_border: boolean;
card_width: number;
hide: string[];
layout: "compact" | "normal";
custom_title: string;
langs_count: number;
};
type WakaTimeOptions = CommonOptions & {
hide_title: boolean;
hide_border: boolean;
hide: string[];
line_height: string;
hide_progress: boolean;
custom_title: string;
layout: "compact" | "normal";
langs_count: number;
};
+109 -44
View File
@@ -1,17 +1,35 @@
// @ts-check
const Card = require("../common/Card");
const I18n = require("../common/I18n");
const { getStyles } = require("../getStyles");
const { wakatimeCardLocales } = require("../translations");
const languageColors = require("../common/languageColors.json");
const { createProgressNode } = require("../common/createProgressNode");
const { clampValue, getCardColors, flexLayout } = require("../common/utils");
const {
clampValue,
getCardColors,
flexLayout,
lowercaseTrim,
} = require("../common/utils");
/**
* @param {{color: string, text: string}} param0
*/
const noCodingActivityNode = ({ color, text }) => {
return `
<text x="25" y="11" class="stat bold" fill="${color}">${text}</text>
`;
};
/**
*
* @param {{
* lang: import("../fetchers/types").WakaTimeLang,
* totalSize: number,
* x: number,
* y: number
* }} props
*/
const createCompactLangNode = ({ lang, totalSize, x, y }) => {
const color = languageColors[lang.name] || "#858585";
@@ -25,6 +43,14 @@ const createCompactLangNode = ({ lang, totalSize, x, y }) => {
`;
};
/**
* @param {{
* langs: import("../fetchers/types").WakaTimeLang[],
* totalSize: number,
* x: number,
* y: number
* }} props
*/
const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
return langs.map((lang, index) => {
if (index % 2 === 0) {
@@ -33,7 +59,6 @@ const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
x: 25,
y: 12.5 * index + y,
totalSize,
index,
});
}
return createCompactLangNode({
@@ -41,11 +66,23 @@ const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
x: 230,
y: 12.5 + 12.5 * index,
totalSize,
index,
});
});
};
/**
*
* @param {{
* id: string;
* label: string;
* value: string;
* index: number;
* percent: number;
* hideProgress: boolean;
* progressBarColor: string;
* progressBarBackgroundColor: string
* }} props
*/
const createTextNode = ({
id,
label,
@@ -61,34 +98,56 @@ const createTextNode = ({
const cardProgress = hideProgress
? null
: createProgressNode({
x: 110,
y: 4,
progress: percent,
color: progressBarColor,
width: 220,
name: label,
progressBarBackgroundColor,
});
x: 110,
y: 4,
progress: percent,
color: progressBarColor,
width: 220,
// @ts-ignore
name: label,
progressBarBackgroundColor,
});
return `
<g class="stagger" style="animation-delay: ${staggerDelay}ms" transform="translate(25, 0)">
<text class="stat bold" y="12.5">${label}:</text>
<text class="stat bold" y="12.5" data-testid="${id}">${label}:</text>
<text
class="stat"
x="${hideProgress ? 170 : 350}"
y="12.5"
data-testid="${id}"
>${value}</text>
${cardProgress}
</g>
`;
};
/**
* @param {import("../fetchers/types").WakaTimeLang[]} languages
*/
const recalculatePercentages = (languages) => {
// recalculating percentages so that,
// compact layout's progress bar does not break when hiding languages
const totalSum = languages.reduce(
(totalSum, language) => totalSum + language.percent,
0,
);
const weight = +(100 / totalSum).toFixed(2);
languages.forEach((language) => {
language.percent = +(language.percent * weight).toFixed(2);
});
};
/**
* @param {Partial<import('../fetchers/types').WakaTimeData>} stats
* @param {Partial<import('./types').WakaTimeOptions>} options
* @returns {string}
*/
const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
const { languages } = stats;
let { languages } = stats;
const {
hide_title = false,
hide_border = false,
hide,
line_height = 25,
title_color,
icon_color,
@@ -104,35 +163,39 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
border_color,
} = options;
const shouldHideLangs = Array.isArray(hide) && hide.length > 0;
if (shouldHideLangs && languages !== undefined) {
const languagesToHide = new Set(hide.map((lang) => lowercaseTrim(lang)));
languages = languages.filter(
(lang) => !languagesToHide.has(lowercaseTrim(lang.name)),
);
recalculatePercentages(languages);
}
const i18n = new I18n({
locale,
translations: wakatimeCardLocales,
});
const lheight = parseInt(line_height, 10);
const lheight = parseInt(String(line_height), 10);
langsCount = clampValue(parseInt(langs_count), 1, langs_count);
const langsCount = clampValue(parseInt(String(langs_count)), 1, langs_count);
// returns theme based colors with proper overrides and defaults
const {
titleColor,
textColor,
iconColor,
bgColor,
borderColor,
} = getCardColors({
title_color,
icon_color,
text_color,
bg_color,
border_color,
theme,
});
const { titleColor, textColor, iconColor, bgColor, borderColor } =
getCardColors({
title_color,
icon_color,
text_color,
bg_color,
border_color,
theme,
});
const filteredLanguages = languages
? languages
.filter((language) => language.hours || language.minutes)
.slice(0, langsCount)
.filter((language) => language.hours || language.minutes)
.slice(0, langsCount)
: [];
// Calculate the card height depending on how many items there are
@@ -186,33 +249,35 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
</mask>
${compactProgressBar}
${createLanguageTextNode({
x: 0,
y: 25,
langs: filteredLanguages,
totalSize: 100,
}).join("")}
x: 0,
y: 25,
langs: filteredLanguages,
totalSize: 100,
}).join("")}
`;
} else {
finalLayout = flexLayout({
items: filteredLanguages.length
? filteredLanguages
.map((language) => {
? filteredLanguages.map((language) => {
return createTextNode({
id: language.name,
label: language.name,
value: language.text,
percent: language.percent,
// @ts-ignore
progressBarColor: titleColor,
// @ts-ignore
progressBarBackgroundColor: textColor,
hideProgress: hide_progress,
});
})
: [
noCodingActivityNode({
color: textColor,
text: i18n.t("wakatimecard.nocodingactivity"),
}),
],
noCodingActivityNode({
// @ts-ignore
color: textColor,
text: i18n.t("wakatimecard.nocodingactivity"),
}),
],
gap: lheight,
direction: "column",
}).join("");
+45 -1
View File
@@ -2,6 +2,16 @@ const { getAnimations } = require("../getStyles");
const { flexLayout, encodeHTML } = require("../common/utils");
class Card {
/**
* @param {object} args
* @param {number?=} args.width
* @param {number?=} args.height
* @param {number?=} args.border_radius
* @param {string?=} args.customTitle
* @param {string?=} args.defaultTitle
* @param {string?=} args.titlePrefixIcon
* @param {ReturnType<import('../common/utils').getCardColors>?=} args.colors
*/
constructor({
width = 100,
height = 100,
@@ -32,20 +42,39 @@ class Card {
this.paddingY = 35;
this.titlePrefixIcon = titlePrefixIcon;
this.animations = true;
this.a11yTitle = "";
this.a11yDesc = "";
}
disableAnimations() {
this.animations = false;
}
/**
* @param {{title: string, desc: string}} prop
*/
setAccessibilityLabel({ title, desc }) {
this.a11yTitle = title;
this.a11yDesc = desc;
}
/**
* @param {string} value
*/
setCSS(value) {
this.css = value;
}
/**
* @param {boolean} value
*/
setHideBorder(value) {
this.hideBorder = value;
}
/**
* @param {boolean} value
*/
setHideTitle(value) {
this.hideTitle = value;
if (value) {
@@ -53,6 +82,9 @@ class Card {
}
}
/**
* @param {string} text
*/
setTitle(text) {
this.title = text;
}
@@ -101,8 +133,9 @@ class Card {
? `
<defs>
<linearGradient
id="gradient"
id="gradient"
gradientTransform="rotate(${this.colors.bgColor[0]})"
gradientUnits="userSpaceOnUse"
>
${gradients.map((grad, index) => {
let offset = (index * 100) / (gradients.length - 1);
@@ -114,6 +147,9 @@ class Card {
: "";
}
/**
* @param {string} body
*/
render(body) {
return `
<svg
@@ -122,13 +158,21 @@ class Card {
viewBox="0 0 ${this.width} ${this.height}"
fill="none"
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-labelledby="descId"
>
<title id="titleId">${this.a11yTitle}</title>
<desc id="descId">${this.a11yDesc}</desc>
<style>
.header {
font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif;
fill: ${this.colors.titleColor};
animation: fadeInAnimation 0.8s ease-in-out forwards;
}
@supports(-moz-appearance: auto) {
/* Selector detects Firefox */
.header { font-size: 15.5px; }
}
${this.css}
${process.env.NODE_ENV === "test" ? "" : getAnimations()}
+273 -201
View File
@@ -1,430 +1,502 @@
{
"1C Enterprise": "#814CCC",
"4D": null,
"2-Dimensional Array": "#38761D",
"4D": "#004289",
"ABAP": "#E8274B",
"ActionScript": "#882B0F",
"Ada": "#02f88c",
"Agda": "#315665",
"ABAP CDS": "#555e25",
"AGS Script": "#B9D9FF",
"AIDL": "#34EB6B",
"AL": "#3AA2B5",
"Alloy": "#64C800",
"Alpine Abuild": null,
"AMPL": "#E6EFBB",
"AngelScript": "#C7D7DC",
"ANTLR": "#9DC3FF",
"Apex": "#1797c0",
"API Blueprint": "#2ACCA8",
"APL": "#5A8164",
"ASP.NET": "#9400ff",
"ATS": "#1ac620",
"ActionScript": "#882B0F",
"Ada": "#02f88c",
"Adobe Font Metrics": "#fa0f00",
"Agda": "#315665",
"Alloy": "#64C800",
"Alpine Abuild": "#0D597F",
"Altium Designer": "#A89663",
"AngelScript": "#C7D7DC",
"Ant Build System": "#A9157E",
"ApacheConf": "#d12127",
"Apex": "#1797c0",
"Apollo Guidance Computer": "#0B3D91",
"AppleScript": "#101F1F",
"Arc": "#aa2afe",
"ASL": null,
"ASP.NET": "#9400ff",
"AsciiDoc": "#73a0c5",
"AspectJ": "#a957b0",
"Assembly": "#6E4C13",
"Astro": "#ff5a03",
"Asymptote": "#ff0000",
"ATS": "#1ac620",
"Augeas": null,
"Augeas": "#9CC134",
"AutoHotkey": "#6594b9",
"AutoIt": "#1C3552",
"Awk": null,
"Avro IDL": "#0040FF",
"Awk": "#c30e9b",
"BASIC": "#ff0000",
"Ballerina": "#FF5000",
"Batchfile": "#C1F12E",
"Befunge": null,
"Beef": "#a52f4e",
"BibTeX": "#778899",
"Bicep": "#519aba",
"Bison": "#6A463F",
"BitBake": null,
"BitBake": "#00bce4",
"Blade": "#f7523f",
"BlitzBasic": null,
"BlitzBasic": "#00FFAE",
"BlitzMax": "#cd6400",
"Bluespec": null,
"Bluespec": "#12223c",
"Boo": "#d4bec1",
"Boogie": "#c80fa0",
"Brainfuck": "#2F2530",
"Brightscript": null,
"Brightscript": "#662D91",
"Browserslist": "#ffd539",
"C": "#555555",
"C#": "#178600",
"C++": "#f34b7d",
"C2hs Haskell": null,
"Cap'n Proto": null,
"CartoCSS": null,
"CLIPS": "#00A300",
"CMake": "#DA3434",
"COLLADA": "#F1A42B",
"CSON": "#244776",
"CSS": "#563d7c",
"CSV": "#237346",
"CUE": "#5886E1",
"CWeb": "#00007a",
"Cabal Config": "#483465",
"Cairo": "#ff4a48",
"Cap'n Proto": "#c42727",
"Ceylon": "#dfa535",
"Chapel": "#8dc63f",
"Charity": null,
"ChucK": null,
"ChucK": "#3f8000",
"Cirru": "#ccccff",
"Clarion": "#db901e",
"Clarity": "#5546ff",
"Classic ASP": "#6a40fd",
"Clean": "#3F85AF",
"Click": "#E4E6F3",
"CLIPS": null,
"Clojure": "#db5855",
"CMake": null,
"COBOL": null,
"CodeQL": null,
"Closure Templates": "#0d948f",
"Cloud Firestore Security Rules": "#FFA000",
"CodeQL": "#140f46",
"CoffeeScript": "#244776",
"ColdFusion": "#ed2cd6",
"ColdFusion CFC": "#ed2cd6",
"Common Lisp": "#3fb68b",
"Common Workflow Language": "#B5314C",
"Component Pascal": "#B0CE4E",
"Cool": null,
"Coq": null,
"Coq": "#d0b68c",
"Crystal": "#000100",
"CSON": "#244776",
"Csound": null,
"Csound Document": null,
"Csound Score": null,
"CSS": "#563d7c",
"Csound": "#1a1a1a",
"Csound Document": "#1a1a1a",
"Csound Score": "#1a1a1a",
"Cuda": "#3A4E3A",
"CWeb": null,
"Cycript": null,
"Cython": null,
"Curry": "#531242",
"Cython": "#fedf5b",
"D": "#ba595e",
"DM": "#447265",
"Dafny": "#FFEC25",
"Darcs Patch": "#8eff23",
"Dart": "#00B4AB",
"DataWeave": "#003a52",
"Debian Package Control File": "#D70751",
"DenizenScript": "#FBEE96",
"Dhall": "#dfafff",
"DIGITAL Command Language": null,
"DM": "#447265",
"DirectX 3D File": "#aace60",
"Dockerfile": "#384d54",
"Dogescript": "#cca760",
"DTrace": null,
"Dylan": "#6c616e",
"E": "#ccce35",
"eC": "#913960",
"ECL": "#8a1267",
"ECLiPSe": null,
"Eiffel": "#4d6977",
"ECLiPSe": "#001d9d",
"EJS": "#a91e50",
"EQ": "#a78649",
"Earthly": "#2af0ff",
"Easybuild": "#069406",
"Ecere Projects": "#913960",
"EditorConfig": "#fff1f2",
"Eiffel": "#4d6977",
"Elixir": "#6e4a7e",
"Elm": "#60B5CC",
"Emacs Lisp": "#c065db",
"EmberScript": "#FFF4F3",
"EQ": "#a78649",
"Erlang": "#B83998",
"Euphoria": "#FF790B",
"F#": "#b845fc",
"F*": "#572e30",
"FIGlet Font": "#FFDDBB",
"FLUX": "#88ccff",
"Factor": "#636746",
"Fancy": "#7b9db4",
"Fantom": "#14253c",
"Faust": "#c37240",
"Filebench WML": null,
"Filterscript": null,
"fish": null,
"FLUX": "#88ccff",
"Fennel": "#fff3d7",
"Filebench WML": "#F6B900",
"Fluent": "#ffcc33",
"Forth": "#341708",
"Fortran": "#4d41b1",
"Fortran Free Form": null,
"Fortran Free Form": "#4d41b1",
"FreeBasic": "#867db1",
"FreeMarker": "#0050b2",
"Frege": "#00cafe",
"Futhark": "#5f021f",
"G-code": "#D08CF2",
"Game Maker Language": "#71b417",
"GAML": "#FFC766",
"GAMS": null,
"GAP": null,
"GCC Machine Description": null,
"GDB": null,
"GAMS": "#f49a22",
"GAP": "#0000cc",
"GCC Machine Description": "#FFCFAB",
"GDScript": "#355570",
"GEDCOM": "#003058",
"GLSL": "#5686a5",
"GSC": "#FF6800",
"Game Maker Language": "#71b417",
"Gemfile.lock": "#701516",
"Genie": "#fb855d",
"Genshi": null,
"Gentoo Ebuild": null,
"Gentoo Eclass": null,
"Genshi": "#951531",
"Gentoo Ebuild": "#9400ff",
"Gentoo Eclass": "#9400ff",
"Gerber Image": "#d20b00",
"Gherkin": "#5B2063",
"GLSL": null,
"Git Attributes": "#F44D27",
"Git Config": "#F44D27",
"Gleam": "#ffaff3",
"Glyph": "#c1ac7f",
"Gnuplot": "#f0a9f0",
"Go": "#00ADD8",
"Go Checksums": "#00ADD8",
"Go Module": "#00ADD8",
"Golo": "#88562A",
"Gosu": "#82937f",
"Grace": null,
"Grace": "#615f8b",
"Gradle": "#02303a",
"Grammatical Framework": "#ff0000",
"GraphQL": "#e10098",
"Groovy": "#e69f56",
"Groovy Server Pages": null,
"Graphviz (DOT)": "#2596be",
"Groovy": "#4298b8",
"Groovy Server Pages": "#4298b8",
"HAProxy": "#106da9",
"HLSL": "#aace60",
"HTML": "#e34c26",
"HTML+ECR": "#2e1052",
"HTML+EEX": "#6e4a7e",
"HTML+ERB": "#701516",
"HTML+PHP": "#4f5d95",
"HTML+Razor": "#512be4",
"HTTP": "#005C9C",
"HXML": "#f68712",
"Hack": "#878787",
"Haml": "#ece2a9",
"Handlebars": "#f7931e",
"Harbour": "#0e60e3",
"Haskell": "#5e5086",
"Haxe": "#df7900",
"HCL": null,
"HiveQL": "#dce200",
"HLSL": null,
"HolyC": "#ffefaf",
"HTML": "#e34c26",
"Hy": "#7790B2",
"HyPhy": null,
"IDL": "#a3522f",
"Idris": "#b30000",
"IGOR Pro": "#0000cc",
"Inform 7": null,
"Inno Setup": null,
"INI": "#d1dbe0",
"Idris": "#b30000",
"Ignore List": "#000000",
"ImageJ Macro": "#99AAFF",
"Inno Setup": "#264b99",
"Io": "#a9188d",
"Ioke": "#078193",
"Isabelle": "#FEFE00",
"Isabelle ROOT": null,
"Isabelle ROOT": "#FEFE00",
"J": "#9EEDFF",
"Jasmin": null,
"Java": "#b07219",
"Java Server Pages": null,
"JavaScript": "#f1e05a",
"JavaScript+ERB": null,
"JAR Manifest": "#b07219",
"JFlex": "#DBCA00",
"Jison": null,
"Jison Lex": null,
"Jolie": "#843179",
"JSON": "#292929",
"JSON with Comments": "#292929",
"JSON5": "#267CB9",
"JSONLD": "#0c479c",
"JSONiq": "#40d47e",
"Janet": "#0886a5",
"Jasmin": "#d03600",
"Java": "#b07219",
"Java Properties": "#2A6277",
"Java Server Pages": "#2A6277",
"JavaScript": "#f1e05a",
"JavaScript+ERB": "#f1e05a",
"Jest Snapshot": "#15c213",
"Jinja": "#a52a22",
"Jison": "#56b3cb",
"Jison Lex": "#56b3cb",
"Jolie": "#843179",
"Jsonnet": "#0064bd",
"JSX": null,
"Julia": "#a270ba",
"Jupyter Notebook": "#DA5B0B",
"Kaitai Struct": "#773b37",
"Kotlin": "#F18E33",
"KRL": "#28430A",
"LabVIEW": null,
"Kaitai Struct": "#773b37",
"KakouneScript": "#6f8042",
"KiCad Layout": "#2f4aab",
"KiCad Legacy Layout": "#2f4aab",
"KiCad Schematic": "#2f4aab",
"Kotlin": "#A97BFF",
"LFE": "#4C3023",
"LLVM": "#185619",
"LOLCODE": "#cc9900",
"LSL": "#3d9970",
"LabVIEW": "#fede06",
"Lark": "#2980B9",
"Lasso": "#999999",
"Latte": "#f2a542",
"Lean": null,
"Less": "#1d365d",
"Lex": "#DBCA00",
"LFE": "#4C3023",
"LilyPond": null,
"Limbo": null,
"Literate Agda": null,
"Literate CoffeeScript": null,
"Literate Haskell": null,
"LilyPond": "#9ccc7c",
"Liquid": "#67b8de",
"Literate Agda": "#315665",
"Literate CoffeeScript": "#244776",
"Literate Haskell": "#5e5086",
"LiveScript": "#499886",
"LLVM": "#185619",
"Logos": null,
"Logtalk": null,
"LOLCODE": "#cc9900",
"Logtalk": "#295b9a",
"LookML": "#652B81",
"LoomScript": null,
"LSL": "#3d9970",
"Lua": "#000080",
"M": null,
"M4": null,
"M4Sugar": null,
"Macaulay2": "#d8ffff",
"Makefile": "#427819",
"Mako": null,
"Markdown": "#083fa1",
"Marko": "#42bff2",
"Mask": "#f97732",
"Mathematica": null,
"MATLAB": "#e16737",
"Max": "#c4a79c",
"MAXScript": "#00a6a6",
"mcfunction": "#E22837",
"Mercury": "#ff2b2b",
"Meson": "#007800",
"Metal": "#8f14e9",
"MiniD": null,
"Mirah": "#c7a938",
"mIRC Script": "#3d57c3",
"MLIR": "#5EC8DB",
"Modelica": null,
"Modula-2": null,
"Modula-3": "#223388",
"Module Management System": null,
"Monkey": null,
"Moocode": null,
"MoonScript": null,
"Motorola 68K Assembly": null,
"MQL4": "#62A8D6",
"MQL5": "#4A76B8",
"MTML": "#b7e1f4",
"MUF": null,
"mupad": null,
"Myghty": null,
"NASL": null,
"Macaulay2": "#d8ffff",
"Makefile": "#427819",
"Mako": "#7e858d",
"Markdown": "#083fa1",
"Marko": "#42bff2",
"Mask": "#f97732",
"Mathematica": "#dd1100",
"Max": "#c4a79c",
"Mercury": "#ff2b2b",
"Meson": "#007800",
"Metal": "#8f14e9",
"MiniYAML": "#ff1111",
"Mint": "#02b046",
"Mirah": "#c7a938",
"Modelica": "#de1d31",
"Modula-2": "#10253f",
"Modula-3": "#223388",
"MoonScript": "#ff4585",
"Motoko": "#fbb03b",
"Motorola 68K Assembly": "#005daa",
"Mustache": "#724b3b",
"NCL": "#28431f",
"NPM Config": "#cb3837",
"NWScript": "#111522",
"Nearley": "#990000",
"Nemerle": "#3d3c6e",
"nesC": "#94B0C7",
"NetLinx": "#0aa0ff",
"NetLinx+ERB": "#747faa",
"NetLogo": "#ff6375",
"NewLisp": "#87AED7",
"Nextflow": "#3ac486",
"Nginx": "#009639",
"Nim": "#ffc200",
"Nit": "#009917",
"Nix": "#7e7eff",
"NSIS": null,
"Nu": "#c9df40",
"NumPy": "#9C8AF9",
"Nunjucks": "#3d8137",
"OCaml": "#3be133",
"ObjectScript": "#424893",
"Objective-C": "#438eff",
"Objective-C++": "#6866fb",
"Objective-J": "#ff0c5a",
"ObjectScript": "#424893",
"OCaml": "#3be133",
"Odin": "#60AFFE",
"Omgrofl": "#cabbff",
"ooc": "#b0b77e",
"Opa": null,
"Opal": "#f7ede0",
"Open Policy Agent": null,
"OpenCL": null,
"OpenEdge ABL": null,
"Open Policy Agent": "#7d9199",
"OpenCL": "#ed2e2d",
"OpenEdge ABL": "#5ce600",
"OpenQASM": "#AA70FF",
"OpenRC runscript": null,
"OpenSCAD": null,
"Ox": null,
"OpenSCAD": "#e5cd45",
"Org": "#77aa99",
"Oxygene": "#cdd0e3",
"Oz": "#fab738",
"P4": "#7055b5",
"PEG.js": "#234d6b",
"PHP": "#4F5D95",
"PLSQL": "#dad8d8",
"PLpgSQL": "#336790",
"POV-Ray SDL": "#6bac65",
"Pan": "#cc0000",
"Papyrus": "#6600cc",
"Parrot": "#f3ca0a",
"Parrot Assembly": null,
"Parrot Internal Representation": null,
"Pascal": "#E3F171",
"Pawn": "#dbb284",
"Pep8": "#C76F5B",
"Perl": "#0298c3",
"PHP": "#4F5D95",
"PicoLisp": null,
"PicoLisp": "#6067af",
"PigLatin": "#fcd7de",
"Pike": "#005390",
"PLpgSQL": null,
"PLSQL": "#dad8d8",
"PogoScript": "#d80074",
"Pony": null,
"PostCSS": "#dc3a0c",
"PostScript": "#da291c",
"POV-Ray SDL": null,
"PowerBuilder": "#8f0f8d",
"PowerShell": "#012456",
"Prisma": "#0c344b",
"Processing": "#0096D8",
"Procfile": "#3B2F63",
"Prolog": "#74283c",
"Promela": "#de0000",
"Propeller Spin": "#7fa2a7",
"Pug": "#a86454",
"Puppet": "#302B6D",
"PureBasic": "#5a6986",
"PureScript": "#1D222D",
"Python": "#3572A5",
"Python console": null,
"q": "#0040cd",
"Python console": "#3572A5",
"Python traceback": "#3572A5",
"Q#": "#fed659",
"QMake": null,
"QML": "#44a51c",
"Qt Script": "#00b841",
"Quake": "#882233",
"R": "#198CE7",
"RAML": "#77d9fb",
"RDoc": "#701516",
"REXX": "#d90e09",
"RMarkdown": "#198ce7",
"RPGLE": "#2BDE21",
"RUNOFF": "#665a4e",
"Racket": "#3c5caa",
"Ragel": "#9d5200",
"Raku": "#0000fb",
"RAML": "#77d9fb",
"Rascal": "#fffaa0",
"REALbasic": null,
"ReScript": "#ed5051",
"Reason": "#ff5847",
"Rebol": "#358a5b",
"Record Jar": "#0673ba",
"Red": "#f50000",
"Redcode": null,
"Regular Expression": "#009a00",
"Ren'Py": "#ff7f7f",
"RenderScript": null,
"REXX": null,
"Ring": "#2D54CB",
"Riot": "#A71E49",
"RobotFramework": null,
"RobotFramework": "#00c0b5",
"Roff": "#ecdebe",
"Roff Manpage": "#ecdebe",
"Rouge": "#cc0088",
"RPC": null,
"Ruby": "#701516",
"RUNOFF": "#665a4e",
"Rust": "#dea584",
"Sage": null,
"SaltStack": "#646464",
"SAS": "#B34936",
"SCSS": "#c6538c",
"SPARQL": "#0C4597",
"SQF": "#3F3F3F",
"SQL": "#e38c00",
"SQLPL": "#e38c00",
"SRecode Template": "#348a34",
"SVG": "#ff9900",
"SaltStack": "#646464",
"Sass": "#a53b70",
"Scala": "#c22d40",
"Scaml": "#bd181a",
"Scheme": "#1e4aec",
"Scilab": null,
"SCSS": "#c6538c",
"sed": "#64b970",
"Scilab": "#ca0f21",
"Self": "#0579aa",
"ShaderLab": null,
"ShaderLab": "#222c37",
"Shell": "#89e051",
"ShellSession": null,
"ShellCheck Config": "#cecfcb",
"Shen": "#120F14",
"Sieve": null,
"Singularity": "#64E6AD",
"Slash": "#007eff",
"Slice": "#003fa2",
"Slim": "#2b2b2b",
"Smali": null,
"Smalltalk": "#596706",
"Smarty": null,
"SmPL": "#c94949",
"SMT": null,
"Smalltalk": "#596706",
"Smarty": "#f0c040",
"Solidity": "#AA6746",
"SourcePawn": "#f69e1d",
"SQF": "#3F3F3F",
"SQLPL": null,
"Squirrel": "#800000",
"SRecode Template": "#348a34",
"Stan": "#b2011d",
"Standard ML": "#dc566d",
"Starlark": "#76d275",
"Stata": null,
"Stata": "#1a5f91",
"StringTemplate": "#3fb34f",
"Stylus": "#ff6347",
"SubRip Text": "#9e0101",
"SugarSS": "#2fcc9f",
"SuperCollider": "#46390b",
"Svelte": "#ff3e00",
"SVG": "#ff9900",
"Swift": "#ffac45",
"SWIG": null,
"Swift": "#F05138",
"SystemVerilog": "#DAE1C2",
"Tcl": "#e4cc98",
"Tcsh": null,
"Terra": "#00004c",
"TeX": "#3D6117",
"Thrift": null,
"TI Program": "#A0AA87",
"TLA": null,
"TSQL": null,
"TSX": null,
"TLA": "#4b0079",
"TOML": "#9c4221",
"TSQL": "#e38c00",
"TSV": "#237346",
"TSX": "#2b7489",
"TXL": "#0178b8",
"Tcl": "#e4cc98",
"TeX": "#3D6117",
"Terra": "#00004c",
"TextMate Properties": "#df66e4",
"Textile": "#ffe7ac",
"Thrift": "#D12127",
"Turing": "#cf142b",
"Twig": "#c1d026",
"TXL": null,
"TypeScript": "#2b7489",
"Unified Parallel C": "#4e3617",
"Unix Assembly": null,
"Unity3D Asset": "#222c37",
"Uno": "#9933cc",
"UnrealScript": "#a54c4d",
"UrWeb": null,
"UrWeb": "#ccccee",
"V": "#4f87c4",
"Vala": "#fbe5cd",
"VBA": "#867db1",
"VBScript": "#15dcdc",
"VCL": "#148AA8",
"Verilog": "#b2b7f8",
"VHDL": "#adb2cb",
"Vim script": "#199f4b",
"Vala": "#fbe5cd",
"Valve Data Format": "#f26025",
"Verilog": "#b2b7f8",
"Vim Help File": "#199f4b",
"Vim Script": "#199f4b",
"Vim Snippet": "#199f4b",
"Visual Basic .NET": "#945db7",
"Volt": "#1F1F1F",
"Vue": "#2c3e50",
"wdl": "#42f1f4",
"Vue": "#41b883",
"Vyper": "#2980b9",
"Web Ontology Language": "#5b70bd",
"WebAssembly": "#04133b",
"WebIDL": null,
"wisp": "#7582D1",
"Wikitext": "#fc5757",
"Windows Registry Entries": "#52d5ff",
"Witcher Script": "#ff0000",
"Wollok": "#a23738",
"World of Warcraft Addon Data": "#f7e43f",
"X10": "#4B6BEF",
"xBase": "#403a40",
"XC": "#99DA07",
"Xojo": null,
"XProc": null,
"XML": "#0060ac",
"XML Property List": "#0060ac",
"XQuery": "#5232e7",
"XS": null,
"XSLT": "#EB8CEB",
"Xtend": null,
"Yacc": "#4B6C4B",
"Xojo": "#81bd41",
"Xonsh": "#285EEF",
"Xtend": "#24255d",
"YAML": "#cb171e",
"YARA": "#220000",
"YASnippet": "#32AB90",
"Yacc": "#4B6C4B",
"ZAP": "#0d665e",
"Zeek": null,
"ZIL": "#dc75e5",
"ZenScript": "#00BCD1",
"Zephir": "#118f9e",
"Zig": "#ec915c",
"ZIL": "#dc75e5",
"Zimpl": null
"Zimpl": "#d67711",
"eC": "#913960",
"fish": "#4aae47",
"hoon": "#00b171",
"jq": "#c7254e",
"kvlang": "#1da6e0",
"mIRC Script": "#3d57c3",
"mcfunction": "#E22837",
"mupad": "#244963",
"nanorc": "#2d004d",
"nesC": "#94B0C7",
"ooc": "#b0b77e",
"q": "#0040cd",
"reStructuredText": "#141414",
"sed": "#64b970",
"wdl": "#42f1f4",
"wisp": "#7582D1",
"xBase": "#403a40"
}
+161 -18
View File
@@ -1,7 +1,14 @@
// @ts-check
const axios = require("axios");
const wrap = require("word-wrap");
const themes = require("../../themes");
const toEmoji = require("emoji-name-map");
/**
* @param {string} message
* @param {string} secondaryMessage
* @returns {string}
*/
const renderError = (message, secondaryMessage = "") => {
return `
<svg width="495" height="120" viewBox="0 0 495 120" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -11,7 +18,7 @@ const renderError = (message, secondaryMessage = "") => {
.gray { fill: #858585 }
</style>
<rect x="0.5" y="0.5" width="494" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/>
<text x="25" y="45" class="text">Something went wrong! file an issue at https://git.io/JJmN9</text>
<text x="25" y="45" class="text">Something went wrong! file an issue at https://tiny.one/readme-stats</text>
<text data-testid="message" x="25" y="55" class="text small">
<tspan x="25" dy="18">${encodeHTML(message)}</tspan>
<tspan x="25" dy="18" class="gray">${secondaryMessage}</tspan>
@@ -20,7 +27,11 @@ const renderError = (message, secondaryMessage = "") => {
`;
};
// https://stackoverflow.com/a/48073476/10629172
/**
* @see https://stackoverflow.com/a/48073476/10629172
* @param {string} str
* @returns {string}
*/
function encodeHTML(str) {
return str
.replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => {
@@ -29,18 +40,29 @@ function encodeHTML(str) {
.replace(/\u0008/gim, "");
}
/**
* @param {number} num
*/
function kFormatter(num) {
return Math.abs(num) > 999
? Math.sign(num) * (Math.abs(num) / 1000).toFixed(1) + "k"
? Math.sign(num) * parseFloat((Math.abs(num) / 1000).toFixed(1)) + "k"
: Math.sign(num) * Math.abs(num);
}
/**
* @param {string} hexColor
* @returns {boolean}
*/
function isValidHexColor(hexColor) {
return new RegExp(
/^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{4})$/,
).test(hexColor);
}
/**
* @param {string} value
* @returns {boolean | string}
*/
function parseBoolean(value) {
if (value === "true") {
return true;
@@ -51,19 +73,37 @@ function parseBoolean(value) {
}
}
/**
* @param {string} str
*/
function parseArray(str) {
if (!str) return [];
return str.split(",");
}
/**
* @param {number} number
* @param {number} min
* @param {number} max
*/
function clampValue(number, min, max) {
// @ts-ignore
if (Number.isNaN(parseInt(number))) return min;
return Math.max(min, Math.min(number, max));
}
/**
* @param {string[]} colors
*/
function isValidGradient(colors) {
return isValidHexColor(colors[1]) && isValidHexColor(colors[2]);
}
/**
* @param {string} color
* @param {string} fallbackColor
* @returns {string | string[]}
*/
function fallbackColor(color, fallbackColor) {
let colors = color.split(",");
let gradient = null;
@@ -78,7 +118,12 @@ function fallbackColor(color, fallbackColor) {
);
}
/**
* @param {import('axios').AxiosRequestConfig['data']} data
* @param {import('axios').AxiosRequestConfig['headers']} headers
*/
function request(data, headers) {
// @ts-ignore
return axios({
url: "https://api.github.com/graphql",
method: "post",
@@ -88,34 +133,53 @@ function request(data, headers) {
}
/**
* @param {object} props
* @param {string[]} props.items
* @param {number} props.gap
* @param {number[]?=} props.sizes
* @param {"column" | "row"?=} props.direction
*
* @param {String[]} items
* @param {Number} gap
* @param {string} direction
* @returns {string[]}
*
* @description
* Auto layout utility, allows us to layout things
* vertically or horizontally with proper gaping
*/
function flexLayout({ items, gap, direction }) {
function flexLayout({ items, gap, direction, sizes = [] }) {
let lastSize = 0;
// filter() for filtering out empty strings
return items.filter(Boolean).map((item, i) => {
let transform = `translate(${gap * i}, 0)`;
const size = sizes[i] || 0;
let transform = `translate(${lastSize}, 0)`;
if (direction === "column") {
transform = `translate(0, ${gap * i})`;
transform = `translate(0, ${lastSize})`;
}
lastSize += size + gap;
return `<g transform="${transform}">${item}</g>`;
});
}
// returns theme based colors with proper overrides and defaults
/**
* @typedef {object} CardColors
* @prop {string?=} title_color
* @prop {string?=} text_color
* @prop {string?=} icon_color
* @prop {string?=} bg_color
* @prop {string?=} border_color
* @prop {keyof typeof import('../../themes')?=} fallbackTheme
* @prop {keyof typeof import('../../themes')?=} theme
*/
/**
* returns theme based colors with proper overrides and defaults
* @param {CardColors} options
*/
function getCardColors({
title_color,
text_color,
icon_color,
bg_color,
theme,
border_color,
theme,
fallbackTheme = "default",
}) {
const defaultTheme = themes[fallbackTheme];
@@ -150,12 +214,28 @@ function getCardColors({
return { titleColor, iconColor, textColor, bgColor, borderColor };
}
function wrapTextMultiline(text, width = 60, maxLines = 3) {
const wrapped = wrap(encodeHTML(text), { width })
.split("\n") // Split wrapped lines to get an array of lines
.map((line) => line.trim()); // Remove leading and trailing whitespace of each line
/**
* @param {string} text
* @param {number} width
* @param {number} maxLines
* @returns {string[]}
*/
function wrapTextMultiline(text, width = 59, maxLines = 3) {
const fullWidthComma = "";
const encoded = encodeHTML(text);
const isChinese = encoded.includes(fullWidthComma);
const lines = wrapped.slice(0, maxLines); // Only consider maxLines lines
let wrapped = [];
if (isChinese) {
wrapped = encoded.split(fullWidthComma); // Chinese full punctuation
} else {
wrapped = wrap(encoded, {
width,
}).split("\n"); // Split wrapped lines to get an array of lines
}
const lines = wrapped.map((line) => line.trim()).slice(0, maxLines); // Only consider maxLines lines
// Add "..." to the last line if the text exceeds maxLines
if (wrapped.length > maxLines) {
@@ -186,17 +266,41 @@ const SECONDARY_ERROR_MESSAGES = {
};
class CustomError extends Error {
/**
* @param {string} message
* @param {string} type
*/
constructor(message, type) {
super(message);
this.type = type;
this.secondaryMessage = SECONDARY_ERROR_MESSAGES[type] || "adsad";
this.secondaryMessage = SECONDARY_ERROR_MESSAGES[type] || type;
}
static MAX_RETRY = "MAX_RETRY";
static USER_NOT_FOUND = "USER_NOT_FOUND";
}
// https://stackoverflow.com/a/48172630/10629172
class MissingParamError extends Error {
/**
* @param {string[]} missedParams
* @param {string?=} secondaryMessage
*/
constructor(missedParams, secondaryMessage) {
const msg = `Missing params ${missedParams
.map((p) => `"${p}"`)
.join(", ")} make sure you pass the parameters in URL`;
super(msg);
this.missedParams = missedParams;
this.secondaryMessage = secondaryMessage;
}
}
/**
* @see https://stackoverflow.com/a/48172630/10629172
* @param {string} str
* @param {number} fontSize
* @returns
*/
function measureText(str, fontSize = 10) {
// prettier-ignore
const widths = [
@@ -231,6 +335,41 @@ function measureText(str, fontSize = 10) {
);
}
/** @param {string} name */
const lowercaseTrim = (name) => name.toLowerCase().trim();
/**
* @template T
* @param {Array<T>} arr
* @param {number} perChunk
* @returns {Array<T>}
*/
function chunkArray(arr, perChunk) {
return arr.reduce((resultArray, item, index) => {
const chunkIndex = Math.floor(index / perChunk);
if (!resultArray[chunkIndex]) {
resultArray[chunkIndex] = []; // start a new chunk
}
resultArray[chunkIndex].push(item);
return resultArray;
}, []);
}
/**
*
* @param {string} str
* @returns {string}
*/
function parseEmojis(str) {
if (!str) throw new Error("[parseEmoji]: str argument not provided");
return str.replace(/:\w+:/gm, (emoji) => {
return toEmoji.get(emoji) || "";
});
}
module.exports = {
renderError,
kFormatter,
@@ -248,4 +387,8 @@ module.exports = {
logger,
CONSTANTS,
CustomError,
MissingParamError,
lowercaseTrim,
chunkArray,
parseEmojis,
};
+26 -6
View File
@@ -1,6 +1,11 @@
// @ts-check
const retryer = require("../common/retryer");
const { request } = require("../common/utils");
const { request, MissingParamError } = require("../common/utils");
/**
* @param {import('Axios').AxiosRequestHeaders} variables
* @param {string} token
*/
const fetcher = (variables, token) => {
return request(
{
@@ -38,15 +43,24 @@ const fetcher = (variables, token) => {
variables,
},
{
Authorization: `bearer ${token}`,
Authorization: `token ${token}`,
},
);
};
const urlExample = "/api/pin?username=USERNAME&amp;repo=REPO_NAME";
/**
* @param {string} username
* @param {string} reponame
* @returns {Promise<import("./types").RepositoryData>}
*/
async function fetchRepo(username, reponame) {
if (!username || !reponame) {
throw new Error("Invalid username or reponame");
if (!username && !reponame) {
throw new MissingParamError(["username", "repo"], urlExample);
}
if (!username) throw new MissingParamError(["username"], urlExample);
if (!reponame) throw new MissingParamError(["repo"], urlExample);
let res = await retryer(fetcher, { login: username, repo: reponame });
@@ -63,7 +77,10 @@ async function fetchRepo(username, reponame) {
if (!data.user.repository || data.user.repository.isPrivate) {
throw new Error("User Repository Not found");
}
return data.user.repository;
return {
...data.user.repository,
starCount: data.user.repository.stargazers.totalCount,
};
}
if (isOrg) {
@@ -73,7 +90,10 @@ async function fetchRepo(username, reponame) {
) {
throw new Error("Organization Repository Not found");
}
return data.organization.repository;
return {
...data.organization.repository,
starCount: data.organization.repository.stargazers.totalCount,
};
}
}
+49 -13
View File
@@ -1,12 +1,22 @@
const axios = require("axios");
// @ts-check
const axios = require("axios").default;
const githubUsernameRegex = require("github-username-regex");
const retryer = require("../common/retryer");
const calculateRank = require("../calculateRank");
const { request, logger, CustomError } = require("../common/utils");
const {
request,
logger,
CustomError,
MissingParamError,
} = require("../common/utils");
require("dotenv").config();
/**
* @param {import('axios').AxiosRequestHeaders} variables
* @param {string} token
*/
const fetcher = (variables, token) => {
return request(
{
@@ -25,7 +35,10 @@ const fetcher = (variables, token) => {
pullRequests(first: 1) {
totalCount
}
issues(first: 1) {
openIssues: issues(states: OPEN) {
totalCount
}
closedIssues: issues(states: CLOSED) {
totalCount
}
followers {
@@ -34,6 +47,7 @@ const fetcher = (variables, token) => {
repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}) {
totalCount
nodes {
name
stargazers {
totalCount
}
@@ -66,30 +80,38 @@ const totalCommitsFetcher = async (username) => {
headers: {
"Content-Type": "application/json",
Accept: "application/vnd.github.cloak-preview",
Authorization: `bearer ${token}`,
Authorization: `token ${token}`,
},
});
};
try {
let res = await retryer(fetchTotalCommits, { login: username });
if (res.data.total_count) {
let total_count = res.data.total_count;
if (!!total_count && !isNaN(total_count)) {
return res.data.total_count;
}
} catch (err) {
logger.log(err);
// just return 0 if there is something wrong so that
// we don't break the whole app
return 0;
}
// just return 0 if there is something wrong so that
// we don't break the whole app
return 0;
};
/**
* @param {string} username
* @param {boolean} count_private
* @param {boolean} include_all_commits
* @returns {Promise<import("./types").StatsData>}
*/
async function fetchStats(
username,
count_private = false,
include_all_commits = false,
exclude_repo = [],
) {
if (!username) throw Error("Invalid username");
if (!username) throw new MissingParamError(["username"]);
const stats = {
name: "",
@@ -113,8 +135,17 @@ async function fetchStats(
const user = res.data.data.user;
// populate repoToHide map for quick lookup
// while filtering out
let repoToHide = {};
if (exclude_repo) {
exclude_repo.forEach((repoName) => {
repoToHide[repoName] = true;
});
}
stats.name = user.name || user.login;
stats.totalIssues = user.issues.totalCount;
stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount;
// normal commits
stats.totalCommits = user.contributionsCollection.totalCommitContributions;
@@ -134,9 +165,14 @@ async function fetchStats(
stats.totalPRs = user.pullRequests.totalCount;
stats.contributedTo = user.repositoriesContributedTo.totalCount;
stats.totalStars = user.repositories.nodes.reduce((prev, curr) => {
return prev + curr.stargazers.totalCount;
}, 0);
// Retrieve stars while filtering out repositories to be hidden
stats.totalStars = user.repositories.nodes
.filter((data) => {
return !repoToHide[data.name];
})
.reduce((prev, curr) => {
return prev + curr.stargazers.totalCount;
}, 0);
stats.rank = calculateRank({
totalCommits: stats.totalCommits,
+15 -9
View File
@@ -1,7 +1,12 @@
const { request, logger } = require("../common/utils");
// @ts-check
const { request, logger, MissingParamError } = require("../common/utils");
const retryer = require("../common/retryer");
require("dotenv").config();
/**
* @param {import('Axios').AxiosRequestHeaders} variables
* @param {string} token
*/
const fetcher = (variables, token) => {
return request(
{
@@ -29,13 +34,18 @@ const fetcher = (variables, token) => {
variables,
},
{
Authorization: `bearer ${token}`,
Authorization: `token ${token}`,
},
);
};
/**
* @param {string} username
* @param {string[]} exclude_repo
* @returns {Promise<import("./types").TopLangData>}
*/
async function fetchTopLanguages(username, exclude_repo = []) {
if (!username) throw Error("Invalid username");
if (!username) throw new MissingParamError(["username"]);
const res = await retryer(fetcher, { login: username });
@@ -58,14 +68,10 @@ async function fetchTopLanguages(username, exclude_repo = []) {
// filter out repositories to be hidden
repoNodes = repoNodes
.sort((a, b) => b.size - a.size)
.filter((name) => {
return !repoToHide[name.name];
});
.filter((name) => !repoToHide[name.name]);
repoNodes = repoNodes
.filter((node) => {
return node.languages.edges.length > 0;
})
.filter((node) => node.languages.edges.length > 0)
// flatten the list of language nodes
.reduce((acc, curr) => curr.languages.edges.concat(acc), [])
.reduce((acc, prev) => {
+104
View File
@@ -0,0 +1,104 @@
export type RepositoryData = {
name: string;
nameWithOwner: string;
isPrivate: boolean;
isArchived: boolean;
isTemplate: boolean;
stargazers: { totalCount: number };
description: string;
primaryLanguage: {
color: string;
id: string;
name: string;
};
forkCount: number;
starCount: number;
};
export type StatsData = {
name: string;
totalPRs: number;
totalCommits: number;
totalIssues: number;
totalStars: number;
contributedTo: number;
rank: { level: string; score: number };
};
export type Lang = {
name: string;
color: string;
size: number;
};
export type TopLangData = Record<string, Lang>;
export type WakaTimeData = {
categories: {
digital: string;
hours: number;
minutes: number;
name: string;
percent: number;
text: string;
total_seconds: number;
}[];
daily_average: number;
daily_average_including_other_language: number;
days_including_holidays: number;
days_minus_holidays: number;
editors: {
digital: string;
hours: number;
minutes: number;
name: string;
percent: number;
text: string;
total_seconds: number;
}[];
holidays: number;
human_readable_daily_average: string;
human_readable_daily_average_including_other_language: string;
human_readable_total: string;
human_readable_total_including_other_language: string;
id: string;
is_already_updating: boolean;
is_coding_activity_visible: boolean;
is_including_today: boolean;
is_other_usage_visible: boolean;
is_stuck: boolean;
is_up_to_date: boolean;
languages: {
digital: string;
hours: number;
minutes: number;
name: string;
percent: number;
text: string;
total_seconds: number;
}[];
operating_systems: {
digital: string;
hours: number;
minutes: number;
name: string;
percent: number;
text: string;
total_seconds: number;
}[];
percent_calculated: number;
range: string;
status: string;
timeout: number;
total_seconds: number;
total_seconds_including_other_language: number;
user_id: string;
username: string;
writes_only: boolean;
};
export type WakaTimeLang = {
name: string;
text: string;
percent: number;
};
+9 -2
View File
@@ -1,11 +1,18 @@
const axios = require("axios");
const { MissingParamError } = require("../common/utils");
/**
* @param {{username: string, api_domain: string, range: string}} props
* @returns {Promise<WakaTimeData>}
*/
const fetchWakatimeStats = async ({ username, api_domain, range }) => {
if (!username) throw new MissingParamError(["username"]);
try {
const { data } = await axios.get(
`https://${
api_domain ? api_domain.replace(/[^a-z-.0-9]/gi, "") : "wakatime.com"
}/api/v1/users/${username}/stats/${range || ''}?is_including_today=true`,
api_domain ? api_domain.replace(/\/$/gi, "") : "wakatime.com"
}/api/v1/users/${username}/stats/${range || ""}?is_including_today=true`,
);
return data.data;
+26 -4
View File
@@ -1,14 +1,22 @@
// @ts-check
/**
* @param {number} value
*/
const calculateCircleProgress = (value) => {
let radius = 40;
let c = Math.PI * (radius * 2);
const radius = 40;
const c = Math.PI * (radius * 2);
if (value < 0) value = 0;
if (value > 100) value = 100;
let percentage = ((100 - value) / 100) * c;
return percentage;
return ((100 - value) / 100) * c;
};
/**
*
* @param {{progress: number}} param0
* @returns
*/
const getProgressAnimation = ({ progress }) => {
return `
@keyframes rankAnimation {
@@ -44,6 +52,15 @@ const getAnimations = () => {
`;
};
/**
* @param {{
* titleColor?: string | string[]
* textColor?: string | string[]
* iconColor?: string | string[]
* show_icons?: boolean;
* progress?: number;
* }} args
*/
const getStyles = ({
titleColor,
textColor,
@@ -55,6 +72,10 @@ const getStyles = ({
.stat {
font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${textColor};
}
@supports(-moz-appearance: auto) {
/* Selector detects Firefox */
.stat { font-size:12px; }
}
.stagger {
opacity: 0;
animation: fadeInAnimation 0.3s ease-in-out forwards;
@@ -64,6 +85,7 @@ const getStyles = ({
animation: scaleInAnimation 0.3s ease-in-out forwards;
}
.not_bold { font-weight: 400 }
.bold { font-weight: 700 }
.icon {
fill: ${iconColor};
+77 -21
View File
@@ -6,6 +6,7 @@ const statCardLocales = ({ name, apostrophe }) => {
"statcard.title": {
ar: `${encodedName} إحصائيات غيت هاب`,
cn: `${encodedName} 的 GitHub 统计数据`,
"zh-tw": `${encodedName} 的 GitHub 統計數據`,
cs: `GitHub statistiky uživatele ${encodedName}`,
de: `${encodedName + apostrophe} GitHub-Statistiken`,
en: `${encodedName}'${apostrophe} GitHub Stats`,
@@ -16,7 +17,7 @@ const statCardLocales = ({ name, apostrophe }) => {
it: `Statistiche GitHub di ${encodedName}`,
ja: `${encodedName}の GitHub 統計`,
kr: `${encodedName}의 GitHub 통계`,
nl: `${encodedName}'${apostrophe} GitHub Statistieken`,
nl: `${encodedName}'${apostrophe} GitHub-statistieken`,
"pt-pt": `Estatísticas do GitHub de ${encodedName}`,
"pt-br": `Estatísticas do GitHub de ${encodedName}`,
np: `${encodedName}'${apostrophe} गिटहब तथ्याङ्क`,
@@ -24,25 +25,30 @@ const statCardLocales = ({ name, apostrophe }) => {
ru: `Статистика GitHub пользователя ${encodedName}`,
"uk-ua": `Статистика GitHub користувача ${encodedName}`,
id: `Statistik GitHub ${encodedName}`,
ml: `${encodedName}'${apostrophe} ഗിറ്റ്ഹബ് സ്ഥിതിവിവരക്കണക്കുകൾ`,
my: `Statistik GitHub ${encodedName}`,
sk: `GitHub štatistiky používateľa ${encodedName}`,
tr: `${encodedName} Hesabının GitHub Yıldızları`,
pl: `Statystyki GitHub użytkownika ${encodedName}`,
uz: `${encodedName}ning Github'dagi statistikasi`,
vi: `Thống Kê GitHub ${encodedName}`,
se: `GitHubstatistik för ${encodedName}`,
},
"statcard.totalstars": {
ar: "مجموع النجوم",
cn: "获标星数(star",
"zh-tw": "獲標星數(star",
cs: "Celkem hvězd",
de: "Sterne Insgesamt",
en: "Total Stars",
bn: "সর্বমোট Stars",
de: "Insgesamt erhaltene Sterne",
en: "Total Stars Earned",
bn: "সর্বমোট Star",
es: "Estrellas totales",
fr: "Total d'étoiles",
hu: "Csillagok",
it: "Stelle totali",
ja: "スターされた数",
kr: "받은 스타 수",
nl: "Totale Sterren",
nl: "Totaal Sterren Ontvangen",
"pt-pt": "Total de estrelas",
"pt-br": "Total de estrelas",
np: "कुल ताराहरू",
@@ -50,25 +56,30 @@ const statCardLocales = ({ name, apostrophe }) => {
ru: "Всего звезд",
"uk-ua": "Всього зірок",
id: "Total Bintang",
ml: "ആകെ നക്ഷത്രങ്ങൾ",
my: "Jumlah Bintang",
sk: "Hviezdy",
tr: "Toplam Yıldız",
pl: "Liczba Gwiazdek",
pl: "Liczba Gwiazdek dostanych",
uz: "Yulduzchalar",
vi: "Tổng Số Sao",
se: "Antal intjänade stjärnor",
},
"statcard.commits": {
ar: "مجموع الحفظ",
cn: "累计提交数(commit",
"zh-tw": "累計提交數(commit",
cs: "Celkem commitů",
de: "Anzahl Commits",
en: "Total Commits",
bn: "সর্বমোট Commits",
bn: "সর্বমোট Commit",
es: "Commits totales",
fr: "Total des validations",
fr: "Total des Commits",
hu: "Összes commit",
it: "Commit totali",
ja: "合計コミット数",
kr: "전체 커밋 수",
nl: "Totale Commits",
nl: "Aantal commits",
"pt-pt": "Total de Commits",
"pt-br": "Total de Commits",
np: "कुल Commits",
@@ -76,25 +87,30 @@ const statCardLocales = ({ name, apostrophe }) => {
ru: "Всего коммитов",
"uk-ua": "Всього коммітов",
id: "Total Komitmen",
ml: "ആകെ കമ്മിറ്റുകൾ",
my: "Jumlah Komitmen",
sk: "Všetky commity",
tr: "Toplam Commit",
pl: "Wszystkie commity",
uz: "'Commit'lar",
vi: "Tổng Số Cam Kết",
se: "Totalt antal commits",
},
"statcard.prs": {
ar: "مجموع طلبات السحب",
cn: "拉取请求数(PR",
"zh-tw": "拉取請求數(PR",
cs: "Celkem PRs",
de: "PRs Insgesamt",
en: "Total PRs",
bn: "সর্বমোট PRs",
bn: "সর্বমোট PR",
es: "PRs totales",
fr: "Total des PR",
fr: "Total des PRs",
hu: "Összes PR",
it: "PR totali",
ja: "合計 PR",
kr: "PR 횟수",
nl: "Totale PR's",
nl: "Aantal PR's",
"pt-pt": "Total de PRs",
"pt-br": "Total de PRs",
np: "कुल PRs",
@@ -102,25 +118,30 @@ const statCardLocales = ({ name, apostrophe }) => {
ru: "Всего pull request`ов",
"uk-ua": "Всього pull request`iв",
id: "Total Permintaan Tarik",
ml: "ആകെ പുൾ അഭ്യർത്ഥനകൾ",
my: "Jumlah PR",
sk: "Všetky PR",
tr: "Toplam PR",
pl: "Wszystkie PR",
pl: "Wszystkie PR-y",
uz: "'Pull Request'lar",
vi: "Tổng Số PR",
se: "Totalt antal PR",
},
"statcard.issues": {
ar: "مجموع التحسينات",
cn: "指出问题数(issue",
"zh-tw": "指出問題數(issue",
cs: "Celkem problémů",
de: "Anzahl Issues",
en: "Total Issues",
bn: "সর্বমোট Issues",
bn: "সর্বমোট Issue",
es: "Issues totales",
fr: "Nombre total d'incidents",
hu: "Összes hibajegy",
it: "Segnalazioni totali",
ja: "合計 issue",
kr: "이슈 개수",
nl: "Totale Issues",
nl: "Aantal kwesties",
"pt-pt": "Total de Issues",
"pt-br": "Total de Issues",
np: "कुल मुद्दाहरू",
@@ -128,14 +149,19 @@ const statCardLocales = ({ name, apostrophe }) => {
ru: "Всего issue",
"uk-ua": "Всього issue",
id: "Total Masalah Dilaporkan",
ml: "ആകെ ലക്കങ്ങൾ",
my: "Jumlah Isu Dilaporkan",
sk: "Všetky problémy",
tr: "Toplam Hata",
pl: "Wszystkie problemy",
pl: "Wszystkie Issues",
uz: "'Issue'lar",
vi: "Tổng Số Vấn Đề",
se: "Total antal issues",
},
"statcard.contribs": {
ar: "ساهم في",
cn: "参与项目数",
"zh-tw": "參與項目數",
cs: "Přispěl k",
de: "Beigetragen zu",
en: "Contributed to",
@@ -144,7 +170,7 @@ const statCardLocales = ({ name, apostrophe }) => {
fr: "Contribué à",
hu: "Hozzájárulások",
it: "Ha contribuito a",
ja: "コントリビュートしたリポジトリ",
ja: "貢献したリポジトリ",
kr: "전체 기여도",
nl: "Bijgedragen aan",
"pt-pt": "Contribuiu em",
@@ -154,10 +180,14 @@ const statCardLocales = ({ name, apostrophe }) => {
ru: "Внёс вклад в",
"uk-ua": "Вніс внесок у",
id: "Berkontribusi ke",
ml: "സമർപ്പിച്ചിരിക്കുന്നത്",
my: "Menyumbang kepada",
sk: "Účasti",
tr: "Katkı Verildi",
pl: "Udziały",
pl: "Kontrybucje",
uz: "Hissa qoʻshgan",
vi: "Đã Đóng Góp",
se: "Bidragit till",
},
};
};
@@ -165,11 +195,13 @@ const statCardLocales = ({ name, apostrophe }) => {
const repoCardLocales = {
"repocard.template": {
ar: "قالب",
bn: "টেমপ্লেট",
cn: "模板",
"zh-tw": "模板",
cs: "Šablona",
de: "Vorlage",
en: "Template",
es: "Planitlla",
es: "Plantilla",
fr: "Modèle",
hu: "Sablon",
it: "Template",
@@ -183,14 +215,19 @@ const repoCardLocales = {
ru: "Шаблон",
"uk-ua": "Шаблон",
id: "Pola",
ml: "ടെംപ്ലേറ്റ്",
my: "Templat",
sk: "Šablóna",
tr: "Şablon",
pl: "Szablony",
vi: "Mẫu",
se: "Mall",
},
"repocard.archived": {
ar: "محفوظ",
bn: "আর্কাইভড",
cn: "已归档",
"zh-tw": "已歸檔",
cs: "Archivováno",
de: "Archiviert",
en: "Archived",
@@ -208,10 +245,13 @@ const repoCardLocales = {
ru: "Архивирован",
"uk-ua": "Архивирован",
id: "Arsip",
ml: "ശേഖരിച്ചത്",
my: "Arkib",
sk: "Archivované",
tr: "Arşiv",
pl: "Zarchiwizowano",
vi: "Đã Lưu Trữ",
se: "Arkiverade",
},
};
@@ -219,8 +259,10 @@ const langCardLocales = {
"langcard.title": {
ar: "أكثر اللغات إستخداماً",
cn: "最常用的语言",
"zh-tw": "最常用的語言",
cs: "Nejpoužívanější jazyky",
de: "Meist verwendete Sprachen",
bn: "সর্বাধিক ব্যবহৃত ভাষা সমূহ",
en: "Most Used Languages",
es: "Lenguajes más usados",
fr: "Langages les plus utilisés",
@@ -236,10 +278,13 @@ const langCardLocales = {
ru: "Наиболее часто используемые языки",
"uk-ua": "Найбільш часто використовувані мови",
id: "Bahasa Yang Paling Banyak Digunakan",
ml: "കൂടുതൽ ഉപയോഗിച്ച ഭാഷകൾ",
my: "Bahasa Paling Digunakan",
sk: "Najviac používané jazyky",
tr: "En Çok Kullanılan Diller",
pl: "Najczęściej używane języki",
vi: "Ngôn Ngữ Thường Sử Dụng",
se: "Mest använda språken",
},
};
@@ -247,16 +292,18 @@ const wakatimeCardLocales = {
"wakatimecard.title": {
ar: "إحصائيات واكا تايم",
cn: "Wakatime 周统计",
"zh-tw": "Wakatime 周統計",
cs: "Statistiky Wakatime",
de: "Wakatime Status",
en: "Wakatime Stats",
bn: "Wakatime স্ট্যাটাস",
es: "Estadísticas de Wakatime",
fr: "Statistiques de Wakatime",
hu: "Wakatime statisztika",
it: "Statistiche Wakatime",
ja: "Wakatime ワカタイム統計",
kr: "Wakatime 주간 통계",
nl: "Takwimu za Wakatime",
nl: "Wakatime-statistieken",
"pt-pt": "Estatísticas Wakatime",
"pt-br": "Estatísticas Wakatime",
np: "Wakatime तथ्या .्क",
@@ -264,24 +311,29 @@ const wakatimeCardLocales = {
ru: "Статистика Wakatime",
"uk-ua": "Статистика Wakatime",
id: "Status Wakatime",
ml: "വേക്ക് ടൈം സ്ഥിതിവിവരക്കണക്കുകൾ",
my: "Statistik Wakatime",
sk: "Wakatime štatistika",
tr: "Waketime İstatistikler",
pl: "statystyki Wakatime",
vi: "Thống Kê Wakatime",
se: "Wakatime statistik",
},
"wakatimecard.nocodingactivity": {
ar: "لا يوجد نشاط برمجي لهذا الأسبوع",
cn: "本周没有编程活动",
"zh-tw": "本周沒有編程活動",
cs: "Tento týden žádná aktivita v kódování",
de: "Keine Aktivitäten in dieser Woche",
en: "No coding activity this week",
bn: "এই সপ্তাহে কোন কোডিং অ্যাক্টিভিটি নেই",
es: "No hay actividad de codificación esta semana",
fr: "Aucune activité de codage cette semaine",
hu: "Nem volt aktivitás ezen a héten",
it: "Nessuna attività in questa settimana",
ja: "今週のコーディング活動はありません",
kr: "이번 주 작업내역 없음",
nl: "Geen coderings activiet deze week",
nl: "Geen programmeeractiviteit deze week",
"pt-pt": "Sem atividade esta semana",
"pt-br": "Nenhuma atividade de codificação esta semana",
np: "यस हप्ता कुनै कोडिंग गतिविधि छैन",
@@ -289,10 +341,14 @@ const wakatimeCardLocales = {
ru: "На этой неделе не было активности",
"uk-ua": "На цьому тижні не було активності",
id: "Tidak ada aktivitas perkodingan minggu ini",
ml: "ഈ ആഴ്ച കോഡിംഗ് പ്രവർത്തനങ്ങളൊന്നുമില്ല",
my: "Tiada aktiviti pengekodan minggu ini",
sk: "Žiadna kódovacia aktivita tento týždeň",
tr: "Bu hafta herhangi bir kod yazma aktivitesi olmadı",
pl: "Brak aktywności w tym tygodniu",
uz: "Bu hafta faol bo'lmadi",
vi: "Không Có Hoạt Động Trong Tuần Này",
se: "Ingen aktivitet denna vecka",
},
};
@@ -8,27 +8,40 @@ exports[`Test Render Wakatime Card should render correctly 1`] = `
viewBox=\\"0 0 495 150\\"
fill=\\"none\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
role=\\"img\\"
aria-labelledby=\\"descId\\"
>
<title id=\\"titleId\\"></title>
<desc id=\\"descId\\"></desc>
<style>
.header {
font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif;
fill: #2f80ed;
animation: fadeInAnimation 0.8s ease-in-out forwards;
}
@supports(-moz-appearance: auto) {
/* Selector detects Firefox */
.header { font-size: 15.5px; }
}
.stat {
font: 600 14px 'Segoe UI', Ubuntu, \\"Helvetica Neue\\", Sans-Serif; fill: #333;
font: 600 14px 'Segoe UI', Ubuntu, \\"Helvetica Neue\\", Sans-Serif; fill: #434d58;
}
@supports(-moz-appearance: auto) {
/* Selector detects Firefox */
.stat { font-size:12px; }
}
.stagger {
opacity: 0;
animation: fadeInAnimation 0.3s ease-in-out forwards;
}
.rank-text {
font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: #333;
font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58;
animation: scaleInAnimation 0.3s ease-in-out forwards;
}
.not_bold { font-weight: 400 }
.bold { font-weight: 700 }
.icon {
fill: #4c71f2;
@@ -54,7 +67,7 @@ exports[`Test Render Wakatime Card should render correctly 1`] = `
}
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: #333 }
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58 }
@@ -99,16 +112,15 @@ exports[`Test Render Wakatime Card should render correctly 1`] = `
<svg x=\\"0\\" y=\\"0\\" width=\\"100%\\">
<g transform=\\"translate(0, 0)\\">
<g class=\\"stagger\\" style=\\"animation-delay: NaNms\\" transform=\\"translate(25, 0)\\">
<text class=\\"stat bold\\" y=\\"12.5\\">Other:</text>
<text class=\\"stat bold\\" y=\\"12.5\\" data-testid=\\"Other\\">Other:</text>
<text
class=\\"stat\\"
x=\\"350\\"
y=\\"12.5\\"
data-testid=\\"Other\\"
>19 mins</text>
<svg width=\\"220\\" x=\\"110\\" y=\\"4\\">
<rect rx=\\"5\\" ry=\\"5\\" x=\\"0\\" y=\\"0\\" width=\\"220\\" height=\\"8\\" fill=\\"#333\\"></rect>
<rect rx=\\"5\\" ry=\\"5\\" x=\\"0\\" y=\\"0\\" width=\\"220\\" height=\\"8\\" fill=\\"#434d58\\"></rect>
<rect
height=\\"8\\"
fill=\\"#2f80ed\\"
@@ -122,16 +134,15 @@ exports[`Test Render Wakatime Card should render correctly 1`] = `
</g>
</g><g transform=\\"translate(0, 25)\\">
<g class=\\"stagger\\" style=\\"animation-delay: NaNms\\" transform=\\"translate(25, 0)\\">
<text class=\\"stat bold\\" y=\\"12.5\\">TypeScript:</text>
<text class=\\"stat bold\\" y=\\"12.5\\" data-testid=\\"TypeScript\\">TypeScript:</text>
<text
class=\\"stat\\"
x=\\"350\\"
y=\\"12.5\\"
data-testid=\\"TypeScript\\"
>1 min</text>
<svg width=\\"220\\" x=\\"110\\" y=\\"4\\">
<rect rx=\\"5\\" ry=\\"5\\" x=\\"0\\" y=\\"0\\" width=\\"220\\" height=\\"8\\" fill=\\"#333\\"></rect>
<rect rx=\\"5\\" ry=\\"5\\" x=\\"0\\" y=\\"0\\" width=\\"220\\" height=\\"8\\" fill=\\"#434d58\\"></rect>
<rect
height=\\"8\\"
fill=\\"#2f80ed\\"
@@ -159,27 +170,40 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1
viewBox=\\"0 0 495 115\\"
fill=\\"none\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
role=\\"img\\"
aria-labelledby=\\"descId\\"
>
<title id=\\"titleId\\"></title>
<desc id=\\"descId\\"></desc>
<style>
.header {
font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif;
fill: #2f80ed;
animation: fadeInAnimation 0.8s ease-in-out forwards;
}
@supports(-moz-appearance: auto) {
/* Selector detects Firefox */
.header { font-size: 15.5px; }
}
.stat {
font: 600 14px 'Segoe UI', Ubuntu, \\"Helvetica Neue\\", Sans-Serif; fill: #333;
font: 600 14px 'Segoe UI', Ubuntu, \\"Helvetica Neue\\", Sans-Serif; fill: #434d58;
}
@supports(-moz-appearance: auto) {
/* Selector detects Firefox */
.stat { font-size:12px; }
}
.stagger {
opacity: 0;
animation: fadeInAnimation 0.3s ease-in-out forwards;
}
.rank-text {
font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: #333;
font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58;
animation: scaleInAnimation 0.3s ease-in-out forwards;
}
.not_bold { font-weight: 400 }
.bold { font-weight: 700 }
.icon {
fill: #4c71f2;
@@ -205,7 +229,7 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1
}
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: #333 }
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58 }
+7 -6
View File
@@ -35,7 +35,8 @@ const data = {
restrictedContributionsCount: 100,
},
pullRequests: { totalCount: stats.totalPRs },
issues: { totalCount: stats.totalIssues },
openIssues: { totalCount: stats.totalIssues },
closedIssues: { totalCount: 0 },
followers: { totalCount: 0 },
repositories: {
totalCount: 1,
@@ -143,17 +144,17 @@ describe("Test /api/", () => {
expect(res.setHeader.mock.calls).toEqual([
["Content-Type", "image/svg+xml"],
["Cache-Control", `public, max-age=${CONSTANTS.TWO_HOURS}`],
["Cache-Control", `public, max-age=${CONSTANTS.FOUR_HOURS}`],
]);
});
it("should set proper cache", async () => {
const { req, res } = faker({ cache_seconds: 8000 }, data);
const { req, res } = faker({ cache_seconds: 15000 }, data);
await api(req, res);
expect(res.setHeader.mock.calls).toEqual([
["Content-Type", "image/svg+xml"],
["Cache-Control", `public, max-age=${8000}`],
["Cache-Control", `public, max-age=${15000}`],
]);
});
@@ -175,7 +176,7 @@ describe("Test /api/", () => {
expect(res.setHeader.mock.calls).toEqual([
["Content-Type", "image/svg+xml"],
["Cache-Control", `public, max-age=${CONSTANTS.TWO_HOURS}`],
["Cache-Control", `public, max-age=${CONSTANTS.FOUR_HOURS}`],
]);
}
@@ -185,7 +186,7 @@ describe("Test /api/", () => {
expect(res.setHeader.mock.calls).toEqual([
["Content-Type", "image/svg+xml"],
["Cache-Control", `public, max-age=${CONSTANTS.TWO_HOURS}`],
["Cache-Control", `public, max-age=${CONSTANTS.FOUR_HOURS}`],
]);
}
});
+7 -7
View File
@@ -1,5 +1,5 @@
require("@testing-library/jest-dom");
const cssToObject = require("css-to-object");
const cssToObject = require("@uppercod/css-to-object").cssToObject;
const Card = require("../src/common/Card");
const icons = require("../src/common/icons");
const { getCardColors } = require("../src/common/utils");
@@ -137,9 +137,9 @@ describe("Card", () => {
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.innerHTML);
const headerClassStyles = stylesObject[".header"];
const headerClassStyles = stylesObject[":host"][".header "];
expect(headerClassStyles.fill).toBe("#f00");
expect(headerClassStyles["fill"].trim()).toBe("#f00");
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
"#fff",
@@ -168,18 +168,18 @@ describe("Card", () => {
"fill",
"url(#gradient)",
);
expect(document.querySelector("defs linearGradient")).toHaveAttribute(
expect(document.querySelector("defs #gradient")).toHaveAttribute(
"gradientTransform",
"rotate(90)",
);
expect(
document.querySelector("defs linearGradient stop:nth-child(1)"),
document.querySelector("defs #gradient stop:nth-child(1)"),
).toHaveAttribute("stop-color", "#fff");
expect(
document.querySelector("defs linearGradient stop:nth-child(2)"),
document.querySelector("defs #gradient stop:nth-child(2)"),
).toHaveAttribute("stop-color", "#000");
expect(
document.querySelector("defs linearGradient stop:nth-child(3)"),
document.querySelector("defs #gradient stop:nth-child(3)"),
).toHaveAttribute("stop-color", "#f00");
});
});
+11 -4
View File
@@ -19,14 +19,14 @@ const data_repo = {
const data_user = {
data: {
user: { repository: data_repo },
user: { repository: data_repo.repository },
organization: null,
},
};
const data_org = {
data: {
user: null,
organization: { repository: data_repo },
organization: { repository: data_repo.repository },
},
};
@@ -41,14 +41,21 @@ describe("Test fetchRepo", () => {
mock.onPost("https://api.github.com/graphql").reply(200, data_user);
let repo = await fetchRepo("anuraghazra", "convoychat");
expect(repo).toStrictEqual(data_repo);
expect(repo).toStrictEqual({
...data_repo.repository,
starCount: data_repo.repository.stargazers.totalCount,
});
});
it("should fetch correct org repo", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data_org);
let repo = await fetchRepo("anuraghazra", "convoychat");
expect(repo).toStrictEqual(data_repo);
expect(repo).toStrictEqual({
...data_repo.repository,
starCount: data_repo.repository.stargazers.totalCount,
});
});
it("should throw error if user is found but repo is null", async () => {
+40 -6
View File
@@ -14,16 +14,17 @@ const data = {
restrictedContributionsCount: 50,
},
pullRequests: { totalCount: 300 },
issues: { totalCount: 200 },
openIssues: { totalCount: 100 },
closedIssues: { totalCount: 100 },
followers: { totalCount: 100 },
repositories: {
totalCount: 5,
nodes: [
{ stargazers: { totalCount: 100 } },
{ stargazers: { totalCount: 100 } },
{ stargazers: { totalCount: 100 } },
{ stargazers: { totalCount: 50 } },
{ stargazers: { totalCount: 50 } },
{ name: "test-repo-1", stargazers: { totalCount: 100 } },
{ name: "test-repo-2", stargazers: { totalCount: 100 } },
{ name: "test-repo-3", stargazers: { totalCount: 100 } },
{ name: "test-repo-4", stargazers: { totalCount: 50 } },
{ name: "test-repo-5", stargazers: { totalCount: 50 } },
],
},
},
@@ -133,4 +134,37 @@ describe("Test fetchStats", () => {
rank,
});
});
it("should exclude stars of the `test-repo-1` repository", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data);
mock
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
.reply(200, { total_count: 1000 });
let stats = await fetchStats(
"anuraghazra",
true,
true,
(exclude_repo = ["test-repo-1"]),
);
const rank = calculateRank({
totalCommits: 1050,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 300,
prs: 300,
issues: 200,
});
expect(stats).toStrictEqual({
contributedTo: 61,
name: "Anurag Hazra",
totalCommits: 1050,
totalIssues: 200,
totalPRs: 300,
totalStars: 300,
rank,
});
});
});
+25
View File
@@ -15,16 +15,19 @@ const data_langs = {
repositories: {
nodes: [
{
name: "test-repo-1",
languages: {
edges: [{ size: 100, node: { color: "#0f0", name: "HTML" } }],
},
},
{
name: "test-repo-2",
languages: {
edges: [{ size: 100, node: { color: "#0f0", name: "HTML" } }],
},
},
{
name: "test-repo-3",
languages: {
edges: [
{ size: 100, node: { color: "#0ff", name: "javascript" } },
@@ -32,6 +35,7 @@ const data_langs = {
},
},
{
name: "test-repo-4",
languages: {
edges: [
{ size: 100, node: { color: "#0ff", name: "javascript" } },
@@ -74,6 +78,27 @@ describe("FetchTopLanguages", () => {
});
});
it("should fetch correct language data while excluding the 'test-repo-1' repository", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
let repo = await fetchTopLanguages(
"anuraghazra",
(exclude_repo = ["test-repo-1"]),
);
expect(repo).toStrictEqual({
HTML: {
color: "#0f0",
name: "HTML",
size: 100,
},
javascript: {
color: "#0ff",
name: "javascript",
size: 200,
},
});
});
it("should throw error", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, error);
+1 -1
View File
@@ -207,7 +207,7 @@ describe("Wakatime fetcher", () => {
mock.onGet(/\/https:\/\/wakatime\.com\/api/).reply(404, wakaTimeData);
await expect(fetchWakatimeStats("noone")).rejects.toThrow(
"Wakatime user not found, make sure you have a wakatime profile",
'Missing params "username" make sure you pass the parameters in URL',
);
});
});
+46
View File
@@ -0,0 +1,46 @@
const { flexLayout } = require("../src/common/utils");
describe("flexLayout", () => {
it("should work with row & col layouts", () => {
const layout = flexLayout({
items: ["<text>1</text>", "<text>2</text>"],
gap: 60,
});
expect(layout).toStrictEqual([
`<g transform="translate(0, 0)"><text>1</text></g>`,
`<g transform="translate(60, 0)"><text>2</text></g>`,
]);
const columns = flexLayout({
items: ["<text>1</text>", "<text>2</text>"],
gap: 60,
direction: "column",
});
expect(columns).toStrictEqual([
`<g transform="translate(0, 0)"><text>1</text></g>`,
`<g transform="translate(0, 60)"><text>2</text></g>`,
]);
});
it("should work with sizes", () => {
const layout = flexLayout({
items: [
"<text>1</text>",
"<text>2</text>",
"<text>3</text>",
"<text>4</text>",
],
gap: 20,
sizes: [200, 100, 55, 25],
});
expect(layout).toStrictEqual([
`<g transform="translate(0, 0)"><text>1</text></g>`,
`<g transform="translate(220, 0)"><text>2</text></g>`,
`<g transform="translate(340, 0)"><text>3</text></g>`,
`<g transform="translate(415, 0)"><text>4</text></g>`,
]);
});
});
+16 -3
View File
@@ -9,7 +9,9 @@ const data_repo = {
repository: {
username: "anuraghazra",
name: "convoychat",
stargazers: { totalCount: 38000 },
stargazers: {
totalCount: 38000,
},
description: "Help us take over the world! React + TS + GraphQL Chat App",
primaryLanguage: {
color: "#2b7489",
@@ -51,7 +53,12 @@ describe("Test /api/pin", () => {
await pin(req, res);
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
expect(res.send).toBeCalledWith(renderRepoCard(data_repo.repository));
expect(res.send).toBeCalledWith(
renderRepoCard({
...data_repo.repository,
starCount: data_repo.repository.stargazers.totalCount,
}),
);
});
it("should get the query options", async () => {
@@ -76,7 +83,13 @@ describe("Test /api/pin", () => {
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
expect(res.send).toBeCalledWith(
renderRepoCard(data_repo.repository, { ...req.query }),
renderRepoCard(
{
...data_repo.repository,
starCount: data_repo.repository.stargazers.totalCount,
},
{ ...req.query },
),
);
});
+66 -68
View File
@@ -1,5 +1,5 @@
require("@testing-library/jest-dom");
const cssToObject = require("css-to-object");
const cssToObject = require("@uppercod/css-to-object").cssToObject;
const renderRepoCard = require("../src/cards/repo-card");
const { queryByTestId } = require("@testing-library/dom");
@@ -9,13 +9,13 @@ const data_repo = {
repository: {
nameWithOwner: "anuraghazra/convoychat",
name: "convoychat",
stargazers: { totalCount: 38000 },
description: "Help us take over the world! React + TS + GraphQL Chat App",
primaryLanguage: {
color: "#2b7489",
id: "MDg6TGFuZ3VhZ2UyODc=",
name: "TypeScript",
},
starCount: 38000,
forkCount: 100,
},
};
@@ -51,6 +51,17 @@ describe("Test renderRepoCard", () => {
);
});
it("should trim header", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
name: "some-really-long-repo-name-for-test-purposes",
});
expect(document.getElementsByClassName("header")[0].textContent).toBe(
"some-really-long-repo-name-for-test...",
);
});
it("should trim description", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
@@ -89,36 +100,6 @@ describe("Test renderRepoCard", () => {
);
});
it("should shift the text position depending on language length", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
primaryLanguage: {
...data_repo.repository.primaryLanguage,
name: "Jupyter Notebook",
},
});
expect(queryByTestId(document.body, "primary-lang")).toBeInTheDocument();
expect(queryByTestId(document.body, "star-fork-group")).toHaveAttribute(
"transform",
"translate(155, 0)",
);
// Small lang
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
primaryLanguage: {
...data_repo.repository.primaryLanguage,
name: "Ruby",
},
});
expect(queryByTestId(document.body, "star-fork-group")).toHaveAttribute(
"transform",
"translate(125, 0)",
);
});
it("should hide language if primaryLanguage is null & fallback to correct values", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
@@ -149,13 +130,13 @@ describe("Test renderRepoCard", () => {
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.innerHTML);
const headerClassStyles = stylesObject[".header"];
const descClassStyles = stylesObject[".description"];
const iconClassStyles = stylesObject[".icon"];
const headerClassStyles = stylesObject[":host"][".header "];
const descClassStyles = stylesObject[":host"][".description "];
const iconClassStyles = stylesObject[":host"][".icon "];
expect(headerClassStyles.fill).toBe("#2f80ed");
expect(descClassStyles.fill).toBe("#333");
expect(iconClassStyles.fill).toBe("#586069");
expect(headerClassStyles.fill.trim()).toBe("#2f80ed");
expect(descClassStyles.fill.trim()).toBe("#434d58");
expect(iconClassStyles.fill.trim()).toBe("#586069");
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
"#fffefe",
@@ -177,13 +158,13 @@ describe("Test renderRepoCard", () => {
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.innerHTML);
const headerClassStyles = stylesObject[".header"];
const descClassStyles = stylesObject[".description"];
const iconClassStyles = stylesObject[".icon"];
const headerClassStyles = stylesObject[":host"][".header "];
const descClassStyles = stylesObject[":host"][".description "];
const iconClassStyles = stylesObject[":host"][".icon "];
expect(headerClassStyles.fill).toBe(`#${customColors.title_color}`);
expect(descClassStyles.fill).toBe(`#${customColors.text_color}`);
expect(iconClassStyles.fill).toBe(`#${customColors.icon_color}`);
expect(headerClassStyles.fill.trim()).toBe(`#${customColors.title_color}`);
expect(descClassStyles.fill.trim()).toBe(`#${customColors.text_color}`);
expect(iconClassStyles.fill.trim()).toBe(`#${customColors.icon_color}`);
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
"#252525",
@@ -199,13 +180,15 @@ describe("Test renderRepoCard", () => {
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.innerHTML);
const headerClassStyles = stylesObject[".header"];
const descClassStyles = stylesObject[".description"];
const iconClassStyles = stylesObject[".icon"];
const headerClassStyles = stylesObject[":host"][".header "];
const descClassStyles = stylesObject[":host"][".description "];
const iconClassStyles = stylesObject[":host"][".icon "];
expect(headerClassStyles.fill).toBe(`#${themes[name].title_color}`);
expect(descClassStyles.fill).toBe(`#${themes[name].text_color}`);
expect(iconClassStyles.fill).toBe(`#${themes[name].icon_color}`);
expect(headerClassStyles.fill.trim()).toBe(
`#${themes[name].title_color}`,
);
expect(descClassStyles.fill.trim()).toBe(`#${themes[name].text_color}`);
expect(iconClassStyles.fill.trim()).toBe(`#${themes[name].icon_color}`);
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
`#${themes[name].bg_color}`,
@@ -222,13 +205,13 @@ describe("Test renderRepoCard", () => {
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.innerHTML);
const headerClassStyles = stylesObject[".header"];
const descClassStyles = stylesObject[".description"];
const iconClassStyles = stylesObject[".icon"];
const headerClassStyles = stylesObject[":host"][".header "];
const descClassStyles = stylesObject[":host"][".description "];
const iconClassStyles = stylesObject[":host"][".icon "];
expect(headerClassStyles.fill).toBe("#5a0");
expect(descClassStyles.fill).toBe(`#${themes.radical.text_color}`);
expect(iconClassStyles.fill).toBe(`#${themes.radical.icon_color}`);
expect(headerClassStyles.fill.trim()).toBe("#5a0");
expect(descClassStyles.fill.trim()).toBe(`#${themes.radical.text_color}`);
expect(iconClassStyles.fill.trim()).toBe(`#${themes.radical.icon_color}`);
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
`#${themes.radical.bg_color}`,
@@ -245,13 +228,15 @@ describe("Test renderRepoCard", () => {
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.innerHTML);
const headerClassStyles = stylesObject[".header"];
const descClassStyles = stylesObject[".description"];
const iconClassStyles = stylesObject[".icon"];
const headerClassStyles = stylesObject[":host"][".header "];
const descClassStyles = stylesObject[":host"][".description "];
const iconClassStyles = stylesObject[":host"][".icon "];
expect(headerClassStyles.fill).toBe(`#${themes.default.title_color}`);
expect(descClassStyles.fill).toBe(`#${themes.default.text_color}`);
expect(iconClassStyles.fill).toBe(`#${themes.radical.icon_color}`);
expect(headerClassStyles.fill.trim()).toBe(
`#${themes.default.title_color}`,
);
expect(descClassStyles.fill.trim()).toBe(`#${themes.default.text_color}`);
expect(iconClassStyles.fill.trim()).toBe(`#${themes.radical.icon_color}`);
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
`#${themes.radical.bg_color}`,
@@ -261,7 +246,7 @@ describe("Test renderRepoCard", () => {
it("should not render star count or fork count if either of the are zero", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
stargazers: { totalCount: 0 },
starCount: 0,
});
expect(queryByTestId(document.body, "stargazers")).toBeNull();
@@ -269,7 +254,7 @@ describe("Test renderRepoCard", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
stargazers: { totalCount: 1 },
starCount: 1,
forkCount: 0,
});
@@ -278,7 +263,7 @@ describe("Test renderRepoCard", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
stargazers: { totalCount: 0 },
starCount: 0,
forkCount: 0,
});
@@ -332,11 +317,24 @@ describe("Test renderRepoCard", () => {
);
expect(queryByTestId(document.body, "badge")).toHaveTextContent("模板");
});
it("should render without rounding", () => {
document.body.innerHTML = renderRepoCard(data_repo.repository, { border_radius: "0" });
document.body.innerHTML = renderRepoCard(data_repo.repository, {
border_radius: "0",
});
expect(document.querySelector("rect")).toHaveAttribute("rx", "0");
document.body.innerHTML = renderRepoCard(data_repo.repository, { });
document.body.innerHTML = renderRepoCard(data_repo.repository, {});
expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5");
});
it("should fallback to default description", () => {
document.body.innerHTML = renderRepoCard({
...data_repo.repository,
description: undefined,
isArchived: true,
});
expect(document.getElementsByClassName("description")[0]).toHaveTextContent(
"No description provided",
);
});
});
+82 -32
View File
@@ -1,5 +1,5 @@
require("@testing-library/jest-dom");
const cssToObject = require("css-to-object");
const cssToObject = require("@uppercod/css-to-object").cssToObject;
const renderStatsCard = require("../src/cards/stats-card");
const {
@@ -75,19 +75,65 @@ describe("Test renderStatsCard", () => {
expect(queryByTestId(document.body, "rank-circle")).not.toBeInTheDocument();
});
it("should render with custom width set", () => {
document.body.innerHTML = renderStatsCard(stats);
expect(document.querySelector("svg")).toHaveAttribute("width", "495");
document.body.innerHTML = renderStatsCard(stats, { card_width: 400 });
expect(document.querySelector("svg")).toHaveAttribute("width", "400");
});
it("should render with custom width set and limit minimum width", () => {
document.body.innerHTML = renderStatsCard(stats, { card_width: 1 });
expect(document.querySelector("svg")).toHaveAttribute("width", "340");
document.body.innerHTML = renderStatsCard(stats, {
card_width: 1,
hide_rank: true,
});
expect(document.querySelector("svg")).toHaveAttribute(
"width",
"305.81250000000006",
);
document.body.innerHTML = renderStatsCard(stats, {
card_width: 1,
hide_rank: true,
show_icons: true,
});
expect(document.querySelector("svg")).toHaveAttribute(
"width",
"305.81250000000006",
);
document.body.innerHTML = renderStatsCard(stats, {
card_width: 1,
hide_rank: false,
show_icons: true,
});
expect(document.querySelector("svg")).toHaveAttribute("width", "356");
document.body.innerHTML = renderStatsCard(stats, {
card_width: 1,
hide_rank: false,
show_icons: false,
});
expect(document.querySelector("svg")).toHaveAttribute("width", "340");
});
it("should render default colors properly", () => {
document.body.innerHTML = renderStatsCard(stats);
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.textContent);
const headerClassStyles = stylesObject[".header"];
const statClassStyles = stylesObject[".stat"];
const iconClassStyles = stylesObject[".icon"];
const headerClassStyles = stylesObject[":host"][".header "];
const statClassStyles = stylesObject[":host"][".stat "];
const iconClassStyles = stylesObject[":host"][".icon "];
expect(headerClassStyles.fill).toBe("#2f80ed");
expect(statClassStyles.fill).toBe("#333");
expect(iconClassStyles.fill).toBe("#4c71f2");
expect(headerClassStyles.fill.trim()).toBe("#2f80ed");
expect(statClassStyles.fill.trim()).toBe("#434d58");
expect(iconClassStyles.fill.trim()).toBe("#4c71f2");
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
"#fffefe",
@@ -107,13 +153,13 @@ describe("Test renderStatsCard", () => {
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.innerHTML);
const headerClassStyles = stylesObject[".header"];
const statClassStyles = stylesObject[".stat"];
const iconClassStyles = stylesObject[".icon"];
const headerClassStyles = stylesObject[":host"][".header "];
const statClassStyles = stylesObject[":host"][".stat "];
const iconClassStyles = stylesObject[":host"][".icon "];
expect(headerClassStyles.fill).toBe(`#${customColors.title_color}`);
expect(statClassStyles.fill).toBe(`#${customColors.text_color}`);
expect(iconClassStyles.fill).toBe(`#${customColors.icon_color}`);
expect(headerClassStyles.fill.trim()).toBe(`#${customColors.title_color}`);
expect(statClassStyles.fill.trim()).toBe(`#${customColors.text_color}`);
expect(iconClassStyles.fill.trim()).toBe(`#${customColors.icon_color}`);
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
"#252525",
@@ -129,13 +175,13 @@ describe("Test renderStatsCard", () => {
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.innerHTML);
const headerClassStyles = stylesObject[".header"];
const statClassStyles = stylesObject[".stat"];
const iconClassStyles = stylesObject[".icon"];
const headerClassStyles = stylesObject[":host"][".header "];
const statClassStyles = stylesObject[":host"][".stat "];
const iconClassStyles = stylesObject[":host"][".icon "];
expect(headerClassStyles.fill).toBe("#5a0");
expect(statClassStyles.fill).toBe(`#${themes.radical.text_color}`);
expect(iconClassStyles.fill).toBe(`#${themes.radical.icon_color}`);
expect(headerClassStyles.fill.trim()).toBe("#5a0");
expect(statClassStyles.fill.trim()).toBe(`#${themes.radical.text_color}`);
expect(iconClassStyles.fill.trim()).toBe(`#${themes.radical.icon_color}`);
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
`#${themes.radical.bg_color}`,
@@ -151,13 +197,15 @@ describe("Test renderStatsCard", () => {
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.innerHTML);
const headerClassStyles = stylesObject[".header"];
const statClassStyles = stylesObject[".stat"];
const iconClassStyles = stylesObject[".icon"];
const headerClassStyles = stylesObject[":host"][".header "];
const statClassStyles = stylesObject[":host"][".stat "];
const iconClassStyles = stylesObject[":host"][".icon "];
expect(headerClassStyles.fill).toBe(`#${themes[name].title_color}`);
expect(statClassStyles.fill).toBe(`#${themes[name].text_color}`);
expect(iconClassStyles.fill).toBe(`#${themes[name].icon_color}`);
expect(headerClassStyles.fill.trim()).toBe(
`#${themes[name].title_color}`,
);
expect(statClassStyles.fill.trim()).toBe(`#${themes[name].text_color}`);
expect(iconClassStyles.fill.trim()).toBe(`#${themes[name].icon_color}`);
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
`#${themes[name].bg_color}`,
@@ -175,13 +223,15 @@ describe("Test renderStatsCard", () => {
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.innerHTML);
const headerClassStyles = stylesObject[".header"];
const statClassStyles = stylesObject[".stat"];
const iconClassStyles = stylesObject[".icon"];
const headerClassStyles = stylesObject[":host"][".header "];
const statClassStyles = stylesObject[":host"][".stat "];
const iconClassStyles = stylesObject[":host"][".icon "];
expect(headerClassStyles.fill).toBe(`#${themes.default.title_color}`);
expect(statClassStyles.fill).toBe(`#${themes.default.text_color}`);
expect(iconClassStyles.fill).toBe(`#${themes.radical.icon_color}`);
expect(headerClassStyles.fill.trim()).toBe(
`#${themes.default.title_color}`,
);
expect(statClassStyles.fill.trim()).toBe(`#${themes.default.text_color}`);
expect(iconClassStyles.fill.trim()).toBe(`#${themes.radical.icon_color}`);
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
`#${themes.radical.bg_color}`,
@@ -245,7 +295,7 @@ describe("Test renderStatsCard", () => {
document.querySelector(
'g[transform="translate(0, 25)"]>.stagger>.stat.bold',
).textContent,
).toMatchInlineSnapshot(`"累计提交数(commit (2021):"`);
).toMatchInlineSnapshot(`"累计提交数(commit (2022):"`);
expect(
document.querySelector(
'g[transform="translate(0, 50)"]>.stagger>.stat.bold',
+32 -18
View File
@@ -1,6 +1,5 @@
require("@testing-library/jest-dom");
const cssToObject = require("css-to-object");
const fetchTopLanguages = require("../src/fetchers/top-languages-fetcher");
const cssToObject = require("@uppercod/css-to-object").cssToObject;
const renderTopLanguages = require("../src/cards/top-languages-card");
const { queryByTestId, queryAllByTestId } = require("@testing-library/dom");
@@ -104,17 +103,32 @@ describe("Test renderTopLanguages", () => {
expect(document.querySelector("svg")).toHaveAttribute("width", "400");
});
it("should render with min width", () => {
document.body.innerHTML = renderTopLanguages(langs, { card_width: 190 });
expect(document.querySelector("svg")).toHaveAttribute(
"width",
renderTopLanguages.MIN_CARD_WIDTH.toString(),
);
document.body.innerHTML = renderTopLanguages(langs, { card_width: 100 });
expect(document.querySelector("svg")).toHaveAttribute(
"width",
renderTopLanguages.MIN_CARD_WIDTH.toString(),
);
});
it("should render default colors properly", () => {
document.body.innerHTML = renderTopLanguages(langs);
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.textContent);
const headerStyles = stylesObject[".header"];
const langNameStyles = stylesObject[".lang-name"];
const headerStyles = stylesObject[":host"][".header "];
const langNameStyles = stylesObject[":host"][".lang-name "];
expect(headerStyles.fill).toBe("#2f80ed");
expect(langNameStyles.fill).toBe("#333");
expect(headerStyles.fill.trim()).toBe("#2f80ed");
expect(langNameStyles.fill.trim()).toBe("#434d58");
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
"#fffefe",
@@ -134,11 +148,11 @@ describe("Test renderTopLanguages", () => {
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.innerHTML);
const headerStyles = stylesObject[".header"];
const langNameStyles = stylesObject[".lang-name"];
const headerStyles = stylesObject[":host"][".header "];
const langNameStyles = stylesObject[":host"][".lang-name "];
expect(headerStyles.fill).toBe(`#${customColors.title_color}`);
expect(langNameStyles.fill).toBe(`#${customColors.text_color}`);
expect(headerStyles.fill.trim()).toBe(`#${customColors.title_color}`);
expect(langNameStyles.fill.trim()).toBe(`#${customColors.text_color}`);
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
"#252525",
@@ -154,11 +168,11 @@ describe("Test renderTopLanguages", () => {
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.innerHTML);
const headerStyles = stylesObject[".header"];
const langNameStyles = stylesObject[".lang-name"];
const headerStyles = stylesObject[":host"][".header "];
const langNameStyles = stylesObject[":host"][".lang-name "];
expect(headerStyles.fill).toBe("#5a0");
expect(langNameStyles.fill).toBe(`#${themes.radical.text_color}`);
expect(headerStyles.fill.trim()).toBe("#5a0");
expect(langNameStyles.fill.trim()).toBe(`#${themes.radical.text_color}`);
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
`#${themes.radical.bg_color}`,
@@ -174,11 +188,11 @@ describe("Test renderTopLanguages", () => {
const styleTag = document.querySelector("style");
const stylesObject = cssToObject(styleTag.innerHTML);
const headerStyles = stylesObject[".header"];
const langNameStyles = stylesObject[".lang-name"];
const headerStyles = stylesObject[":host"][".header "];
const langNameStyles = stylesObject[":host"][".lang-name "];
expect(headerStyles.fill).toBe(`#${themes[name].title_color}`);
expect(langNameStyles.fill).toBe(`#${themes[name].text_color}`);
expect(headerStyles.fill.trim()).toBe(`#${themes[name].title_color}`);
expect(langNameStyles.fill.trim()).toBe(`#${themes[name].text_color}`);
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
"fill",
`#${themes[name].bg_color}`,
+29 -3
View File
@@ -1,6 +1,7 @@
require("@testing-library/jest-dom");
const renderWakatimeCard = require("../src/cards/wakatime-card");
const { queryByTestId } = require("@testing-library/dom");
const renderWakatimeCard = require("../src/cards/wakatime-card");
const { wakaTimeData } = require("./fetchWakatime.test");
describe("Test Render Wakatime Card", () => {
@@ -16,6 +17,16 @@ describe("Test Render Wakatime Card", () => {
expect(card).toMatchSnapshot();
});
it("should hide languages when hide is passed", () => {
document.body.innerHTML = renderWakatimeCard(wakaTimeData.data, {
hide: ["YAML", "Other"],
});
expect(queryByTestId(document.body, /YAML/i)).toBeNull();
expect(queryByTestId(document.body, /Other/i)).toBeNull();
expect(queryByTestId(document.body, /TypeScript/i)).not.toBeNull();
});
it("should render translations", () => {
document.body.innerHTML = renderWakatimeCard({}, { locale: "cn" });
expect(document.getElementsByClassName("header")[0].textContent).toBe(
@@ -28,9 +39,24 @@ describe("Test Render Wakatime Card", () => {
});
it("should render without rounding", () => {
document.body.innerHTML = renderWakatimeCard(wakaTimeData.data, { border_radius: "0" });
document.body.innerHTML = renderWakatimeCard(wakaTimeData.data, {
border_radius: "0",
});
expect(document.querySelector("rect")).toHaveAttribute("rx", "0");
document.body.innerHTML = renderWakatimeCard(wakaTimeData.data, { });
document.body.innerHTML = renderWakatimeCard(wakaTimeData.data, {});
expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5");
});
it('should show "no coding activitiy this week" message when there hasn not been activity', () => {
document.body.innerHTML = renderWakatimeCard(
{
...wakaTimeData.data,
languages: undefined,
},
{},
);
expect(document.querySelector(".stat").textContent).toBe(
"No coding activity this week",
);
});
});
+7 -21
View File
@@ -44,27 +44,6 @@ describe("Test utils.js", () => {
).toHaveTextContent(/Secondary Message/gim);
});
it("should test flexLayout", () => {
const layout = flexLayout({
items: ["<text>1</text>", "<text>2</text>"],
gap: 60,
}).join("");
expect(layout).toBe(
`<g transform=\"translate(0, 0)\"><text>1</text></g><g transform=\"translate(60, 0)\"><text>2</text></g>`,
);
const columns = flexLayout({
items: ["<text>1</text>", "<text>2</text>"],
gap: 60,
direction: "column",
}).join("");
expect(columns).toBe(
`<g transform=\"translate(0, 0)\"><text>1</text></g><g transform=\"translate(0, 60)\"><text>2</text></g>`,
);
});
it("getCardColors: should return expected values", () => {
let colors = getCardColors({
title_color: "f00",
@@ -138,4 +117,11 @@ describe("wrapTextMultiline", () => {
);
expect(multiLineText).toEqual(["Hello", "world long..."]);
});
it("should wrap chinese by punctuation", () => {
let multiLineText = wrapTextMultiline(
"专门为刚开始刷题的同学准备的算法基地,没有最细只有更细,立志用动画将晦涩难懂的算法说的通俗易懂!",
);
expect(multiLineText.length).toEqual(3);
expect(multiLineText[0].length).toEqual(18 * 8); // &#xxxxx; x 8
});
});
+70 -30
View File
@@ -2,7 +2,7 @@
<!-- DO NOT EDIT THIS FILE DIRECTLY -->
With inbuilt themes you can customize the look of the card without doing any manual customization.
With inbuilt themes, you can customize the look of the card without doing any manual customization.
Use `?theme=THEME_NAME` parameter like so :-
@@ -17,20 +17,25 @@ Use `?theme=THEME_NAME` parameter like so :-
| | | |
| :--: | :--: | :--: |
| `default` ![default][default] | `dark` ![dark][dark] | `radical` ![radical][radical] |
| `merko` ![merko][merko] | `gruvbox` ![gruvbox][gruvbox] | `tokyonight` ![tokyonight][tokyonight] |
| `onedark` ![onedark][onedark] | `cobalt` ![cobalt][cobalt] | `synthwave` ![synthwave][synthwave] |
| `highcontrast` ![highcontrast][highcontrast] | `dracula` ![dracula][dracula] | `prussian` ![prussian][prussian] |
| `monokai` ![monokai][monokai] | `vue` ![vue][vue] | `vue-dark` ![vue-dark][vue-dark] |
| `shades-of-purple` ![shades-of-purple][shades-of-purple] | `nightowl` ![nightowl][nightowl] | `buefy` ![buefy][buefy] |
| `blue-green` ![blue-green][blue-green] | `algolia` ![algolia][algolia] | `great-gatsby` ![great-gatsby][great-gatsby] |
| `darcula` ![darcula][darcula] | `bear` ![bear][bear] | `solarized-dark` ![solarized-dark][solarized-dark] |
| `solarized-light` ![solarized-light][solarized-light] | `chartreuse-dark` ![chartreuse-dark][chartreuse-dark] | `nord` ![nord][nord] |
| `gotham` ![gotham][gotham] | `material-palenight` ![material-palenight][material-palenight] | `graywhite` ![graywhite][graywhite] |
| `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark] | `ayu-mirage` ![ayu-mirage][ayu-mirage] | `midnight-purple` ![midnight-purple][midnight-purple] |
| `calm` ![calm][calm] | `flag-india` ![flag-india][flag-india] | `omni` ![omni][omni] |
| `react` ![react][react] | `jolly` ![jolly][jolly] | `maroongold` ![maroongold][maroongold] |
| `yeblu` ![yeblu][yeblu] | `blueberry` ![blueberry][blueberry] | `slateorange` ![slateorange][slateorange] |
| `kacho_ga` ![kacho_ga][kacho_ga] | `outrun` ![outrun][outrun] | [Add your theme][add-theme] |
| `merko` ![merko][merko] | `gruvbox` ![gruvbox][gruvbox] | `gruvbox_light` ![gruvbox_light][gruvbox_light] |
| `tokyonight` ![tokyonight][tokyonight] | `onedark` ![onedark][onedark] | `cobalt` ![cobalt][cobalt] |
| `synthwave` ![synthwave][synthwave] | `highcontrast` ![highcontrast][highcontrast] | `dracula` ![dracula][dracula] |
| `prussian` ![prussian][prussian] | `monokai` ![monokai][monokai] | `vue` ![vue][vue] |
| `vue-dark` ![vue-dark][vue-dark] | `shades-of-purple` ![shades-of-purple][shades-of-purple] | `nightowl` ![nightowl][nightowl] |
| `buefy` ![buefy][buefy] | `blue-green` ![blue-green][blue-green] | `algolia` ![algolia][algolia] |
| `great-gatsby` ![great-gatsby][great-gatsby] | `darcula` ![darcula][darcula] | `bear` ![bear][bear] |
| `solarized-dark` ![solarized-dark][solarized-dark] | `solarized-light` ![solarized-light][solarized-light] | `chartreuse-dark` ![chartreuse-dark][chartreuse-dark] |
| `nord` ![nord][nord] | `gotham` ![gotham][gotham] | `material-palenight` ![material-palenight][material-palenight] |
| `graywhite` ![graywhite][graywhite] | `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark] | `ayu-mirage` ![ayu-mirage][ayu-mirage] |
| `midnight-purple` ![midnight-purple][midnight-purple] | `calm` ![calm][calm] | `flag-india` ![flag-india][flag-india] |
| `omni` ![omni][omni] | `react` ![react][react] | `jolly` ![jolly][jolly] |
| `maroongold` ![maroongold][maroongold] | `yeblu` ![yeblu][yeblu] | `blueberry` ![blueberry][blueberry] |
| `slateorange` ![slateorange][slateorange] | `kacho_ga` ![kacho_ga][kacho_ga] | `outrun` ![outrun][outrun] |
| `ocean_dark` ![ocean_dark][ocean_dark] | `city_lights` ![city_lights][city_lights] | `github_dark` ![github_dark][github_dark] |
| `discord_old_blurple` ![discord_old_blurple][discord_old_blurple] | `aura_dark` ![aura_dark][aura_dark] | `panda` ![panda][panda] |
| `noctis_minimus` ![noctis_minimus][noctis_minimus] | `cobalt2` ![cobalt2][cobalt2] | `swift` ![swift][swift] |
| `aura` ![aura][aura] | `apprentice` ![apprentice][apprentice] | `moltack` ![moltack][moltack] |
| `codeSTACKr` ![codeSTACKr][codeSTACKr] | `rose_pine` ![rose_pine][rose_pine] | [Add your theme][add-theme] |
## Repo Card
@@ -39,20 +44,25 @@ Use `?theme=THEME_NAME` parameter like so :-
| | | |
| :--: | :--: | :--: |
| `default_repocard` ![default_repocard][default_repocard_repo] | `dark` ![dark][dark_repo] | `radical` ![radical][radical_repo] |
| `merko` ![merko][merko_repo] | `gruvbox` ![gruvbox][gruvbox_repo] | `tokyonight` ![tokyonight][tokyonight_repo] |
| `onedark` ![onedark][onedark_repo] | `cobalt` ![cobalt][cobalt_repo] | `synthwave` ![synthwave][synthwave_repo] |
| `highcontrast` ![highcontrast][highcontrast_repo] | `dracula` ![dracula][dracula_repo] | `prussian` ![prussian][prussian_repo] |
| `monokai` ![monokai][monokai_repo] | `vue` ![vue][vue_repo] | `vue-dark` ![vue-dark][vue-dark_repo] |
| `shades-of-purple` ![shades-of-purple][shades-of-purple_repo] | `nightowl` ![nightowl][nightowl_repo] | `buefy` ![buefy][buefy_repo] |
| `blue-green` ![blue-green][blue-green_repo] | `algolia` ![algolia][algolia_repo] | `great-gatsby` ![great-gatsby][great-gatsby_repo] |
| `darcula` ![darcula][darcula_repo] | `bear` ![bear][bear_repo] | `solarized-dark` ![solarized-dark][solarized-dark_repo] |
| `solarized-light` ![solarized-light][solarized-light_repo] | `chartreuse-dark` ![chartreuse-dark][chartreuse-dark_repo] | `nord` ![nord][nord_repo] |
| `gotham` ![gotham][gotham_repo] | `material-palenight` ![material-palenight][material-palenight_repo] | `graywhite` ![graywhite][graywhite_repo] |
| `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark_repo] | `ayu-mirage` ![ayu-mirage][ayu-mirage_repo] | `midnight-purple` ![midnight-purple][midnight-purple_repo] |
| `calm` ![calm][calm_repo] | `flag-india` ![flag-india][flag-india_repo] | `omni` ![omni][omni_repo] |
| `react` ![react][react_repo] | `jolly` ![jolly][jolly_repo] | `maroongold` ![maroongold][maroongold_repo] |
| `yeblu` ![yeblu][yeblu_repo] | `blueberry` ![blueberry][blueberry_repo] | `slateorange` ![slateorange][slateorange_repo] |
| `kacho_ga` ![kacho_ga][kacho_ga_repo] | `outrun` ![outrun][outrun_repo] | [Add your theme][add-theme] |
| `merko` ![merko][merko_repo] | `gruvbox` ![gruvbox][gruvbox_repo] | `gruvbox_light` ![gruvbox_light][gruvbox_light_repo] |
| `tokyonight` ![tokyonight][tokyonight_repo] | `onedark` ![onedark][onedark_repo] | `cobalt` ![cobalt][cobalt_repo] |
| `synthwave` ![synthwave][synthwave_repo] | `highcontrast` ![highcontrast][highcontrast_repo] | `dracula` ![dracula][dracula_repo] |
| `prussian` ![prussian][prussian_repo] | `monokai` ![monokai][monokai_repo] | `vue` ![vue][vue_repo] |
| `vue-dark` ![vue-dark][vue-dark_repo] | `shades-of-purple` ![shades-of-purple][shades-of-purple_repo] | `nightowl` ![nightowl][nightowl_repo] |
| `buefy` ![buefy][buefy_repo] | `blue-green` ![blue-green][blue-green_repo] | `algolia` ![algolia][algolia_repo] |
| `great-gatsby` ![great-gatsby][great-gatsby_repo] | `darcula` ![darcula][darcula_repo] | `bear` ![bear][bear_repo] |
| `solarized-dark` ![solarized-dark][solarized-dark_repo] | `solarized-light` ![solarized-light][solarized-light_repo] | `chartreuse-dark` ![chartreuse-dark][chartreuse-dark_repo] |
| `nord` ![nord][nord_repo] | `gotham` ![gotham][gotham_repo] | `material-palenight` ![material-palenight][material-palenight_repo] |
| `graywhite` ![graywhite][graywhite_repo] | `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark_repo] | `ayu-mirage` ![ayu-mirage][ayu-mirage_repo] |
| `midnight-purple` ![midnight-purple][midnight-purple_repo] | `calm` ![calm][calm_repo] | `flag-india` ![flag-india][flag-india_repo] |
| `omni` ![omni][omni_repo] | `react` ![react][react_repo] | `jolly` ![jolly][jolly_repo] |
| `maroongold` ![maroongold][maroongold_repo] | `yeblu` ![yeblu][yeblu_repo] | `blueberry` ![blueberry][blueberry_repo] |
| `slateorange` ![slateorange][slateorange_repo] | `kacho_ga` ![kacho_ga][kacho_ga_repo] | `outrun` ![outrun][outrun_repo] |
| `ocean_dark` ![ocean_dark][ocean_dark_repo] | `city_lights` ![city_lights][city_lights_repo] | `github_dark` ![github_dark][github_dark_repo] |
| `discord_old_blurple` ![discord_old_blurple][discord_old_blurple_repo] | `aura_dark` ![aura_dark][aura_dark_repo] | `panda` ![panda][panda_repo] |
| `noctis_minimus` ![noctis_minimus][noctis_minimus_repo] | `cobalt2` ![cobalt2][cobalt2_repo] | `swift` ![swift][swift_repo] |
| `aura` ![aura][aura_repo] | `apprentice` ![apprentice][apprentice_repo] | `moltack` ![moltack][moltack_repo] |
| `codeSTACKr` ![codeSTACKr][codeSTACKr_repo] | `rose_pine` ![rose_pine][rose_pine_repo] | [Add your theme][add-theme] |
[default]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=default
@@ -61,6 +71,7 @@ Use `?theme=THEME_NAME` parameter like so :-
[radical]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=radical
[merko]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=merko
[gruvbox]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=gruvbox
[gruvbox_light]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=gruvbox_light
[tokyonight]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=tokyonight
[onedark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=onedark
[cobalt]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=cobalt
@@ -100,6 +111,20 @@ Use `?theme=THEME_NAME` parameter like so :-
[slateorange]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=slateorange
[kacho_ga]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=kacho_ga
[outrun]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=outrun
[ocean_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=ocean_dark
[city_lights]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=city_lights
[github_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=github_dark
[discord_old_blurple]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=discord_old_blurple
[aura_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=aura_dark
[panda]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=panda
[noctis_minimus]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=noctis_minimus
[cobalt2]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=cobalt2
[swift]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=swift
[aura]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=aura
[apprentice]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=apprentice
[moltack]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=moltack
[codeSTACKr]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=codeSTACKr
[rose_pine]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=rose_pine
[default_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default
@@ -108,6 +133,7 @@ Use `?theme=THEME_NAME` parameter like so :-
[radical_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=radical
[merko_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=merko
[gruvbox_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=gruvbox
[gruvbox_light_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=gruvbox_light
[tokyonight_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=tokyonight
[onedark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=onedark
[cobalt_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=cobalt
@@ -147,8 +173,22 @@ Use `?theme=THEME_NAME` parameter like so :-
[slateorange_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=slateorange
[kacho_ga_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=kacho_ga
[outrun_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=outrun
[ocean_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=ocean_dark
[city_lights_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=city_lights
[github_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=github_dark
[discord_old_blurple_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=discord_old_blurple
[aura_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=aura_dark
[panda_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=panda
[noctis_minimus_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=noctis_minimus
[cobalt2_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=cobalt2
[swift_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=swift
[aura_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=aura
[apprentice_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=apprentice
[moltack_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=moltack
[codeSTACKr_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=codeSTACKr
[rose_pine_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=rose_pine
[add-theme]: https://github.com/anuraghazra/github-readme-stats/edit/master/themes/index.js
Wanted to add a new theme? Consider reading the [contribution guidelines](../CONTRIBUTING.md#themes-contribution) :D
Want to add a new theme? Consider reading the [contribution guidelines](../CONTRIBUTING.md#themes-contribution) :D
+69 -2
View File
@@ -2,14 +2,14 @@ const themes = {
default: {
title_color: "2f80ed",
icon_color: "4c71f2",
text_color: "333",
text_color: "434d58",
bg_color: "fffefe",
border_color: "e4e2e2",
},
default_repocard: {
title_color: "2f80ed",
icon_color: "586069", // icon color is different
text_color: "333",
text_color: "434d58",
bg_color: "fffefe",
},
dark: {
@@ -36,6 +36,12 @@ const themes = {
text_color: "8ec07c",
bg_color: "282828",
},
gruvbox_light: {
title_color: "b57614",
icon_color: "af3a03",
text_color: "427b58",
bg_color: "fbf1c7",
},
tokyonight: {
title_color: "70a5fd",
icon_color: "bf91f3",
@@ -294,6 +300,67 @@ const themes = {
text_color: "FFFFFF",
bg_color: "2C2F33",
},
aura_dark: {
title_color: "ff7372",
icon_color: "6cffd0",
text_color: "dbdbdb",
bg_color: "252334",
},
panda: {
title_color: "19f9d899",
icon_color: "19f9d899",
text_color: "FF75B5",
bg_color: "31353a",
},
noctis_minimus: {
title_color: "d3b692",
icon_color: "72b7c0",
text_color: "c5cdd3",
bg_color: "1b2932",
},
cobalt2: {
title_color: "ffc600",
icon_color: "ffffff",
text_color: "0088ff",
bg_color: "193549",
},
swift: {
title_color: "000000",
icon_color: "f05237",
text_color: "000000",
bg_color: "f7f7f7",
},
aura: {
title_color: "a277ff",
icon_color: "ffca85",
text_color: "61ffca",
bg_color: "15141b",
},
apprentice: {
title_color: "ffffff",
icon_color: "ffffaf",
text_color: "bcbcbc",
bg_color: "262626",
},
moltack: {
title_color: "86092C",
icon_color: "86092C",
text_color: "574038",
bg_color: "F5E1C0",
},
codeSTACKr: {
title_color: "ff652f",
icon_color: "FFE400",
text_color: "ffffff",
bg_color: "09131B",
border_color: "0c1a25",
},
rose_pine: {
title_color: "9ccfd8",
icon_color: "ebbcba",
text_color: "e0def4",
bg_color: "191724",
},
};
module.exports = themes;
+6
View File
@@ -1,4 +1,10 @@
{
"functions": {
"api/*.js": {
"memory": 128,
"maxDuration": 30
}
},
"redirects": [
{
"source": "/",