diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 71b71275..fa7228a6 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -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
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index d3ecf0fa..5ed7d3c9 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -5,11 +5,12 @@ title: ""
labels: ""
assignees: ""
---
+
**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.
-
diff --git a/.github/workflows/empty-issues-closer.yaml b/.github/workflows/empty-issues-closer.yaml
new file mode 100644
index 00000000..5f050fe0
--- /dev/null
+++ b/.github/workflows/empty-issues-closer.yaml
@@ -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.
diff --git a/.github/workflows/preview-theme.yml b/.github/workflows/preview-theme.yml
index f102b59c..db7e291d 100644
--- a/.github/workflows/preview-theme.yml
+++ b/.github/workflows/preview-theme.yml
@@ -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:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 0eb02215..0911168f 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -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
diff --git a/.github/workflows/top-issues-dashboard.yml b/.github/workflows/top-issues-dashboard.yml
new file mode 100644
index 00000000..a9fca7f3
--- /dev/null
+++ b/.github/workflows/top-issues-dashboard.yml
@@ -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
diff --git a/.gitignore b/.gitignore
index 1c96025a..835034e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,6 @@ package-lock.json
coverage
vercel_token
+# IDE
+.vscode
+*.code-workspace
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 04528df1..0d4b558a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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 .
## 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:
+> - YT tutorial by codeSTACKr:
**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-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
diff --git a/LICENSE b/LICENSE
index 83b114d1..14d9ba34 100644
--- a/LICENSE
+++ b/LICENSE
@@ -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
diff --git a/api/index.js b/api/index.js
index 0cda587e..2315ddda 100644
--- a/api/index.js
+++ b/api/index.js
@@ -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,
diff --git a/api/pin.js b/api/pin.js
index 7fad6c09..f2761cb6 100644
--- a/api/pin.js
+++ b/api/pin.js
@@ -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;
diff --git a/api/top-langs.js b/api/top-langs.js
index 89306fa2..80802074 100644
--- a/api/top-langs.js
+++ b/api/top-langs.js
@@ -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,
);
diff --git a/api/wakatime.js b/api/wakatime.js
index 4bb8fca5..18b52189 100644
--- a/api/wakatime.js
+++ b/api/wakatime.js
@@ -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,
diff --git a/docs/readme_cn.md b/docs/readme_cn.md
index 60a7d26c..17fd2b71 100644
--- a/docs/readme_cn.md
+++ b/docs/readme_cn.md
@@ -290,7 +290,7 @@ _注意:热门语言并不表示我的技能水平或类似的水平,它是
你通常无法将图片靠边显示。为此,您可以使用以下方法:
-```md
+```html
@@ -325,7 +325,7 @@ _注意:热门语言并不表示我的技能水平或类似的水平,它是
1. 选择 `Import Git Repository`

1. 选择 root 并将所有内容保持不变,并且只需添加名为 PAT_1 的环境变量(如图所示),其中将包含一个个人访问令牌(PAT),你可以在[这里](https://github.com/settings/tokens/new)轻松创建(保留默认,并且只需要命名下,名字随便)
- 
+ 
1. 点击 deploy,这就完成了,查看你的域名就可使用 API 了!
diff --git a/docs/readme_de.md b/docs/readme_de.md
index aa0769c3..55523fe3 100644
--- a/docs/readme_de.md
+++ b/docs/readme_de.md
@@ -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
[](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

@@ -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`
[](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.
[](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
[](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
[](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
@@ -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)

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)

-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!
## :sparkling_heart: Unterstütze das Projekt
diff --git a/docs/readme_es.md b/docs/readme_es.md
index ae0cc830..5ddceec7 100644
--- a/docs/readme_es.md
+++ b/docs/readme_es.md
@@ -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
diff --git a/docs/readme_fr.md b/docs/readme_fr.md
index d69c39b7..20996bd6 100644
--- a/docs/readme_fr.md
+++ b/docs/readme_fr.md
@@ -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
diff --git a/docs/readme_it.md b/docs/readme_it.md
index 8ba49435..1b2df96a 100644
--- a/docs/readme_it.md
+++ b/docs/readme_it.md
@@ -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
diff --git a/docs/readme_ja.md b/docs/readme_ja.md
index 6e7e747f..b00c77a7 100644
--- a/docs/readme_ja.md
+++ b/docs/readme_ja.md
@@ -296,7 +296,7 @@ _NOTE: Top languages は、ユーザのスキルレベルを示すものでは
通常、画像を並べてレイアウトすることはできません。画像を並べるには、以下のような方法があります。
-```md
+```html
diff --git a/docs/readme_kr.md b/docs/readme_kr.md
index 55851fd2..f5dfc9f2 100644
--- a/docs/readme_kr.md
+++ b/docs/readme_kr.md
@@ -367,7 +367,7 @@ _참고:
그럴땐, 이렇게 해보세요!
-```md
+```html
diff --git a/docs/readme_nl.md b/docs/readme_nl.md
index 788f2c8b..597f0c86 100644
--- a/docs/readme_nl.md
+++ b/docs/readme_nl.md
@@ -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
diff --git a/docs/readme_np.md b/docs/readme_np.md
index b8f92967..15bb34ba 100644
--- a/docs/readme_np.md
+++ b/docs/readme_np.md
@@ -340,7 +340,7 @@ Change the `?username=` value to your [Wakatime](https://wakatime.com) username.
तपाइले इमेजलाई सइद बय सइद अलीग्न गर्न सक्नु हुदैन तेसैले येसरी गर्नु होस् :
-```md
+```html
diff --git a/docs/readme_pt-BR.md b/docs/readme_pt-BR.md
index 6542f52d..7f4814ce 100644
--- a/docs/readme_pt-BR.md
+++ b/docs/readme_pt-BR.md
@@ -27,7 +27,7 @@
- Ver demonstração
+ Ver demonstração
·
Reportar erros
·
@@ -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
diff --git a/docs/readme_tr.md b/docs/readme_tr.md
index 5a1c974e..35f32f2f 100644
--- a/docs/readme_tr.md
+++ b/docs/readme_tr.md
@@ -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
diff --git a/package.json b/package.json
index 0cb50dc1..be9e4354 100644
--- a/package.json
+++ b/package.json
@@ -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": {
diff --git a/readme.md b/readme.md
index 068e3978..3ef40724 100644
--- a/readme.md
+++ b/readme.md
@@ -1,7 +1,7 @@
GitHub Readme Stats
-
Get dynamically generated GitHub stats on your readmes!
+
Get dynamically generated GitHub stats on your READMEs!
Français
@@ -65,13 +67,13 @@
-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:
@@ -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
[](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

```
-#### 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
[](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
[](https://github.com/anuraghazra/github-readme-stats)
@@ -306,6 +319,8 @@ Change the `?username=` value to your [Wakatime](https://wakatime.com) username.
[](https://github.com/anuraghazra/github-readme-stats)
```
+> Note: Please be aware that we currently only show data from Wakatime profiles that are public.
+
### Demo
[](https://github.com/anuraghazra/github-readme-stats)
@@ -403,14 +418,16 @@ NOTE: Since [#58](https://github.com/anuraghazra/github-readme-stats/pull/58) we

1. Sign in with GitHub by pressing `Continue with GitHub`

-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`

-1. Select `Import Git Repository`
+1. Select `Import Git Repository`. Select root and keep everything as is.

-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).

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.
+
diff --git a/scripts/generate-langs-json.js b/scripts/generate-langs-json.js
new file mode 100644
index 00000000..bd210a3c
--- /dev/null
+++ b/scripts/generate-langs-json.js
@@ -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, " "),
+ );
+ });
diff --git a/scripts/generate-theme-doc.js b/scripts/generate-theme-doc.js
index 806b2cc3..fcb3c5ee 100644
--- a/scripts/generate-theme-doc.js
+++ b/scripts/generate-theme-doc.js
@@ -12,7 +12,7 @@ const THEME_TEMPLATE = `## Available Themes
-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) => {
diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js
index e809777b..e4422ecd 100644
--- a/scripts/preview-theme.js
+++ b/scripts/preview-theme.js
@@ -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: #${titleColor} | icon_color: #${iconColor} | text_color: #${textColor} | bg_color: #${bgColor}
- \rLink: ${url}
+ \r[Preview Link](${url})
\r[](${url})
${themeContribGuidelines}
`,
- issue_number: pullRequestId,
});
} catch (error) {
console.log(error);
diff --git a/src/calculateRank.js b/src/calculateRank.js
index df6fdcd3..a835e5dd 100644
--- a/src/calculateRank.js
+++ b/src/calculateRank.js
@@ -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};
}
diff --git a/src/cards/repo-card.js b/src/cards/repo-card.js
index 9a21fcbd..034ee1f6 100644
--- a/src/cards/repo-card.js
+++ b/src/cards/repo-card.js
@@ -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) => `
+
+
+
+ ${label}
+
+
+`;
+
+/**
+ * @param {string} langName
+ * @param {string} langColor
+ * @returns {string}
+ */
+const createLanguageNode = (langName, langColor) => {
+ return `
+
+
+ ${langName}
+
+ `;
+};
+
+const ICON_SIZE = 16;
+const iconWithLabel = (icon, label, testid) => {
+ if (label <= 0) return "";
+ const iconSvg = `
+
+ `;
+ const text = `${label}`;
+ return flexLayout({ items: [iconSvg, text], gap: 20 }).join("");
+};
+
+/**
+ * @param {import('../fetchers/types').RepositoryData} repo
+ * @param {Partial} 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) => `${encodeHTML(line)}`)
+ .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) => `
-
-
-
- ${label}
-
-
- `;
-
const svgLanguage = primaryLanguage
- ? `
-
-
- ${langName}
-
- `
+ ? createLanguageNode(langName, langColor)
: "";
- const iconWithLabel = (icon, label, testid) => {
- return `
-
- ${label}
- `;
- };
- 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)
: ""
}
- ${multiLineDescription
- .map((line) => `${encodeHTML(line)}`)
- .join("")}
+ ${descriptionSvg}
-
- ${svgLanguage}
-
-
- ${starAndForkCount}
-
+
+ ${starAndForkCount}
`);
};
diff --git a/src/cards/stats-card.js b/src/cards/stats-card.js
index 99582e0a..30056b85 100644
--- a/src/cards/stats-card.js
+++ b/src/cards/stats-card.js
@@ -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 `
${iconSvg}
- ${label}:
- ${label}:
+ ${kValue}
`;
};
+/**
+ * @param {Partial} stats
+ * @param {Partial} 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
- ? ""
- : `
-
-
-
-
- ${rank.level}
-
-
- `;
-
// 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
+ ? ""
+ : `
+
+
+
+
+ ${rank.level}
+
+
+ `;
+
+ // 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}
-
+
`);
};
diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js
index f281b125..b2f5c362 100644
--- a/src/cards/top-languages-card.js
+++ b/src/cards/top-languages-card.js
@@ -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 `
-
+
${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) => {
${compactProgressBar}
- ${createLanguageTextNode({
- x: 0,
- y: 25,
- langs,
- totalSize: totalLanguageSize,
- }).join("")}
+
+
+ ${createLanguageTextNode({
+ langs,
+ totalSize: totalLanguageSize,
+ })}
+
`;
};
@@ -154,6 +196,12 @@ const calculateNormalLayoutHeight = (totalLangs) => {
return 45 + (totalLangs + 1) * 40;
};
+/**
+ *
+ * @param {Record} 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} 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;
diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts
new file mode 100644
index 00000000..46bf6659
--- /dev/null
+++ b/src/cards/types.d.ts
@@ -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;
+};
diff --git a/src/cards/wakatime-card.js b/src/cards/wakatime-card.js
index 5e12cf39..5ab2f29d 100644
--- a/src/cards/wakatime-card.js
+++ b/src/cards/wakatime-card.js
@@ -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}
`;
};
+/**
+ *
+ * @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 `
- ${label}:
+ ${label}:${value}
${cardProgress}
`;
};
+/**
+ * @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} stats
+ * @param {Partial} 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: [] }) => {
${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("");
diff --git a/src/common/Card.js b/src/common/Card.js
index 520560f9..2cc84553 100644
--- a/src/common/Card.js
+++ b/src/common/Card.js
@@ -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?=} 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 {
? `
${gradients.map((grad, index) => {
let offset = (index * 100) / (gradients.length - 1);
@@ -114,6 +147,9 @@ class Card {
: "";
}
+ /**
+ * @param {string} body
+ */
render(body) {
return `