forked from mirrored/github-readme-stats
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c624fe9507 | ||
|
|
bcda6ce01e | ||
|
|
af041ba9b8 | ||
|
|
a7d3aaa7b4 | ||
|
|
1617c3fb22 |
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
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 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)**
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
Q: Language Card is incorrect
|
||||
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 impossible. The only way is to deploy on your own instance & use your own PAT (Personal Access Token)
|
||||
-->
|
||||
@@ -1,58 +0,0 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve.
|
||||
labels:
|
||||
- "bug"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
:warning: PLEASE FIRST READ THE FAQ [(#1770)](https://github.com/anuraghazra/github-readme-stats/discussions/1770) AND COMMON ERROR CODES [(#1772)](https://github.com/anuraghazra/github-readme-stats/issues/1772)!!!
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behaviour
|
||||
description:
|
||||
A clear and concise description of what you expected to happen.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Screenshots / Live demo link
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
placeholder: Paste the github-readme-stats link as markdown image
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
---
|
||||
### FAQ (Snippet)
|
||||
|
||||
Below are some questions that are found in the FAQ. The full FAQ can be found in [#1770](https://github.com/anuraghazra/github-readme-stats/discussions/1770).
|
||||
|
||||
#### 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`.
|
||||
|
||||
#### Q: I could not figure out how to deploy on my own vercel instance
|
||||
|
||||
**Ans:** Please check:
|
||||
- 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 these 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 impossible. The only way is to deploy on your own instance & use your own PAT (Personal Access Token).
|
||||
@@ -1,12 +0,0 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Question
|
||||
url: https://github.com/anuraghazra/github-readme-stats/discussions
|
||||
about: Please ask and answer questions here.
|
||||
- name: Error
|
||||
url: https://github.com/anuraghazra/github-readme-stats/issues/1772
|
||||
about:
|
||||
Before opening a bug report, please check the 'Common Error Codes' issue.
|
||||
- name: FAQ
|
||||
url: https://github.com/anuraghazra/github-readme-stats/discussions/1770
|
||||
about: Please first check the FAQ before asking a question.
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ""
|
||||
labels: ""
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
<!--
|
||||
|
||||
PLEASE READ THE FAQs!!!
|
||||
|
||||
Q: How to hide jupyter Notebook?
|
||||
Ans: &hide=jupyter%20notebook
|
||||
|
||||
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
|
||||
|
||||
Q: Language Card is incorrect
|
||||
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)
|
||||
|
||||
-->
|
||||
@@ -1,26 +0,0 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project.
|
||||
labels:
|
||||
- "enhancement"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description:
|
||||
A clear and concise description of what the problem is. Ex. I'm always
|
||||
frustrated when [...]
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description:
|
||||
A clear and concise description of any alternative solutions or features
|
||||
you've considered.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description:
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -1 +0,0 @@
|
||||
_
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm test
|
||||
npx lint-staged
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as dotenv from "dotenv";
|
||||
import { renderStatsCard } from "../src/cards/stats-card.js";
|
||||
import { blacklist } from "../src/common/blacklist.js";
|
||||
import { renderStatsCard } from "../src/cards/stats-card";
|
||||
import { blacklist } from "../src/common/blacklist";
|
||||
import {
|
||||
clampValue,
|
||||
CONSTANTS,
|
||||
parseArray,
|
||||
parseBoolean,
|
||||
renderError,
|
||||
} from "../src/common/utils.js";
|
||||
import { fetchStats } from "../src/fetchers/stats-fetcher.js";
|
||||
import { isLocaleAvailable } from "../src/translations.js";
|
||||
} from "../src/common/utils";
|
||||
import { fetchStats } from "../src/fetchers/stats-fetcher";
|
||||
import { isLocaleAvailable } from "../src/translations";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -89,7 +89,6 @@ export default async (req, res) => {
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
res.setHeader("Cache-Control", `no-store`); // Don't cache error responses.
|
||||
return res.send(renderError(err.message, err.secondaryMessage));
|
||||
}
|
||||
};
|
||||
@@ -1,13 +1,13 @@
|
||||
import { renderRepoCard } from "../src/cards/repo-card.js";
|
||||
import { blacklist } from "../src/common/blacklist.js";
|
||||
import { renderRepoCard } from "../src/cards/repo-card";
|
||||
import { blacklist } from "../src/common/blacklist";
|
||||
import {
|
||||
clampValue,
|
||||
CONSTANTS,
|
||||
parseBoolean,
|
||||
renderError,
|
||||
} from "../src/common/utils.js";
|
||||
import { fetchRepo } from "../src/fetchers/repo-fetcher.js";
|
||||
import { isLocaleAvailable } from "../src/translations.js";
|
||||
} from "../src/common/utils";
|
||||
import { fetchRepo } from "../src/fetchers/repo-fetcher";
|
||||
import { isLocaleAvailable } from "../src/translations";
|
||||
|
||||
export default async (req, res) => {
|
||||
const {
|
||||
@@ -75,7 +75,6 @@ export default async (req, res) => {
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
res.setHeader("Cache-Control", `no-store`); // Don't cache error responses.
|
||||
return res.send(renderError(err.message, err.secondaryMessage));
|
||||
}
|
||||
};
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as dotenv from "dotenv";
|
||||
import { renderTopLanguages } from "../src/cards/top-languages-card.js";
|
||||
import { blacklist } from "../src/common/blacklist.js";
|
||||
import { renderTopLanguages } from "../src/cards/top-languages-card";
|
||||
import { blacklist } from "../src/common/blacklist";
|
||||
import {
|
||||
clampValue,
|
||||
CONSTANTS,
|
||||
parseArray,
|
||||
parseBoolean,
|
||||
renderError,
|
||||
} from "../src/common/utils.js";
|
||||
import { fetchTopLanguages } from "../src/fetchers/top-languages-fetcher.js";
|
||||
import { isLocaleAvailable } from "../src/translations.js";
|
||||
} from "../src/common/utils";
|
||||
import { fetchTopLanguages } from "../src/fetchers/top-languages-fetcher";
|
||||
import { isLocaleAvailable } from "../src/translations";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -76,7 +76,6 @@ export default async (req, res) => {
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
res.setHeader("Cache-Control", `no-store`); // Don't cache error responses.
|
||||
return res.send(renderError(err.message, err.secondaryMessage));
|
||||
}
|
||||
};
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as dotenv from "dotenv";
|
||||
import { renderWakatimeCard } from "../src/cards/wakatime-card.js";
|
||||
import { renderWakatimeCard } from "../src/cards/wakatime-card";
|
||||
import {
|
||||
clampValue,
|
||||
CONSTANTS,
|
||||
parseArray,
|
||||
parseBoolean,
|
||||
renderError,
|
||||
} from "../src/common/utils.js";
|
||||
import { fetchWakatimeStats } from "../src/fetchers/wakatime-fetcher.js";
|
||||
import { isLocaleAvailable } from "../src/translations.js";
|
||||
} from "../src/common/utils";
|
||||
import { fetchWakatimeStats } from "../src/fetchers/wakatime-fetcher";
|
||||
import { isLocaleAvailable } from "../src/translations";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -78,7 +78,6 @@ export default async (req, res) => {
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
res.setHeader("Cache-Control", `no-store`); // Don't cache error responses.
|
||||
return res.send(renderError(err.message, err.secondaryMessage));
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
["@babel/preset-env", { targets: { node: "current" } }],
|
||||
"@babel/preset-typescript",
|
||||
],
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
export default {
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
clearMocks: true,
|
||||
transform: {},
|
||||
testEnvironment: "jsdom",
|
||||
coverageProvider: "v8",
|
||||
testPathIgnorePatterns: ["<rootDir>/node_modules/", "<rootDir>/tests/e2e/"],
|
||||
modulePathIgnorePatterns: ["<rootDir>/node_modules/", "<rootDir>/tests/e2e/"],
|
||||
coveragePathIgnorePatterns: [
|
||||
@@ -0,0 +1,7 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
clearMocks: true,
|
||||
testMatch: ["<rootDir>/tests/e2e/**/*.test.ts"],
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
export default {
|
||||
clearMocks: true,
|
||||
transform: {},
|
||||
testEnvironment: "node",
|
||||
coverageProvider: "v8",
|
||||
testMatch: ["<rootDir>/tests/e2e/**/*.test.js"],
|
||||
};
|
||||
Generated
+5262
-1504
File diff suppressed because it is too large
Load Diff
+24
-29
@@ -1,55 +1,48 @@
|
||||
{
|
||||
"name": "github-readme-stats",
|
||||
"version": "1.0.0",
|
||||
"description": "Dynamically generate stats for your GitHub readme",
|
||||
"keywords": [
|
||||
"github-readme-stats",
|
||||
"readme-stats",
|
||||
"cards",
|
||||
"card-generator"
|
||||
],
|
||||
"main": "src/index.js",
|
||||
"description": "Dynamically generate stats for your github readmes",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"homepage": "https://github.com/anuraghazra/github-readme-stats",
|
||||
"bugs": {
|
||||
"url": "https://github.com/anuraghazra/github-readme-stats/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/anuraghazra/github-readme-stats.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
|
||||
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
|
||||
"test:update:snapshot": "node --experimental-vm-modules node_modules/jest/bin/jest.js -u",
|
||||
"test:e2e": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.e2e.config.js",
|
||||
"test": "jest --coverage",
|
||||
"test:watch": "jest --watch",
|
||||
"test:update:snapshot": "jest -u",
|
||||
"test:e2e": "jest --config jest.e2e.config.cjs",
|
||||
"theme-readme-gen": "node scripts/generate-theme-doc",
|
||||
"preview-theme": "node scripts/preview-theme",
|
||||
"close-stale-theme-prs": "node scripts/close-stale-theme-prs",
|
||||
"generate-langs-json": "node scripts/generate-langs-json",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"prepare": "husky install"
|
||||
"format": "./node_modules/.bin/prettier --write .",
|
||||
"format:check": "./node_modules/.bin/prettier --check .",
|
||||
"start": "vercel dev"
|
||||
},
|
||||
"author": "Anurag Hazra",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.9.1",
|
||||
"@actions/github": "^4.0.0",
|
||||
"@babel/core": "^7.19.3",
|
||||
"@babel/preset-env": "^7.19.3",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@testing-library/dom": "^8.17.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@types/jest": "^29.1.1",
|
||||
"@uppercod/css-to-object": "^1.1.1",
|
||||
"@vercel/node": "^2.5.21",
|
||||
"axios-mock-adapter": "^1.18.1",
|
||||
"babel-jest": "^29.1.2",
|
||||
"color-contrast-checker": "^2.1.0",
|
||||
"hjson": "^3.2.2",
|
||||
"husky": "^8.0.0",
|
||||
"jest": "^29.0.3",
|
||||
"husky": "^4.2.5",
|
||||
"jest": "^29.1.2",
|
||||
"jest-environment-jsdom": "^29.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"lodash.snakecase": "^4.1.1",
|
||||
"parse-diff": "^0.7.0",
|
||||
"prettier": "^2.1.2"
|
||||
"prettier": "^2.1.2",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.24.0",
|
||||
@@ -59,7 +52,9 @@
|
||||
"upgrade": "^1.1.0",
|
||||
"word-wrap": "^1.2.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,css,md}": "prettier --write"
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "npm test"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,8 +62,7 @@
|
||||
<a href="/docs/readme_tr.md">Türkçe</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
<p align="center">Love the project? Please consider <a href="https://www.paypal.me/anuraghazra">donating</a> to help it improve!</p>
|
||||
<p align="center">Love the project? Please consider <a href="https://www.paypal.me/anuraghazra">donating</a> to help it improve!
|
||||
|
||||
<a href="https://indiafightscorona.giveindia.org">
|
||||
<img src="https://indiaspora.org/wp-content/uploads/2021/04/give-India-logo.png" alt="Give india logo" width="200" />
|
||||
@@ -92,7 +91,6 @@ Visit <https://indiafightscorona.giveindia.org> and make a small donation to hel
|
||||
- [Language Card Exclusive Options](#language-card-exclusive-options)
|
||||
- [Wakatime Card Exclusive Option](#wakatime-card-exclusive-options)
|
||||
- [Deploy Yourself](#deploy-on-your-own-vercel-instance)
|
||||
- [Keep your fork up to date](#keep-your-fork-up-to-date)
|
||||
|
||||
# GitHub Stats Card
|
||||
|
||||
@@ -104,8 +102,9 @@ Change the `?username=` value to your GitHub username.
|
||||
[](https://github.com/anuraghazra/github-readme-stats)
|
||||
```
|
||||
|
||||
> **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).
|
||||
_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)._
|
||||
|
||||
### Hiding individual stats
|
||||
|
||||
@@ -121,8 +120,7 @@ You can pass a query parameter `&hide=` to hide any specific stats with comma-se
|
||||
|
||||
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. If you are using the public Vercel instance, you need to choose to [share your private contributions](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/managing-contribution-settings-on-your-profile/showing-your-private-contributions-and-achievements-on-your-profile).
|
||||
_Note: If you are deploying this project yourself, the private contributions will be counted by default. If you are using the public Vercel instance, you need to choose to [share your private contributions](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/managing-contribution-settings-on-your-profile/showing-your-private-contributions-and-achievements-on-your-profile)._
|
||||
|
||||
> Options: `&count_private=true`
|
||||
|
||||
@@ -173,8 +171,7 @@ You can customize the appearance of your `Stats Card` or `Repo Card` however you
|
||||
- `locale` - set the language in the card _(e.g. cn, de, es, etc.)_. Default: `en`.
|
||||
- `border_radius` - Corner rounding on the card. Default: `4.5`.
|
||||
|
||||
> **Warning**
|
||||
> We use caching to decrease the load on our servers (see https://github.com/anuraghazra/github-readme-stats/issues/1471#issuecomment-1271551425). Our cards have a default cache of 4 hours (14400 seconds). Also, note that the cache is clamped to a minimum of 4 hours and a maximum of 24 hours.
|
||||
> Note: The minimum of cache_seconds is currently 4 hours as a temporary fix for PATs exhaustion.
|
||||
|
||||
##### Gradient in bg_color
|
||||
|
||||
@@ -182,6 +179,8 @@ You can provide multiple comma-separated values in the bg_color option to render
|
||||
|
||||
&bg_color=DEG,COLOR1,COLOR2,COLOR3...COLOR10
|
||||
|
||||
> Note on cache: Repo cards have a default cache of 4 hours (14400 seconds) if the fork count & star count is less than 1k; otherwise, it is 2 hours (7200 seconds). Also, note that the cache is clamped to a minimum of 2 hours and a maximum of 24 hours.
|
||||
|
||||
#### Stats Card Exclusive Options
|
||||
|
||||
- `hide` - Hides the [specified items](#hiding-individual-stats) from stats _(Comma-separated values)_. Default: `[] (blank array)`.
|
||||
@@ -197,7 +196,7 @@ You can provide multiple comma-separated values in the bg_color option to render
|
||||
- `text_bold` - Use bold text _(boolean)_. Default: `true`.
|
||||
- `disable_animations` - Disables all animations in the card _(boolean)_. Default: `false`.
|
||||
|
||||
> **Note**
|
||||
> Note on `hide_rank`:
|
||||
> When hide_rank=`true`, the minimum card width is 270 px + the title length and padding.
|
||||
|
||||
#### Repo Card Exclusive Options
|
||||
@@ -214,7 +213,7 @@ You can provide multiple comma-separated values in the bg_color option to render
|
||||
- `exclude_repo` - Exclude specified repositories _(Comma-separated values)_. Default: `[] (blank array)`.
|
||||
- `custom_title` - Sets a custom title for the card _(string)_. Default `Most Used Languages`.
|
||||
|
||||
> **Warning**
|
||||
> :warning: **Important:**
|
||||
> Language names should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding)
|
||||
> (i.e: `c++` should become `c%2B%2B`, `jupyter notebook` should become `jupyter%20notebook`, etc.) You can use
|
||||
> [urlencoder.org](https://www.urlencoder.org/) to help you do this automatically.
|
||||
@@ -261,8 +260,7 @@ Use [show_owner](#customization) variable to include the repo's owner username
|
||||
|
||||
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 to determine which languages have the most code on GitHub. It is 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 is a new feature of github-readme-stats._
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -322,8 +320,7 @@ 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.
|
||||
> Note: Please be aware that we currently only show data from Wakatime profiles that are public.
|
||||
|
||||
### Demo
|
||||
|
||||
@@ -408,18 +405,14 @@ By default, GitHub does not lay out the cards side by side. To do that, you can
|
||||
|
||||
#### [Check Out Step By Step Video Tutorial By @codeSTACKr](https://youtu.be/n6d4KHSKqGk?t=107)
|
||||
|
||||
> **Warning**
|
||||
> If you are on the [hobby (i.e. free)](https://vercel.com/pricing) Vercel plan, please make sure you change the `maxDuration` parameter in the [vercel.json](https://github.com/anuraghazra/github-readme-stats/blob/master/vercel.json) file from `30` to `10` (see [#1416](https://github.com/anuraghazra/github-readme-stats/issues/1416#issuecomment-950275476) for more information).
|
||||
|
||||
Since the GitHub API only allows 5k requests per hour, my `https://github-readme-stats.vercel.app/api` could possibly hit the rate limiter. If you host it on your own Vercel server, then you do not have to worry about anything. Click on the deploy button to get started!
|
||||
|
||||
> **Note**
|
||||
> Since [#58](https://github.com/anuraghazra/github-readme-stats/pull/58), we should be able to handle more than 5k requests and have fewer issues with downtime :grin:.
|
||||
NOTE: Since [#58](https://github.com/anuraghazra/github-readme-stats/pull/58) we should be able to handle more than 5k requests and have no issues with downtime :D
|
||||
|
||||
[](https://vercel.com/import/project?template=https://github.com/anuraghazra/github-readme-stats)
|
||||
|
||||
<details>
|
||||
<summary><b>:hammer_and_wrench: Step-by-step guide on setting up your own Vercel instance</b></summary>
|
||||
<summary><b> Guide on setting up Vercel 🔨 </b></summary>
|
||||
|
||||
1. Go to [vercel.com](https://vercel.com/).
|
||||
2. Click on `Log in`.
|
||||
@@ -441,10 +434,6 @@ Since the GitHub API only allows 5k requests per hour, my `https://github-readme
|
||||
|
||||
</details>
|
||||
|
||||
### Keep your fork up to date
|
||||
|
||||
You can keep your fork, and thus your private Vercel instance up to date with the upstream using GitHubs' [Sync Fork button](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork). You can also use the [pull](https://github.com/wei/pull) package created by [@wei](https://github.com/wei) to automate this process.
|
||||
|
||||
## :sparkling_heart: Support the project
|
||||
|
||||
I open-source almost everything I can and try to reply to everyone needing help using these projects. Obviously,
|
||||
|
||||
@@ -24,7 +24,7 @@ const THEME_PR_FAIL_TEXT = ":x: Theme PR does not adhere to our guidelines.";
|
||||
const THEME_PR_SUCCESS_TEXT =
|
||||
":heavy_check_mark: Theme PR does adhere to our guidelines.";
|
||||
const FAIL_TEXT = `
|
||||
\rUnfortunately, your theme PR contains an error or does not adhere to our [theme guidelines](https://github.com/anuraghazra/github-readme-stats/blob/master/CONTRIBUTING.md#themes-contribution). Please fix the issues below, and we will review your\
|
||||
\rUnfortunately, your theme PR does not adhere to our [theme guidelines](https://github.com/anuraghazra/github-readme-stats/blob/master/CONTRIBUTING.md#themes-contribution). Please fix the issues below, and we will review your\
|
||||
\r PR again. This pull request will **automatically close in 15 days** if no changes are made. After this time, you must re-open the PR for it to be reviewed.
|
||||
`;
|
||||
const THEME_CONTRIB_GUIDELINESS = `
|
||||
@@ -558,28 +558,6 @@ export const run = async (prNumber) => {
|
||||
info(`DRY_RUN: Review reason: ${reviewReason}`);
|
||||
}
|
||||
} catch (error) {
|
||||
debug("Set review state to `REQUEST_CHANGES` and add `invalid` label...");
|
||||
if (!dryRun) {
|
||||
await addReview(
|
||||
octokit,
|
||||
pullRequestId,
|
||||
owner,
|
||||
repo,
|
||||
"REQUEST_CHANGES",
|
||||
error.message,
|
||||
);
|
||||
await addRemoveLabel(
|
||||
octokit,
|
||||
pullRequestId,
|
||||
owner,
|
||||
repo,
|
||||
"invalid",
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
info(`DRY_RUN: Review state: REQUEST_CHANGES`);
|
||||
info(`DRY_RUN: Review reason: ${error.message}`);
|
||||
}
|
||||
setFailed(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
*
|
||||
* @see https://stackoverflow.com/a/5263759/10629172
|
||||
*
|
||||
* @param {string} mean The mean of the normal distribution.
|
||||
* @param {number} sigma The standard deviation of the normal distribution.
|
||||
* @param {number} to The value to calculate the probability for.
|
||||
* @param {string} mean
|
||||
* @param {number} sigma
|
||||
* @param {number} to
|
||||
* @returns {number} Probability.
|
||||
*/
|
||||
function normalcdf(mean, sigma, to) {
|
||||
@@ -29,13 +29,13 @@ function normalcdf(mean, sigma, to) {
|
||||
/**
|
||||
* Calculates the users rank.
|
||||
*
|
||||
* @param {number} totalRepos Total number of repos.
|
||||
* @param {number} totalCommits Total number of commits.
|
||||
* @param {number} contributions The number of contributions.
|
||||
* @param {number} followers The number of followers.
|
||||
* @param {number} prs The number of pull requests.
|
||||
* @param {number} issues The number of issues.
|
||||
* @param {number} stargazers The number of stars.
|
||||
* @param {number} totalRepos
|
||||
* @param {number} totalCommits
|
||||
* @param {number} contributions
|
||||
* @param {number} followers
|
||||
* @param {number} prs
|
||||
* @param {number} issues
|
||||
* @param {number} stargazers
|
||||
* @returns {{level: string, score: number}}} The users rank.
|
||||
*/
|
||||
function calculateRank({
|
||||
@@ -1,4 +0,0 @@
|
||||
export { renderRepoCard } from "./repo-card.js";
|
||||
export { renderStatsCard } from "./stats-card.js";
|
||||
export { renderTopLanguages } from "./top-languages-card.js";
|
||||
export { renderWakatimeCard } from "./wakatime-card.js";
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-check
|
||||
import { Card } from "../common/Card.js";
|
||||
import { I18n } from "../common/I18n.js";
|
||||
import { icons } from "../common/icons.js";
|
||||
import { Card } from "../common/Card";
|
||||
import { I18n } from "../common/I18n";
|
||||
import { icons } from "../common/icons";
|
||||
import {
|
||||
encodeHTML,
|
||||
flexLayout,
|
||||
@@ -10,15 +10,13 @@ import {
|
||||
measureText,
|
||||
parseEmojis,
|
||||
wrapTextMultiline,
|
||||
} from "../common/utils.js";
|
||||
import { repoCardLocales } from "../translations.js";
|
||||
} from "../common/utils";
|
||||
import { repoCardLocales } from "../translations";
|
||||
|
||||
/**
|
||||
* Retrieves the repository description and wraps it to fit the card width.
|
||||
*
|
||||
* @param {string} label The repository description.
|
||||
* @param {string} textColor The color of the text.
|
||||
* @returns {string} Wrapped repo description SVG object.
|
||||
* @param {string} label
|
||||
* @param {string} textColor
|
||||
* @returns {string}
|
||||
*/
|
||||
const getBadgeSVG = (label, textColor) => `
|
||||
<g data-testid="badge" class="badge" transform="translate(320, -18)">
|
||||
@@ -36,11 +34,9 @@ const getBadgeSVG = (label, textColor) => `
|
||||
`;
|
||||
|
||||
/**
|
||||
* Creates a node to display the primary programming language of the repository.
|
||||
*
|
||||
* @param {string} langName Language name.
|
||||
* @param {string} langColor Language color.
|
||||
* @returns {string} Language display SVG object.
|
||||
* @param {string} langName
|
||||
* @param {string} langColor
|
||||
* @returns {string}
|
||||
*/
|
||||
const createLanguageNode = (langName, langColor) => {
|
||||
return `
|
||||
@@ -52,15 +48,6 @@ const createLanguageNode = (langName, langColor) => {
|
||||
};
|
||||
|
||||
const ICON_SIZE = 16;
|
||||
|
||||
/**
|
||||
* Creates an icon with label to display repository stats like forks, stars, etc.
|
||||
*
|
||||
* @param {string} icon The icon to display.
|
||||
* @param {number|string} label The label to display.
|
||||
* @param {string} testid The testid to assign to the label.
|
||||
* @returns {string} Icon with label SVG object.
|
||||
*/
|
||||
const iconWithLabel = (icon, label, testid) => {
|
||||
if (label <= 0) return "";
|
||||
const iconSvg = `
|
||||
@@ -80,11 +67,9 @@ const iconWithLabel = (icon, label, testid) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders repository card details.
|
||||
*
|
||||
* @param {import('../fetchers/types').RepositoryData} repo Repository data.
|
||||
* @param {Partial<import("./types").RepoCardOptions>} options Card options.
|
||||
* @returns {string} Repository card SVG object.
|
||||
* @param {import('../fetchers/types').RepositoryData} repo
|
||||
* @param {Partial<import("./types").RepoCardOptions>} options
|
||||
* @returns {string}
|
||||
*/
|
||||
const renderRepoCard = (repo, options = {}) => {
|
||||
const {
|
||||
@@ -1,16 +1,16 @@
|
||||
// @ts-check
|
||||
import { Card } from "../common/Card.js";
|
||||
import { I18n } from "../common/I18n.js";
|
||||
import { icons } from "../common/icons.js";
|
||||
import { Card } from "../common/Card";
|
||||
import { I18n } from "../common/I18n";
|
||||
import { icons } from "../common/icons";
|
||||
import {
|
||||
clampValue,
|
||||
flexLayout,
|
||||
getCardColors,
|
||||
kFormatter,
|
||||
measureText,
|
||||
} from "../common/utils.js";
|
||||
import { getStyles } from "../getStyles.js";
|
||||
import { statCardLocales } from "../translations.js";
|
||||
} from "../common/utils";
|
||||
import { getStyles } from "../getStyles";
|
||||
import { statCardLocales } from "../translations";
|
||||
|
||||
/**
|
||||
* Create a stats card text item.
|
||||
@@ -63,11 +63,9 @@ const createTextNode = ({
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the stats card.
|
||||
*
|
||||
* @param {Partial<import('../fetchers/types').StatsData>} stats The stats data.
|
||||
* @param {Partial<import("./types").StatCardOptions>} options The card options.
|
||||
* @returns {string} The stats card SVG object.
|
||||
* @param {Partial<import('../fetchers/types').StatsData>} stats
|
||||
* @param {Partial<import("./types").StatCardOptions>} options
|
||||
* @returns {string}
|
||||
*/
|
||||
const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
||||
const {
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-check
|
||||
import { Card } from "../common/Card.js";
|
||||
import { createProgressNode } from "../common/createProgressNode.js";
|
||||
import { I18n } from "../common/I18n.js";
|
||||
import { Card } from "../common/Card";
|
||||
import { createProgressNode } from "../common/createProgressNode";
|
||||
import { I18n } from "../common/I18n";
|
||||
import {
|
||||
chunkArray,
|
||||
clampValue,
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
getCardColors,
|
||||
lowercaseTrim,
|
||||
measureText,
|
||||
} from "../common/utils.js";
|
||||
import { langCardLocales } from "../translations.js";
|
||||
} from "../common/utils";
|
||||
import { langCardLocales } from "../translations";
|
||||
|
||||
const DEFAULT_CARD_WIDTH = 300;
|
||||
const MIN_CARD_WIDTH = 230;
|
||||
@@ -23,10 +23,7 @@ const CARD_PADDING = 25;
|
||||
*/
|
||||
|
||||
/**
|
||||
* Retrieves the programming language whose name is the longest.
|
||||
*
|
||||
* @param {Lang[]} arr Array of programming languages.
|
||||
* @returns {Object} Longest programming language object.
|
||||
* @param {Lang[]} arr
|
||||
*/
|
||||
const getLongestLang = (arr) =>
|
||||
arr.reduce(
|
||||
@@ -36,15 +33,12 @@ const getLongestLang = (arr) =>
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates a node to display usage of a programming language in percentage
|
||||
* using text and a horizontal progress bar.
|
||||
*
|
||||
* @param {object[]} props Function properties.
|
||||
* @param {number} props.width The card width
|
||||
* @param {string} props.name Name of the programming language.
|
||||
* @param {string} props.color Color of the programming language.
|
||||
* @param {string} props.progress Usage of the programming language in percentage.
|
||||
* @returns {string} Programming language SVG node.
|
||||
* @param {{
|
||||
* width: number,
|
||||
* color: string,
|
||||
* name: string,
|
||||
* progress: string
|
||||
* }} props
|
||||
*/
|
||||
const createProgressTextNode = ({ width, color, name, progress }) => {
|
||||
const paddingRight = 95;
|
||||
@@ -66,12 +60,7 @@ const createProgressTextNode = ({ width, color, name, progress }) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a text only node to display usage of a programming language in percentage.
|
||||
*
|
||||
* @param {object[]} props Function properties.
|
||||
* @param {Lang} props.lang Programming language object.
|
||||
* @param {number} props.totalSize Total size of all languages.
|
||||
* @returns {string} Compact layout programming language SVG node.
|
||||
* @param {{ lang: Lang, totalSize: number }} props
|
||||
*/
|
||||
const createCompactLangNode = ({ lang, totalSize }) => {
|
||||
const percentage = ((lang.size / totalSize) * 100).toFixed(2);
|
||||
@@ -88,12 +77,7 @@ const createCompactLangNode = ({ lang, totalSize }) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates compact layout of text only language nodes.
|
||||
*
|
||||
* @param {object[]} props Function properties.
|
||||
* @param {Lang[]} props.langs Array of programming languages.
|
||||
* @param {number} props.totalSize Total size of all languages.
|
||||
* @returns {string} Programming languages SVG node.
|
||||
* @param {{ langs: Lang[], totalSize: number }} props
|
||||
*/
|
||||
const createLanguageTextNode = ({ langs, totalSize }) => {
|
||||
const longestLang = getLongestLang(langs);
|
||||
@@ -125,12 +109,10 @@ const createLanguageTextNode = ({ langs, totalSize }) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders layout to display user's most frequently used programming languages.
|
||||
*
|
||||
* @param {Lang[]} langs Array of programming languages.
|
||||
* @param {number} width Card width.
|
||||
* @param {number} totalLanguageSize Total size of all languages.
|
||||
* @returns {string} Normal layout card SVG object.
|
||||
* @param {Lang[]} langs
|
||||
* @param {number} width
|
||||
* @param {number} totalLanguageSize
|
||||
* @returns {string}
|
||||
*/
|
||||
const renderNormalLayout = (langs, width, totalLanguageSize) => {
|
||||
return flexLayout({
|
||||
@@ -148,12 +130,10 @@ const renderNormalLayout = (langs, width, totalLanguageSize) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders compact layout to display user's most frequently used programming languages.
|
||||
*
|
||||
* @param {Lang[]} langs Array of programming languages.
|
||||
* @param {number} width Card width.
|
||||
* @param {number} totalLanguageSize Total size of all languages.
|
||||
* @returns {string} Compact layout card SVG object.
|
||||
* @param {Lang[]} langs
|
||||
* @param {number} width
|
||||
* @param {number} totalLanguageSize
|
||||
* @returns {string}
|
||||
*/
|
||||
const renderCompactLayout = (langs, width, totalLanguageSize) => {
|
||||
const paddingRight = 50;
|
||||
@@ -201,31 +181,26 @@ const renderCompactLayout = (langs, width, totalLanguageSize) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates height for the compact layout.
|
||||
*
|
||||
* @param {number} totalLangs Total number of languages.
|
||||
* @returns {number} Card height.
|
||||
* @param {number} totalLangs
|
||||
* @returns {number}
|
||||
*/
|
||||
const calculateCompactLayoutHeight = (totalLangs) => {
|
||||
return 90 + Math.round(totalLangs / 2) * 25;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates height for the normal layout.
|
||||
*
|
||||
* @param {number} totalLangs Total number of languages.
|
||||
* @returns {number} Card height.
|
||||
* @param {number} totalLangs
|
||||
* @returns {number}
|
||||
*/
|
||||
const calculateNormalLayoutHeight = (totalLangs) => {
|
||||
return 45 + (totalLangs + 1) * 40;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides languages and trims the list to show only the top N languages.
|
||||
*
|
||||
* @param {Record<string, Lang>} topLangs Top languages.
|
||||
* @param {string[]} hide Languages to hide.
|
||||
* @param {string} langs_count Number of languages to show.
|
||||
* @param {Record<string, Lang>} topLangs
|
||||
* @param {string[]} hide
|
||||
* @param {string} langs_count
|
||||
*/
|
||||
const useLanguages = (topLangs, hide, langs_count) => {
|
||||
let langs = Object.values(topLangs);
|
||||
@@ -254,11 +229,9 @@ const useLanguages = (topLangs, hide, langs_count) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders card to display user's most frequently used programming languages.
|
||||
*
|
||||
* @param {import('../fetchers/types').TopLangData} topLangs User's most frequently used programming languages.
|
||||
* @param {Partial<import("./types").TopLangOptions>} options Card options.
|
||||
* @returns {string} Language card SVG object.
|
||||
* @param {import('../fetchers/types').TopLangData} topLangs
|
||||
* @param {Partial<import("./types").TopLangOptions>} options
|
||||
* @returns {string}
|
||||
*/
|
||||
const renderTopLanguages = (topLangs, options = {}) => {
|
||||
const {
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
type ThemeNames = keyof typeof import("../../themes/index.js");
|
||||
type ThemeNames = keyof typeof import("../../themes/index");
|
||||
|
||||
export type CommonOptions = {
|
||||
title_color: string;
|
||||
|
||||
@@ -1,31 +1,20 @@
|
||||
// @ts-check
|
||||
import { Card } from "../common/Card.js";
|
||||
import { createProgressNode } from "../common/createProgressNode.js";
|
||||
import { I18n } from "../common/I18n.js";
|
||||
import { Card } from "../common/Card";
|
||||
import { createProgressNode } from "../common/createProgressNode";
|
||||
import { I18n } from "../common/I18n";
|
||||
import {
|
||||
clampValue,
|
||||
flexLayout,
|
||||
getCardColors,
|
||||
lowercaseTrim,
|
||||
} from "../common/utils.js";
|
||||
import { getStyles } from "../getStyles.js";
|
||||
import { wakatimeCardLocales } from "../translations.js";
|
||||
} from "../common/utils";
|
||||
import { getStyles } from "../getStyles";
|
||||
import { wakatimeCardLocales } from "../translations";
|
||||
|
||||
/** Import language colors.
|
||||
*
|
||||
* @description Here we use the workaround found in
|
||||
* https://stackoverflow.com/questions/66726365/how-should-i-import-json-in-node
|
||||
* since vercel is using v16.14.0 which does not yet support json imports without the
|
||||
* --experimental-json-modules flag.
|
||||
*/
|
||||
import { createRequire } from "module";
|
||||
const require = createRequire(import.meta.url);
|
||||
const languageColors = require("../common/languageColors.json"); // now works
|
||||
import languageColors from "../common/languageColors.json";
|
||||
|
||||
/**
|
||||
* Creates the no coding activity SVG node.
|
||||
*
|
||||
* @param {{color: string, text: string}} The function prop
|
||||
* @param {{color: string, text: string}} param0
|
||||
*/
|
||||
const noCodingActivityNode = ({ color, text }) => {
|
||||
return `
|
||||
@@ -34,13 +23,13 @@ const noCodingActivityNode = ({ color, text }) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Create compact WakaTime layout.
|
||||
*
|
||||
* @param {Object[]} args The function arguments.
|
||||
* @param {import("../fetchers/types").WakaTimeLang[]} languages The languages array.
|
||||
* @param {number} totalSize The total size of the languages.
|
||||
* @param {number} x The x position of the language node.
|
||||
* @param {number} y The y position of the language node.
|
||||
* @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";
|
||||
@@ -56,13 +45,12 @@ const createCompactLangNode = ({ lang, totalSize, x, y }) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Create WakaTime language text node item.
|
||||
*
|
||||
* @param {Object[]} args The function arguments.
|
||||
* @param {import("../fetchers/types").WakaTimeLang} lang The language object.
|
||||
* @param {number} totalSize The total size of the languages.
|
||||
* @param {number} x The x position of the language node.
|
||||
* @param {number} y The y position of the language node.
|
||||
* @param {{
|
||||
* langs: import("../fetchers/types").WakaTimeLang[],
|
||||
* totalSize: number,
|
||||
* x: number,
|
||||
* y: number
|
||||
* }} props
|
||||
*/
|
||||
const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
|
||||
return langs.map((lang, index) => {
|
||||
@@ -84,16 +72,17 @@ const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Create WakaTime text item.
|
||||
*
|
||||
* @param {Object[]} args The function arguments.
|
||||
* @param {string} id The id of the text node item.
|
||||
* @param {string} label The label of the text node item.
|
||||
* @param {string} value The value of the text node item.
|
||||
* @param {number} index The index of the text node item.
|
||||
* @param {percent} percent Percentage of the text node item.
|
||||
* @param {boolean} hideProgress Whether to hide the progress bar.
|
||||
* @param {string} progressBarBackgroundColor The color of the progress bar background.
|
||||
* @param {{
|
||||
* id: string;
|
||||
* label: string;
|
||||
* value: string;
|
||||
* index: number;
|
||||
* percent: number;
|
||||
* hideProgress: boolean;
|
||||
* progressBarColor: string;
|
||||
* progressBarBackgroundColor: string
|
||||
* }} props
|
||||
*/
|
||||
const createTextNode = ({
|
||||
id,
|
||||
@@ -134,13 +123,11 @@ const createTextNode = ({
|
||||
};
|
||||
|
||||
/**
|
||||
* Recalculating percentages so that, compact layout's progress bar does not break when
|
||||
* hiding languages.
|
||||
*
|
||||
* @param {import("../fetchers/types").WakaTimeLang[]} languages The languages array.
|
||||
* @return {import("../fetchers/types").WakaTimeLang[]} The recalculated languages array.
|
||||
* @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,
|
||||
@@ -152,11 +139,9 @@ const recalculatePercentages = (languages) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders WakaTime card.
|
||||
*
|
||||
* @param {Partial<import('../fetchers/types').WakaTimeData>} stats WakaTime stats.
|
||||
* @param {Partial<import('./types').WakaTimeOptions>} options Card options.
|
||||
* @returns {string} WakaTime card SVG.
|
||||
* @param {Partial<import('../fetchers/types').WakaTimeData>} stats
|
||||
* @param {Partial<import('./types').WakaTimeOptions>} options
|
||||
* @returns {string}
|
||||
*/
|
||||
const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
||||
let { languages } = stats;
|
||||
@@ -1,18 +1,87 @@
|
||||
import { getAnimations } from "../getStyles.js";
|
||||
import { encodeHTML, flexLayout } from "./utils.js";
|
||||
import { getAnimations } from "../getStyles";
|
||||
import { encodeHTML, flexLayout } from "./utils";
|
||||
|
||||
class Card {
|
||||
/** Card colors. */
|
||||
export interface CardColors {
|
||||
/** Title color. */
|
||||
titleColor: string;
|
||||
/** Text color. */
|
||||
textColor: string;
|
||||
/** Icon color. */
|
||||
iconColor: string;
|
||||
/** Background color. */
|
||||
bgColor: string;
|
||||
/** Border color. */
|
||||
borderColor: string;
|
||||
}
|
||||
|
||||
/** Accessibility label. */
|
||||
interface AccessibilityLabel {
|
||||
/** The label to display. */
|
||||
title: string;
|
||||
/** The value to display. */
|
||||
desc: string;
|
||||
}
|
||||
|
||||
/** Card properties. */
|
||||
interface CardProps {
|
||||
/** Card width. */
|
||||
width: number;
|
||||
/** Card height. */
|
||||
height: number;
|
||||
/** Card border radius. */
|
||||
border_radius: number;
|
||||
/** Card colors. */
|
||||
colors: CardColors | {};
|
||||
/** Card title. */
|
||||
customTitle?: string;
|
||||
/** Card default title. */
|
||||
defaultTitle?: string;
|
||||
/** Card title prefix icon. */
|
||||
titlePrefixIcon?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Card class.
|
||||
*/
|
||||
export class Card {
|
||||
/** Card width. */
|
||||
width: number;
|
||||
/** Card height. */
|
||||
height: number;
|
||||
/** Whether the card border is hidden. */
|
||||
hideBorder: boolean;
|
||||
/** Whether the card title is hidden. */
|
||||
hideTitle: boolean;
|
||||
/** Border radius. */
|
||||
border_radius: number;
|
||||
/** Card colors. */
|
||||
colors: CardColors | {};
|
||||
/** Card title. */
|
||||
title: string;
|
||||
/** Card css. */
|
||||
css: string;
|
||||
/** Card x padding. */
|
||||
paddingX: number;
|
||||
/** Card y padding. */
|
||||
paddingY: number;
|
||||
/** Card title prefix icon. */
|
||||
titlePrefixIcon?: string;
|
||||
/** Whether the card is animated. */
|
||||
animations: boolean;
|
||||
/** Accessibility label title. */
|
||||
a11yTitle: string;
|
||||
/** Accessibility label description. */
|
||||
a11yDesc: string;
|
||||
/**
|
||||
* Creates a new card instance.
|
||||
*
|
||||
* @param {object} args Card arguments.
|
||||
* @param {number?=} args.width Card width.
|
||||
* @param {number?=} args.height Card height.
|
||||
* @param {number?=} args.border_radius Card border radius.
|
||||
* @param {string?=} args.customTitle Card custom title.
|
||||
* @param {string?=} args.defaultTitle Card default title.
|
||||
* @param {string?=} args.titlePrefixIcon Card title prefix icon.
|
||||
* @returns {Card} Card instance.
|
||||
* @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('./utils').getCardColors>?=} args.colors
|
||||
*/
|
||||
constructor({
|
||||
width = 100,
|
||||
@@ -22,7 +91,7 @@ class Card {
|
||||
customTitle,
|
||||
defaultTitle = "",
|
||||
titlePrefixIcon,
|
||||
}) {
|
||||
}: CardProps) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
@@ -55,7 +124,7 @@ class Card {
|
||||
/**
|
||||
* @param {{title: string, desc: string}} prop
|
||||
*/
|
||||
setAccessibilityLabel({ title, desc }) {
|
||||
setAccessibilityLabel({ title, desc }: AccessibilityLabel) {
|
||||
this.a11yTitle = title;
|
||||
this.a11yDesc = desc;
|
||||
}
|
||||
@@ -63,21 +132,21 @@ class Card {
|
||||
/**
|
||||
* @param {string} value
|
||||
*/
|
||||
setCSS(value) {
|
||||
setCSS(value: string) {
|
||||
this.css = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} value
|
||||
*/
|
||||
setHideBorder(value) {
|
||||
setHideBorder(value: boolean) {
|
||||
this.hideBorder = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} value
|
||||
*/
|
||||
setHideTitle(value) {
|
||||
setHideTitle(value: boolean) {
|
||||
this.hideTitle = value;
|
||||
if (value) {
|
||||
this.height -= 30;
|
||||
@@ -87,7 +156,7 @@ class Card {
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
setTitle(text) {
|
||||
setTitle(text: string) {
|
||||
this.title = text;
|
||||
}
|
||||
|
||||
@@ -139,7 +208,7 @@ class Card {
|
||||
gradientTransform="rotate(${this.colors.bgColor[0]})"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
${gradients.map((grad, index) => {
|
||||
${gradients.map((grad: string, index: number) => {
|
||||
let offset = (index * 100) / (gradients.length - 1);
|
||||
return `<stop offset="${offset}%" stop-color="#${grad}" />`;
|
||||
})}
|
||||
@@ -152,7 +221,7 @@ class Card {
|
||||
/**
|
||||
* @param {string} body
|
||||
*/
|
||||
render(body) {
|
||||
render(body: string) {
|
||||
return `
|
||||
<svg
|
||||
width="${this.width}"
|
||||
@@ -217,6 +286,3 @@ class Card {
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export { Card };
|
||||
export default Card;
|
||||
@@ -1,14 +1,29 @@
|
||||
type Translations = Record<string, Record<string, string>>;
|
||||
|
||||
/**
|
||||
* I18n translation class.
|
||||
*/
|
||||
class I18n {
|
||||
constructor({ locale, translations }) {
|
||||
export class I18n {
|
||||
/** The language locale. */
|
||||
locale?: string;
|
||||
/** The translations object. */
|
||||
translations: Translations;
|
||||
/** The fallback language locale. */
|
||||
fallbackLocale: string;
|
||||
|
||||
constructor({
|
||||
locale,
|
||||
translations,
|
||||
}: {
|
||||
locale?: string;
|
||||
translations: Translations;
|
||||
}) {
|
||||
this.locale = locale;
|
||||
this.translations = translations;
|
||||
this.fallbackLocale = "en";
|
||||
}
|
||||
|
||||
t(str) {
|
||||
t(str: string) {
|
||||
if (!this.translations[str]) {
|
||||
throw new Error(`${str} Translation string not found`);
|
||||
}
|
||||
@@ -20,6 +35,3 @@ class I18n {
|
||||
return this.translations[str][this.locale || this.fallbackLocale];
|
||||
}
|
||||
}
|
||||
|
||||
export { I18n };
|
||||
export default I18n;
|
||||
@@ -1,4 +0,0 @@
|
||||
const blacklist = ["renovate-bot", "technote-space", "sw-yx"];
|
||||
|
||||
export { blacklist };
|
||||
export default blacklist;
|
||||
@@ -0,0 +1,2 @@
|
||||
/** User blacklist. */
|
||||
export const blacklist = ["renovate-bot", "technote-space", "sw-yx"];
|
||||
@@ -1,41 +0,0 @@
|
||||
import { clampValue } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Create a node to indicate progress in percentage along a horizontal line.
|
||||
*
|
||||
* @param {Object} createProgressNodeParams Object that contains the createProgressNode parameters.
|
||||
* @param {number} createProgressNodeParams.x X-axis position.
|
||||
* @param {number} createProgressNodeParams.y Y-axis position.
|
||||
* @param {number} createProgressNodeParams.width Width of progress bar.
|
||||
* @param {string} createProgressNodeParams.color Progress color.
|
||||
* @param {string} createProgressNodeParams.progress Progress value.
|
||||
* @param {string} createProgressNodeParams.progressBarBackgroundColor Progress bar bg color.
|
||||
* @returns {string} Progress node.
|
||||
*/
|
||||
const createProgressNode = ({
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
color,
|
||||
progress,
|
||||
progressBarBackgroundColor,
|
||||
}) => {
|
||||
const progressPercentage = clampValue(progress, 2, 100);
|
||||
|
||||
return `
|
||||
<svg width="${width}" x="${x}" y="${y}">
|
||||
<rect rx="5" ry="5" x="0" y="0" width="${width}" height="8" fill="${progressBarBackgroundColor}"></rect>
|
||||
<rect
|
||||
height="8"
|
||||
fill="${color}"
|
||||
rx="5" ry="5" x="0" y="0"
|
||||
data-testid="lang-progress"
|
||||
width="${progressPercentage}%"
|
||||
>
|
||||
</rect>
|
||||
</svg>
|
||||
`;
|
||||
};
|
||||
|
||||
export { createProgressNode };
|
||||
export default createProgressNode;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { clampValue } from "./utils";
|
||||
|
||||
|
||||
/** CreateProgressNode properties. */
|
||||
interface CreateProgressNodeProps {
|
||||
/** Progress bar item x position. */
|
||||
x: number;
|
||||
/** Progress bar item y position.. */
|
||||
y: number;
|
||||
/** Progress bar item width. */
|
||||
width: number;
|
||||
/** Progress bar item color. */
|
||||
color: string;
|
||||
/** Progress value. */
|
||||
progress: number;
|
||||
/** Progress bar item background color. */
|
||||
progressBarBackgroundColor: string;
|
||||
}
|
||||
|
||||
const createProgressNode = ({
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
color,
|
||||
progress,
|
||||
progressBarBackgroundColor,
|
||||
}: CreateProgressNodeProps) => {
|
||||
const progressPercentage = clampValue(progress, 2, 100);
|
||||
|
||||
return `
|
||||
<svg width="${width}" x="${x}" y="${y}">
|
||||
<rect rx="5" ry="5" x="0" y="0" width="${width}" height="8" fill="${progressBarBackgroundColor}"></rect>
|
||||
<rect
|
||||
height="8"
|
||||
fill="${color}"
|
||||
rx="5" ry="5" x="0" y="0"
|
||||
data-testid="lang-progress"
|
||||
width="${progressPercentage}%"
|
||||
>
|
||||
</rect>
|
||||
</svg>
|
||||
`;
|
||||
};
|
||||
|
||||
export { createProgressNode };
|
||||
export default createProgressNode;
|
||||
@@ -1,4 +1,7 @@
|
||||
const icons = {
|
||||
|
||||
export type Icons = {[key: string]: string};
|
||||
|
||||
const icons: Icons = {
|
||||
star: `<path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"/>`,
|
||||
commits: `<path fill-rule="evenodd" d="M1.643 3.143L.427 1.927A.25.25 0 000 2.104V5.75c0 .138.112.25.25.25h3.646a.25.25 0 00.177-.427L2.715 4.215a6.5 6.5 0 11-1.18 4.458.75.75 0 10-1.493.154 8.001 8.001 0 101.6-5.684zM7.75 4a.75.75 0 01.75.75v2.992l2.028.812a.75.75 0 01-.557 1.392l-2.5-1A.75.75 0 017 8.25v-3.5A.75.75 0 017.75 4z"/>`,
|
||||
prs: `<path fill-rule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"/>`,
|
||||
@@ -1,30 +0,0 @@
|
||||
export { blacklist } from "./blacklist.js";
|
||||
export { Card } from "./Card.js";
|
||||
export { createProgressNode } from "./createProgressNode.js";
|
||||
export { I18n } from "./I18n.js";
|
||||
export { icons } from "./icons.js";
|
||||
export { retryer } from "./retryer.js";
|
||||
export {
|
||||
ERROR_CARD_LENGTH,
|
||||
renderError,
|
||||
encodeHTML,
|
||||
kFormatter,
|
||||
isValidHexColor,
|
||||
parseBoolean,
|
||||
parseArray,
|
||||
clampValue,
|
||||
isValidGradient,
|
||||
fallbackColor,
|
||||
request,
|
||||
flexLayout,
|
||||
getCardColors,
|
||||
wrapTextMultiline,
|
||||
logger,
|
||||
CONSTANTS,
|
||||
CustomError,
|
||||
MissingParamError,
|
||||
measureText,
|
||||
lowercaseTrim,
|
||||
chunkArray,
|
||||
parseEmojis,
|
||||
} from "./utils.js";
|
||||
@@ -1,4 +1,21 @@
|
||||
import { CustomError, logger } from "./utils.js";
|
||||
import axios, {
|
||||
AxiosError,
|
||||
AxiosPromise,
|
||||
AxiosRequestHeaders,
|
||||
AxiosResponse,
|
||||
} from "axios";
|
||||
import { CustomError, logger } from "./utils";
|
||||
|
||||
|
||||
/** Data fetcher. */
|
||||
type Fetcher = (
|
||||
/** Fetcher variables. */
|
||||
variables: AxiosRequestHeaders,
|
||||
/** Authentication token. */
|
||||
token: string,
|
||||
/** The number of retries. */
|
||||
retries?: number,
|
||||
) => AxiosPromise<any>
|
||||
|
||||
/**
|
||||
* Try to execute the fetcher function until it succeeds or the max number of retries is reached.
|
||||
@@ -9,7 +26,11 @@ import { CustomError, logger } from "./utils.js";
|
||||
* @param {number} retryerParams.retries How many times to retry.
|
||||
* @returns Promise<retryer>
|
||||
*/
|
||||
const retryer = async (fetcher, variables, retries = 0) => {
|
||||
export const retryer = async (
|
||||
fetcher: Fetcher,
|
||||
variables: AxiosRequestHeaders,
|
||||
retries = 0,
|
||||
): Promise<any> => {
|
||||
if (retries > 7) {
|
||||
throw new CustomError("Maximum retries exceeded", CustomError.MAX_RETRY);
|
||||
}
|
||||
@@ -17,7 +38,7 @@ const retryer = async (fetcher, variables, retries = 0) => {
|
||||
// try to fetch with the first token since RETRIES is 0 index i'm adding +1
|
||||
let response = await fetcher(
|
||||
variables,
|
||||
process.env[`PAT_${retries + 1}`],
|
||||
process.env[`PAT_${retries + 1}`] as string,
|
||||
retries,
|
||||
);
|
||||
|
||||
@@ -36,20 +57,17 @@ const retryer = async (fetcher, variables, retries = 0) => {
|
||||
// finally return the response
|
||||
return response;
|
||||
} catch (err) {
|
||||
// prettier-ignore
|
||||
// also checking for bad credentials if any tokens gets invalidated
|
||||
const isBadCredential = err.response.data && err.response.data.message === "Bad credentials";
|
||||
if (axios.isAxiosError(err)) {
|
||||
// prettier-ignore
|
||||
// also checking for bad credentials if any tokens gets invalidated
|
||||
const isBadCredential = err?.response?.data && err.response.data.message === "Bad credentials";
|
||||
|
||||
if (isBadCredential) {
|
||||
logger.log(`PAT_${retries + 1} Failed`);
|
||||
retries++;
|
||||
// directly return from the function
|
||||
return retryer(fetcher, variables, retries);
|
||||
} else {
|
||||
return err.response;
|
||||
if (isBadCredential) {
|
||||
logger.log(`PAT_${retries + 1} Failed`);
|
||||
retries++;
|
||||
// directly return from the function
|
||||
return retryer(fetcher, variables, retries);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export { retryer };
|
||||
export default retryer;
|
||||
@@ -1,30 +1,24 @@
|
||||
// @ts-check
|
||||
import axios from "axios";
|
||||
import axios, { AxiosRequestConfig, AxiosRequestHeaders } from "axios";
|
||||
import toEmoji from "emoji-name-map";
|
||||
import wrap from "word-wrap";
|
||||
import { themes } from "../../themes/index.js";
|
||||
|
||||
// Script parameters.
|
||||
const ERROR_CARD_LENGTH = 576.5;
|
||||
import { themes } from "../../themes/index";
|
||||
import type { ThemeEnum } from "../../themes/index";
|
||||
|
||||
/**
|
||||
* Renders error message on the card.
|
||||
*
|
||||
* @param {string} message Main error message.
|
||||
* @param {string} secondaryMessage The secondary error message.
|
||||
* @returns {string} The SVG markup.
|
||||
* @param {string} message
|
||||
* @param {string} secondaryMessage
|
||||
* @returns {string}
|
||||
*/
|
||||
const renderError = (message, secondaryMessage = "") => {
|
||||
const renderError = (message: string, secondaryMessage = "") => {
|
||||
return `
|
||||
<svg width="${ERROR_CARD_LENGTH}" height="120" viewBox="0 0 ${ERROR_CARD_LENGTH} 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="576.5" height="120" viewBox="0 0 576.5 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.text { font: 600 16px 'Segoe UI', Ubuntu, Sans-Serif; fill: #2F80ED }
|
||||
.small { font: 600 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: #252525 }
|
||||
.gray { fill: #858585 }
|
||||
</style>
|
||||
<rect x="0.5" y="0.5" width="${
|
||||
ERROR_CARD_LENGTH - 1
|
||||
}" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/>
|
||||
<rect x="0.5" y="0.5" width="575.5" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/>
|
||||
<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>
|
||||
@@ -35,14 +29,11 @@ const renderError = (message, secondaryMessage = "") => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode string as HTML.
|
||||
*
|
||||
* @see https://stackoverflow.com/a/48073476/10629172
|
||||
*
|
||||
* @param {string} str String to encode.
|
||||
* @returns {string} Encoded string.
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
function encodeHTML(str) {
|
||||
function encodeHTML(str: string) {
|
||||
return str
|
||||
.replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => {
|
||||
return "&#" + i.charCodeAt(0) + ";";
|
||||
@@ -51,38 +42,29 @@ function encodeHTML(str) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves num with suffix k(thousands) precise to 1 decimal if greater than 999.
|
||||
*
|
||||
* @param {number} num The number to format.
|
||||
* @returns {string|number} The formatted number.
|
||||
* @param {number} num
|
||||
*/
|
||||
function kFormatter(num) {
|
||||
function kFormatter(num: number) {
|
||||
return Math.abs(num) > 999
|
||||
? Math.sign(num) * parseFloat((Math.abs(num) / 1000).toFixed(1)) + "k"
|
||||
: Math.sign(num) * Math.abs(num);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string is a valid hex color.
|
||||
*
|
||||
* @param {string} hexColor String to check.
|
||||
* @returns {boolean} True if the given string is a valid hex color.
|
||||
*
|
||||
* @description: Tests color codes without the hash (#) prefix.
|
||||
* @param {string} hexColor
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isValidHexColor(hexColor) {
|
||||
function isValidHexColor(hexColor: string) {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns boolean if value is either "true" or "false" else the value as it is.
|
||||
*
|
||||
* @param {string} value The value to parse.
|
||||
* @returns {boolean | string} The parsed value.
|
||||
* @param {string} value
|
||||
* @returns {boolean | string}
|
||||
*/
|
||||
function parseBoolean(value) {
|
||||
function parseBoolean(value: string) {
|
||||
if (value === "true") {
|
||||
return true;
|
||||
} else if (value === "false") {
|
||||
@@ -98,7 +80,7 @@ function parseBoolean(value) {
|
||||
* @param {string} str The string to parse.
|
||||
* @returns {string[]} The array of strings.
|
||||
*/
|
||||
function parseArray(str) {
|
||||
function parseArray(str: string) {
|
||||
if (!str) return [];
|
||||
return str.split(",");
|
||||
}
|
||||
@@ -111,7 +93,7 @@ function parseArray(str) {
|
||||
* @param {number} max The maximum value.
|
||||
* returns {number} The clamped number.
|
||||
*/
|
||||
function clampValue(number, min, max) {
|
||||
function clampValue(number: number, min: number, max: number) {
|
||||
// @ts-ignore
|
||||
if (Number.isNaN(parseInt(number))) return min;
|
||||
return Math.max(min, Math.min(number, max));
|
||||
@@ -121,21 +103,18 @@ function clampValue(number, min, max) {
|
||||
* Check if the given string is a valid gradient.
|
||||
*
|
||||
* @param {string[]} colors Array of colors.
|
||||
* @returns {boolean} True if the given string is a valid gradient.
|
||||
* returns {boolean} True if the given string is a valid gradient.
|
||||
*/
|
||||
function isValidGradient(colors) {
|
||||
if (isNaN(colors[0])) return false;
|
||||
return colors.slice(1).every((color) => isValidHexColor(color));
|
||||
function isValidGradient(colors: string[]) {
|
||||
return isValidHexColor(colors[1]) && isValidHexColor(colors[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a gradient if color has more than one valid hex codes else a single color.
|
||||
*
|
||||
* @param {string} color The color to parse.
|
||||
* @param {string} fallbackColor The fallback color.
|
||||
* @returns {string | string[]} The gradient or color.
|
||||
* @param {string} color
|
||||
* @param {string} fallbackColor
|
||||
* @returns {string | string[]}
|
||||
*/
|
||||
function fallbackColor(color, fallbackColor) {
|
||||
function fallbackColor(color: string, fallbackColor: string) {
|
||||
let colors = color.split(",");
|
||||
let gradient = null;
|
||||
|
||||
@@ -150,13 +129,13 @@ function fallbackColor(color, fallbackColor) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send GraphQL request to GitHub API.
|
||||
*
|
||||
* @param {import('axios').AxiosRequestConfig['data']} data Request data.
|
||||
* @param {import('axios').AxiosRequestConfig['headers']} headers Request headers.
|
||||
* @returns {Promise<any>} Request response.
|
||||
* @param {import('axios').AxiosRequestConfig['data']} data
|
||||
* @param {import('axios').AxiosRequestConfig['headers']} headers
|
||||
*/
|
||||
function request(data, headers) {
|
||||
function request(
|
||||
data: AxiosRequestConfig["data"],
|
||||
headers: AxiosRequestHeaders,
|
||||
) {
|
||||
// @ts-ignore
|
||||
return axios({
|
||||
url: "https://api.github.com/graphql",
|
||||
@@ -167,51 +146,72 @@ function request(data, headers) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto layout utility, allows us to layout things vertically or horizontally with
|
||||
* proper gaping.
|
||||
* @param {object} props
|
||||
* @param {string[]} props.items
|
||||
* @param {number} props.gap
|
||||
* @param {number[]?=} props.sizes
|
||||
* @param {"column" | "row"?=} props.direction
|
||||
*
|
||||
* @param {object} props Function properties.
|
||||
* @param {string[]} props.items Array of items to layout.
|
||||
* @param {number} props.gap Gap between items.
|
||||
* @param {"column" | "row"?=} props.direction Direction to layout items.
|
||||
* @param {number[]?=} props.sizes Array of sizes for each item.
|
||||
* @returns {string[]} Array of items with proper layout.
|
||||
* @returns {string[]}
|
||||
*
|
||||
* @description
|
||||
* Auto layout utility, allows us to layout things
|
||||
* vertically or horizontally with proper gaping
|
||||
*/
|
||||
function flexLayout({ items, gap, direction, sizes = [] }) {
|
||||
function flexLayout({
|
||||
items,
|
||||
gap,
|
||||
direction,
|
||||
sizes = [],
|
||||
}: {
|
||||
items: string[];
|
||||
gap: number;
|
||||
direction?: "column" | "row";
|
||||
sizes?: number[];
|
||||
}) {
|
||||
let lastSize = 0;
|
||||
// filter() for filtering out empty strings
|
||||
return items.filter(Boolean).map((item, i) => {
|
||||
return items.filter(Boolean).map((item: string, i: number) => {
|
||||
const size = sizes[i] || 0;
|
||||
let transform = `translate(${lastSize}, 0)`;
|
||||
if (direction === "column") {
|
||||
transform = `translate(0, ${lastSize})`;
|
||||
}
|
||||
lastSize += size + gap;
|
||||
lastSize += ((size as number) + gap) as number;
|
||||
return `<g transform="${transform}">${item}</g>`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns theme based colors with proper overrides and defaults.
|
||||
*
|
||||
* @param {Object[]} args Function arguments.
|
||||
* @param {string} args.title_color Card title color.
|
||||
* @param {string} args.text_color Card text color.
|
||||
* @param {string} args.icon_color Card icon color.
|
||||
* @param {string} args.bg_color Card background color.
|
||||
* @param {string} args.border_color Card border color.
|
||||
* @param {string} args.theme Card theme.
|
||||
* @param {string} args.fallbackTheme Fallback theme.
|
||||
*
|
||||
* @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,
|
||||
border_color,
|
||||
title_color = "",
|
||||
text_color = "",
|
||||
icon_color = "",
|
||||
bg_color = "",
|
||||
border_color = "",
|
||||
theme,
|
||||
fallbackTheme = "default",
|
||||
}: {
|
||||
title_color?: string;
|
||||
text_color?: string;
|
||||
icon_color?: string;
|
||||
bg_color?: string;
|
||||
border_color?: string;
|
||||
theme: ThemeEnum;
|
||||
fallbackTheme?: "default";
|
||||
}) {
|
||||
const defaultTheme = themes[fallbackTheme];
|
||||
const selectedTheme = themes[theme] || defaultTheme;
|
||||
@@ -246,14 +246,12 @@ function getCardColors({
|
||||
}
|
||||
|
||||
/**
|
||||
* Split text over multiple lines based on the card width.
|
||||
*
|
||||
* @param {string} text Text to split.
|
||||
* @param {number} width Line width in number of characters.
|
||||
* @param {number} maxLines Maximum number of lines.
|
||||
* @returns {string[]} Array of lines.
|
||||
* @param {string} text
|
||||
* @param {number} width
|
||||
* @param {number} maxLines
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function wrapTextMultiline(text, width = 59, maxLines = 3) {
|
||||
function wrapTextMultiline(text: string, width = 59, maxLines = 3) {
|
||||
const fullWidthComma = ",";
|
||||
const encoded = encodeHTML(text);
|
||||
const isChinese = encoded.includes(fullWidthComma);
|
||||
@@ -296,37 +294,33 @@ const SECONDARY_ERROR_MESSAGES = {
|
||||
MAX_RETRY:
|
||||
"Please add an env variable called PAT_1 with your github token in vercel",
|
||||
USER_NOT_FOUND: "Make sure the provided username is not an organization",
|
||||
GRAPHQL_ERROR: "Please try again later",
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom error class to handle custom GRS errors.
|
||||
*/
|
||||
class CustomError extends Error {
|
||||
type: string;
|
||||
secondaryMessage?: string;
|
||||
/**
|
||||
* @param {string} message Error message.
|
||||
* @param {string} type Error type.
|
||||
* @param {string} message
|
||||
* @param {string} type
|
||||
*/
|
||||
constructor(message, type) {
|
||||
constructor(message: string, type: keyof typeof SECONDARY_ERROR_MESSAGES) {
|
||||
super(message);
|
||||
this.type = type;
|
||||
this.secondaryMessage = SECONDARY_ERROR_MESSAGES[type] || type;
|
||||
}
|
||||
|
||||
static MAX_RETRY = "MAX_RETRY";
|
||||
static USER_NOT_FOUND = "USER_NOT_FOUND";
|
||||
static GRAPHQL_ERROR = "GRAPHQL_ERROR";
|
||||
static MAX_RETRY = "MAX_RETRY" as "MAX_RETRY";
|
||||
static USER_NOT_FOUND = "USER_NOT_FOUND" as "USER_NOT_FOUND";
|
||||
}
|
||||
|
||||
/**
|
||||
* Missing query parameter class.
|
||||
*/
|
||||
class MissingParamError extends Error {
|
||||
missedParams: string[];
|
||||
secondaryMessage?: string;
|
||||
/**
|
||||
* @param {string[]} missedParams
|
||||
* @param {string?=} secondaryMessage
|
||||
*/
|
||||
constructor(missedParams, secondaryMessage) {
|
||||
constructor(missedParams: string[], secondaryMessage?: string) {
|
||||
const msg = `Missing params ${missedParams
|
||||
.map((p) => `"${p}"`)
|
||||
.join(", ")} make sure you pass the parameters in URL`;
|
||||
@@ -337,14 +331,12 @@ class MissingParamError extends Error {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve text length.
|
||||
*
|
||||
* @see https://stackoverflow.com/a/48172630/10629172
|
||||
* @param {string} str String to measure.
|
||||
* @param {number} fontSize Font size.
|
||||
* @returns {number} Text length.
|
||||
* @param {string} str
|
||||
* @param {number} fontSize
|
||||
* @returns
|
||||
*/
|
||||
function measureText(str, fontSize = 10) {
|
||||
function measureText(str: string, fontSize = 10) {
|
||||
// prettier-ignore
|
||||
const widths = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
@@ -379,63 +371,58 @@ function measureText(str, fontSize = 10) {
|
||||
}
|
||||
|
||||
/** @param {string} name */
|
||||
const lowercaseTrim = (name) => name.toLowerCase().trim();
|
||||
const lowercaseTrim = (name: string) => name.toLowerCase().trim();
|
||||
|
||||
/**
|
||||
* Split array of languages in columns.
|
||||
*
|
||||
* @template T Langauge object.
|
||||
* @param {Array<T>} arr Array of languages.
|
||||
* @param {number} perChunk Number of languages per column.
|
||||
* @returns {Array<T>} Array of languages split in columns.
|
||||
* @template T
|
||||
* @param {Array<T>} arr
|
||||
* @param {number} perChunk
|
||||
* @returns {Array<T><T>}
|
||||
*/
|
||||
function chunkArray(arr, perChunk) {
|
||||
return arr.reduce((resultArray, item, index) => {
|
||||
function chunkArray<T>(arr: Array<T>, perChunk: number) {
|
||||
return arr.reduce((resultArray: Array<T> | Array<Array<T>>, item, index) => {
|
||||
const chunkIndex = Math.floor(index / perChunk);
|
||||
|
||||
if (!resultArray[chunkIndex]) {
|
||||
resultArray[chunkIndex] = []; // start a new chunk
|
||||
}
|
||||
|
||||
resultArray[chunkIndex].push(item);
|
||||
(resultArray[chunkIndex] as Array<T>).push(item);
|
||||
|
||||
return resultArray;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse emoji from string.
|
||||
*
|
||||
* @param {string} str String to parse emoji from.
|
||||
* @returns {string} String with emoji parsed.
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
function parseEmojis(str) {
|
||||
function parseEmojis(str: string) {
|
||||
if (!str) throw new Error("[parseEmoji]: str argument not provided");
|
||||
return str.replace(/:[\w+-]+:/gm, (emoji) => {
|
||||
return str.replace(/:\w+:/gm, (emoji) => {
|
||||
return toEmoji.get(emoji) || "";
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
ERROR_CARD_LENGTH,
|
||||
renderError,
|
||||
encodeHTML,
|
||||
kFormatter,
|
||||
encodeHTML,
|
||||
isValidHexColor,
|
||||
parseBoolean,
|
||||
parseArray,
|
||||
clampValue,
|
||||
isValidGradient,
|
||||
fallbackColor,
|
||||
request,
|
||||
parseArray,
|
||||
parseBoolean,
|
||||
fallbackColor,
|
||||
flexLayout,
|
||||
getCardColors,
|
||||
clampValue,
|
||||
wrapTextMultiline,
|
||||
measureText,
|
||||
logger,
|
||||
CONSTANTS,
|
||||
CustomError,
|
||||
MissingParamError,
|
||||
measureText,
|
||||
lowercaseTrim,
|
||||
chunkArray,
|
||||
parseEmojis,
|
||||
@@ -1,15 +1,13 @@
|
||||
// @ts-check
|
||||
import { retryer } from "../common/retryer.js";
|
||||
import { MissingParamError, request } from "../common/utils.js";
|
||||
import { AxiosRequestHeaders } from "axios";
|
||||
import { retryer } from "../common/retryer";
|
||||
import { MissingParamError, request } from "../common/utils";
|
||||
|
||||
/**
|
||||
* Repo data fetcher.
|
||||
*
|
||||
* @param {import('Axios').AxiosRequestHeaders} variables Fetcher variables.
|
||||
* @param {string} token Github token.
|
||||
* @returns {Promise<import('Axios').AxiosResponse>} The response.
|
||||
* @param {AxiosRequestHeaders} variables
|
||||
* @param {string} token
|
||||
*/
|
||||
const fetcher = (variables, token) => {
|
||||
const fetcher = (variables: AxiosRequestHeaders, token: string) => {
|
||||
return request(
|
||||
{
|
||||
query: `
|
||||
@@ -54,13 +52,11 @@ const fetcher = (variables, token) => {
|
||||
const urlExample = "/api/pin?username=USERNAME&repo=REPO_NAME";
|
||||
|
||||
/**
|
||||
* Fetch repository data.
|
||||
*
|
||||
* @param {string} username Github username.
|
||||
* @param {string} reponame Github repository name.
|
||||
* @returns {Promise<import("./types").RepositoryData>} Repository data.
|
||||
* @param {string} username
|
||||
* @param {string} reponame
|
||||
* @returns {Promise<import("./types").RepositoryData>}
|
||||
*/
|
||||
async function fetchRepo(username, reponame) {
|
||||
async function fetchRepo(username: string, reponame: string) {
|
||||
if (!username && !reponame) {
|
||||
throw new MissingParamError(["username", "repo"], urlExample);
|
||||
}
|
||||
@@ -2,24 +2,20 @@
|
||||
import axios from "axios";
|
||||
import * as dotenv from "dotenv";
|
||||
import githubUsernameRegex from "github-username-regex";
|
||||
import { calculateRank } from "../calculateRank.js";
|
||||
import { retryer } from "../common/retryer.js";
|
||||
import { calculateRank } from "../calculateRank";
|
||||
import { retryer } from "../common/retryer";
|
||||
import {
|
||||
CustomError,
|
||||
logger,
|
||||
MissingParamError,
|
||||
request,
|
||||
wrapTextMultiline,
|
||||
} from "../common/utils.js";
|
||||
} from "../common/utils";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
/**
|
||||
* Stats fetcher object.
|
||||
*
|
||||
* @param {import('axios').AxiosRequestHeaders} variables Fetcher variables.
|
||||
* @param {string} token Github token.
|
||||
* @returns {Promise<import('../common/types').StatsFetcherResponse>} Stats fetcher response.
|
||||
* @param {import('axios').AxiosRequestHeaders} variables
|
||||
* @param {string} token
|
||||
*/
|
||||
const fetcher = (variables, token) => {
|
||||
return request(
|
||||
@@ -63,11 +59,8 @@ const fetcher = (variables, token) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch first 100 repositories for a given username.
|
||||
*
|
||||
* @param {import('axios').AxiosRequestHeaders} variables Fetcher variables.
|
||||
* @param {string} token Github token.
|
||||
* @returns {Promise<import('../common/types').StatsFetcherResponse>} Repositories fetcher response.
|
||||
* @param {import('axios').AxiosRequestHeaders} variables
|
||||
* @param {string} token
|
||||
*/
|
||||
const repositoriesFetcher = (variables, token) => {
|
||||
return request(
|
||||
@@ -98,15 +91,8 @@ const repositoriesFetcher = (variables, token) => {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch all the commits for all the repositories of a given username.
|
||||
*
|
||||
* @param {*} username Github username.
|
||||
* @returns {Promise<number>} Total commits.
|
||||
*
|
||||
* @description Done like this because the Github API does not provide a way to fetch all the commits. See
|
||||
* #92#issuecomment-661026467 and #211 for more information.
|
||||
*/
|
||||
// https://github.com/anuraghazra/github-readme-stats/issues/92#issuecomment-661026467
|
||||
// https://github.com/anuraghazra/github-readme-stats/pull/211/
|
||||
const totalCommitsFetcher = async (username) => {
|
||||
if (!githubUsernameRegex.test(username)) {
|
||||
logger.log("Invalid username");
|
||||
@@ -141,11 +127,9 @@ const totalCommitsFetcher = async (username) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch all the stars for all the repositories of a given username.
|
||||
*
|
||||
* @param {string} username Github username.
|
||||
* @param {array} repoToHide Repositories to hide.
|
||||
* @returns {Promise<number>} Total stars.
|
||||
* Fetch all the stars for all the repositories of a given username
|
||||
* @param {string} username
|
||||
* @param {array} repoToHide
|
||||
*/
|
||||
const totalStarsFetcher = async (username, repoToHide) => {
|
||||
let nodes = [];
|
||||
@@ -168,10 +152,9 @@ const totalStarsFetcher = async (username, repoToHide) => {
|
||||
(node) => node.stargazers.totalCount !== 0,
|
||||
);
|
||||
nodes.push(...nodesWithStars);
|
||||
// hasNextPage =
|
||||
// allNodes.length === nodesWithStars.length &&
|
||||
// res.data.data.user.repositories.pageInfo.hasNextPage;
|
||||
hasNextPage = false; // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
hasNextPage =
|
||||
allNodes.length === nodesWithStars.length &&
|
||||
res.data.data.user.repositories.pageInfo.hasNextPage;
|
||||
endCursor = res.data.data.user.repositories.pageInfo.endCursor;
|
||||
}
|
||||
|
||||
@@ -181,12 +164,10 @@ const totalStarsFetcher = async (username, repoToHide) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch stats for a given username.
|
||||
*
|
||||
* @param {string} username Github username.
|
||||
* @param {boolean} count_private Include private contributions.
|
||||
* @param {boolean} include_all_commits Include all commits.
|
||||
* @returns {Promise<import("./types").StatsData>} Stats data.
|
||||
* @param {string} username
|
||||
* @param {boolean} count_private
|
||||
* @param {boolean} include_all_commits
|
||||
* @returns {Promise<import("./types").StatsData>}
|
||||
*/
|
||||
async function fetchStats(
|
||||
username,
|
||||
@@ -208,24 +189,11 @@ async function fetchStats(
|
||||
|
||||
let res = await retryer(fetcher, { login: username });
|
||||
|
||||
// Catch GraphQL errors.
|
||||
if (res.data.errors) {
|
||||
logger.error(res.data.errors);
|
||||
if (res.data.errors[0].type === "NOT_FOUND") {
|
||||
throw new CustomError(
|
||||
res.data.errors[0].message || "Could not fetch user.",
|
||||
CustomError.USER_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
if (res.data.errors[0].message) {
|
||||
throw new CustomError(
|
||||
wrapTextMultiline(res.data.errors[0].message, 90, 1)[0],
|
||||
res.statusText,
|
||||
);
|
||||
}
|
||||
throw new CustomError(
|
||||
"Something went while trying to retrieve the stats data using the GraphQL API.",
|
||||
CustomError.GRAPHQL_ERROR,
|
||||
res.data.errors[0].message || "Could not fetch user",
|
||||
CustomError.USER_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
// @ts-check
|
||||
import * as dotenv from "dotenv";
|
||||
import { retryer } from "../common/retryer.js";
|
||||
import {
|
||||
CustomError,
|
||||
logger,
|
||||
MissingParamError,
|
||||
request,
|
||||
wrapTextMultiline,
|
||||
} from "../common/utils.js";
|
||||
import { retryer } from "../common/retryer";
|
||||
import { logger, MissingParamError, request } from "../common/utils";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
/**
|
||||
* Top languages fetcher object.
|
||||
*
|
||||
* @param {import('Axios').AxiosRequestHeaders} variables Fetcher variables.
|
||||
* @param {string} token Github token.
|
||||
* @returns {Promise<import('../common/types').StatsFetcherResponse>} Languages fetcher response.
|
||||
* @param {import('Axios').AxiosRequestHeaders} variables
|
||||
* @param {string} token
|
||||
*/
|
||||
const fetcher = (variables, token) => {
|
||||
return request(
|
||||
@@ -51,11 +42,9 @@ const fetcher = (variables, token) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch top languages for a given username.
|
||||
*
|
||||
* @param {string} username Github username.
|
||||
* @param {string[]} exclude_repo List of repositories to exclude.
|
||||
* @returns {Promise<import("./types").TopLangData>} Top languages data.
|
||||
* @param {string} username
|
||||
* @param {string[]} exclude_repo
|
||||
* @returns {Promise<import("./types").TopLangData>}
|
||||
*/
|
||||
async function fetchTopLanguages(username, exclude_repo = []) {
|
||||
if (!username) throw new MissingParamError(["username"]);
|
||||
@@ -67,27 +56,6 @@ async function fetchTopLanguages(username, exclude_repo = []) {
|
||||
throw Error(res.data.errors[0].message || "Could not fetch user");
|
||||
}
|
||||
|
||||
// Catch GraphQL errors.
|
||||
if (res.data.errors) {
|
||||
logger.error(res.data.errors);
|
||||
if (res.data.errors[0].type === "NOT_FOUND") {
|
||||
throw new CustomError(
|
||||
res.data.errors[0].message || "Could not fetch user.",
|
||||
CustomError.USER_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
if (res.data.errors[0].message) {
|
||||
throw new CustomError(
|
||||
wrapTextMultiline(res.data.errors[0].message, 90, 1)[0],
|
||||
res.statusText,
|
||||
);
|
||||
}
|
||||
throw new CustomError(
|
||||
"Something went while trying to retrieve the language data using the GraphQL API.",
|
||||
CustomError.GRAPHQL_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
let repoNodes = res.data.data.user.repositories.nodes;
|
||||
let repoToHide = {};
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import axios from "axios";
|
||||
import { MissingParamError } from "../common/utils.js";
|
||||
import { MissingParamError } from "../common/utils";
|
||||
|
||||
/**
|
||||
* WakaTime data fetcher.
|
||||
*
|
||||
* @param {{username: string, api_domain: string, range: string}} props Fetcher props.
|
||||
* @returns {Promise<WakaTimeData>} WakaTime data response.
|
||||
* @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"]);
|
||||
@@ -1,9 +1,6 @@
|
||||
// @ts-check
|
||||
/**
|
||||
* Calculates progress along the boundary of the circle i.e it's circumference.
|
||||
*
|
||||
* @param {number} value The rank value to calculate progress for.
|
||||
* @returns {number} Progress value.
|
||||
* @param {number} value
|
||||
*/
|
||||
const calculateCircleProgress = (value) => {
|
||||
const radius = 40;
|
||||
@@ -16,11 +13,9 @@ const calculateCircleProgress = (value) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the animation to display progress along the circumference of circle
|
||||
* from the beginning to the given value in a clockwise direction.
|
||||
*
|
||||
* @param {{progress: number}} progress The progress value to animate to.
|
||||
* @returns {string} Progress animation css.
|
||||
* @param {{progress: number}} param0
|
||||
* @returns
|
||||
*/
|
||||
const getProgressAnimation = ({ progress }) => {
|
||||
return `
|
||||
@@ -35,11 +30,6 @@ const getProgressAnimation = ({ progress }) => {
|
||||
`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves css animations for a card.
|
||||
*
|
||||
* @returns {string} Animation css.
|
||||
*/
|
||||
const getAnimations = () => {
|
||||
return `
|
||||
/* Animations */
|
||||
@@ -63,15 +53,13 @@ const getAnimations = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves CSS styles for a card.
|
||||
*
|
||||
* @param {Object[]} colors The colors to use for the card.
|
||||
* @param {string} colors.titleColor The title color.
|
||||
* @param {string} colors.textColor The text color.
|
||||
* @param {string} colors.iconColor The icon color.
|
||||
* @param {boolean} colors.show_icons Whether to show icons.
|
||||
* @param {number} colors.progress The progress value to animate to.
|
||||
* @returns {string} Card CSS styles.
|
||||
* @param {{
|
||||
* titleColor?: string | string[]
|
||||
* textColor?: string | string[]
|
||||
* iconColor?: string | string[]
|
||||
* show_icons?: boolean;
|
||||
* progress?: number;
|
||||
* }} args
|
||||
*/
|
||||
const getStyles = ({
|
||||
titleColor,
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from "./common/index.js";
|
||||
export * from "./cards/index.js";
|
||||
export { getStyles, getAnimations } from "./getStyles.js";
|
||||
@@ -1,12 +1,5 @@
|
||||
import { encodeHTML } from "./common/utils.js";
|
||||
import { encodeHTML } from "./common/utils";
|
||||
|
||||
/**
|
||||
* Retrieves stat card labels in the available locales.
|
||||
*
|
||||
* @param {string} name The name of the locale.
|
||||
* @param {string} apostrophe Whether to use apostrophe or not.
|
||||
* @returns {Object} The locales object.
|
||||
*/
|
||||
const statCardLocales = ({ name, apostrophe }) => {
|
||||
const encodedName = encodeHTML(name);
|
||||
return {
|
||||
@@ -361,12 +354,6 @@ const wakatimeCardLocales = {
|
||||
|
||||
const availableLocales = Object.keys(repoCardLocales["repocard.archived"]);
|
||||
|
||||
/**
|
||||
* Checks whether the locale is available or not.
|
||||
*
|
||||
* @param {string} locale The locale to check.
|
||||
* @returns {boolean} Boolean specifying whether the locale is available or not.
|
||||
*/
|
||||
function isLocaleAvailable(locale) {
|
||||
return availableLocales.includes(locale.toLowerCase());
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* @file Contains global type definitions.
|
||||
*/
|
||||
|
||||
// Declare global emoji-name-map module since it doesn't have a type definition.
|
||||
declare module "emoji-name-map";
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
/**
|
||||
* @file Contains shared types.
|
||||
*/
|
||||
@@ -1,161 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Test Render Wakatime Card should render correctly 1`] = `[Function]`;
|
||||
|
||||
exports[`Test Render Wakatime Card should render correctly with compact layout 1`] = `
|
||||
"
|
||||
<svg
|
||||
width="495"
|
||||
height="115"
|
||||
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: #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: #434d58;
|
||||
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||
}
|
||||
|
||||
.not_bold { font-weight: 400 }
|
||||
.bold { font-weight: 700 }
|
||||
.icon {
|
||||
fill: #4c71f2;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rank-circle-rim {
|
||||
stroke: #2f80ed;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: #2f80ed;
|
||||
stroke-dasharray: 250;
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.8;
|
||||
transform-origin: -10px 8px;
|
||||
transform: rotate(-90deg);
|
||||
animation: rankAnimation 1s forwards ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58 }
|
||||
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
<rect
|
||||
data-testid="card-bg"
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
rx="4.5"
|
||||
height="99%"
|
||||
stroke="#e4e2e2"
|
||||
width="494"
|
||||
fill="#fffefe"
|
||||
stroke-opacity="1"
|
||||
/>
|
||||
|
||||
|
||||
<g
|
||||
data-testid="card-title"
|
||||
transform="translate(25, 35)"
|
||||
>
|
||||
<g transform="translate(0, 0)">
|
||||
<text
|
||||
x="0"
|
||||
y="0"
|
||||
class="header"
|
||||
data-testid="header"
|
||||
>Wakatime Stats</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
||||
<g
|
||||
data-testid="main-card-body"
|
||||
transform="translate(0, 55)"
|
||||
>
|
||||
|
||||
<svg x="0" y="0" width="100%">
|
||||
|
||||
<mask id="rect-mask">
|
||||
<rect x="25" y="0" width="440" height="8" fill="white" rx="5" />
|
||||
</mask>
|
||||
|
||||
<rect
|
||||
mask="url(#rect-mask)"
|
||||
data-testid="lang-progress"
|
||||
x="0"
|
||||
y="0"
|
||||
width="6.6495"
|
||||
height="8"
|
||||
fill="#858585"
|
||||
/>
|
||||
|
||||
<rect
|
||||
mask="url(#rect-mask)"
|
||||
data-testid="lang-progress"
|
||||
x="6.6495"
|
||||
y="0"
|
||||
width="0.465"
|
||||
height="8"
|
||||
fill="#3178c6"
|
||||
/>
|
||||
|
||||
|
||||
<g transform="translate(25, 25)">
|
||||
<circle cx="5" cy="6" r="5" fill="#858585" />
|
||||
<text data-testid="lang-name" x="15" y="10" class='lang-name'>
|
||||
Other - 19 mins
|
||||
</text>
|
||||
</g>
|
||||
|
||||
<g transform="translate(230, 25)">
|
||||
<circle cx="5" cy="6" r="5" fill="#3178c6" />
|
||||
<text data-testid="lang-name" x="15" y="10" class='lang-name'>
|
||||
TypeScript - 1 min
|
||||
</text>
|
||||
</g>
|
||||
|
||||
|
||||
</svg>
|
||||
|
||||
</g>
|
||||
</svg>
|
||||
"
|
||||
`;
|
||||
@@ -1,10 +1,10 @@
|
||||
import { jest } from "@jest/globals";
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import api from "../api/index.js";
|
||||
import { calculateRank } from "../src/calculateRank.js";
|
||||
import { renderStatsCard } from "../src/cards/stats-card.js";
|
||||
import { CONSTANTS, renderError } from "../src/common/utils.js";
|
||||
import api from "../api/index";
|
||||
import { calculateRank } from "../src/calculateRank";
|
||||
import { renderStatsCard } from "../src/cards/stats-card";
|
||||
import { CONSTANTS, renderError } from "../src/common/utils";
|
||||
|
||||
const stats = {
|
||||
name: "Anurag Hazra",
|
||||
@@ -174,16 +174,6 @@ describe("Test /api/", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("should not store cache when error", async () => {
|
||||
const { req, res } = faker({}, error);
|
||||
await api(req, res);
|
||||
|
||||
expect(res.setHeader.mock.calls).toEqual([
|
||||
["Content-Type", "image/svg+xml"],
|
||||
["Cache-Control", `no-store`],
|
||||
]);
|
||||
});
|
||||
|
||||
it("should set proper cache with clamped values", async () => {
|
||||
{
|
||||
let { req, res } = faker({ cache_seconds: 200000 }, data);
|
||||
@@ -1,5 +1,5 @@
|
||||
import "@testing-library/jest-dom";
|
||||
import { calculateRank } from "../src/calculateRank.js";
|
||||
import { calculateRank } from "../src/calculateRank";
|
||||
|
||||
describe("Test calculateRank", () => {
|
||||
it("should calculate rank correctly", () => {
|
||||
@@ -1,9 +1,9 @@
|
||||
import { queryByTestId } from "@testing-library/dom";
|
||||
import "@testing-library/jest-dom";
|
||||
import { cssToObject } from "@uppercod/css-to-object";
|
||||
import { Card } from "../src/common/Card.js";
|
||||
import { icons } from "../src/common/icons.js";
|
||||
import { getCardColors } from "../src/common/utils.js";
|
||||
import { Card } from "../src/common/Card";
|
||||
import { icons } from "../src/common/icons";
|
||||
import { getCardColors } from "../src/common/utils";
|
||||
|
||||
describe("Card", () => {
|
||||
it("should hide border", () => {
|
||||
@@ -145,7 +145,6 @@ describe("Card", () => {
|
||||
"#fff",
|
||||
);
|
||||
});
|
||||
|
||||
it("should render gradient backgrounds", () => {
|
||||
const { titleColor, textColor, iconColor, bgColor } = getCardColors({
|
||||
title_color: "f00",
|
||||
@@ -6,11 +6,12 @@ dotenv.config();
|
||||
|
||||
import { describe } from "@jest/globals";
|
||||
import axios from "axios";
|
||||
import { renderRepoCard } from "../../src/cards/repo-card.js";
|
||||
import { renderStatsCard } from "../../src/cards/stats-card.js";
|
||||
import { renderTopLanguages } from "../../src/cards/top-languages-card.js";
|
||||
import { renderWakatimeCard } from "../../src/cards/wakatime-card.js";
|
||||
import { renderRepoCard } from "../../src/cards/repo-card";
|
||||
import { renderStatsCard } from "../../src/cards/stats-card";
|
||||
import { renderTopLanguages } from "../../src/cards/top-languages-card";
|
||||
import { renderWakatimeCard } from "../../src/cards/wakatime-card";
|
||||
|
||||
// Script variables
|
||||
const REPO = "dummy-cra";
|
||||
const USER = "grsdummy";
|
||||
const STATS_DATA = {
|
||||
@@ -25,7 +26,6 @@ const STATS_DATA = {
|
||||
score: 51.01013099671447,
|
||||
},
|
||||
};
|
||||
|
||||
const LANGS_DATA = {
|
||||
TypeScript: {
|
||||
color: "#3178c6",
|
||||
@@ -48,7 +48,6 @@ const LANGS_DATA = {
|
||||
size: 671,
|
||||
},
|
||||
};
|
||||
|
||||
const WAKATIME_DATA = {
|
||||
human_readable_range: "last week",
|
||||
is_already_updating: false,
|
||||
@@ -65,7 +64,6 @@ const WAKATIME_DATA = {
|
||||
username: "grsdummy",
|
||||
writes_only: false,
|
||||
};
|
||||
|
||||
const REPOSITORY_DATA = {
|
||||
name: "dummy-cra",
|
||||
nameWithOwner: "grsdummy/dummy-cra",
|
||||
@@ -85,8 +83,6 @@ const REPOSITORY_DATA = {
|
||||
starCount: 1,
|
||||
};
|
||||
|
||||
const CACHE_BURST_STRING = `v=${new Date().getTime()}`;
|
||||
|
||||
describe("Fetch Cards", () => {
|
||||
let VERCEL_PREVIEW_URL;
|
||||
|
||||
@@ -108,24 +104,19 @@ describe("Fetch Cards", () => {
|
||||
|
||||
// Get the Vercel preview stats card response.
|
||||
const serverStatsSvg = await axios.get(
|
||||
`${VERCEL_PREVIEW_URL}/api?username=${USER}&${CACHE_BURST_STRING}`,
|
||||
`${VERCEL_PREVIEW_URL}/api?username=${USER}`,
|
||||
);
|
||||
|
||||
// Check if stats card from deployment matches the stats card from local.
|
||||
expect(serverStatsSvg.data).toEqual(localStatsCardSVG);
|
||||
}, 7000);
|
||||
});
|
||||
|
||||
test("retrieve language card", async () => {
|
||||
expect(VERCEL_PREVIEW_URL).toBeDefined();
|
||||
|
||||
// Check if the Vercel preview instance language card function is up and running.
|
||||
console.log(
|
||||
`${VERCEL_PREVIEW_URL}/api/top-langs/?username=${USER}&${CACHE_BURST_STRING}`,
|
||||
);
|
||||
await expect(
|
||||
axios.get(
|
||||
`${VERCEL_PREVIEW_URL}/api/top-langs/?username=${USER}&${CACHE_BURST_STRING}`,
|
||||
),
|
||||
axios.get(`${VERCEL_PREVIEW_URL}/api/top-langs/?username=${USER}`),
|
||||
).resolves.not.toThrow();
|
||||
|
||||
// Get local language card.
|
||||
@@ -133,7 +124,7 @@ describe("Fetch Cards", () => {
|
||||
|
||||
// Get the Vercel preview language card response.
|
||||
const severLanguageSVG = await axios.get(
|
||||
`${VERCEL_PREVIEW_URL}/api/top-langs/?username=${USER}&${CACHE_BURST_STRING}`,
|
||||
`${VERCEL_PREVIEW_URL}/api/top-langs/?username=${USER}`,
|
||||
);
|
||||
|
||||
// Check if language card from deployment matches the local language card.
|
||||
@@ -153,7 +144,7 @@ describe("Fetch Cards", () => {
|
||||
|
||||
// Get the Vercel preview WakaTime card response.
|
||||
const serverWakaTimeSvg = await axios.get(
|
||||
`${VERCEL_PREVIEW_URL}/api/wakatime?username=${USER}&${CACHE_BURST_STRING}`,
|
||||
`${VERCEL_PREVIEW_URL}/api/wakatime?username=${USER}`,
|
||||
);
|
||||
|
||||
// Check if WakaTime card from deployment matches the local WakaTime card.
|
||||
@@ -165,9 +156,7 @@ describe("Fetch Cards", () => {
|
||||
|
||||
// Check if the Vercel preview instance Repo function is up and running.
|
||||
await expect(
|
||||
axios.get(
|
||||
`${VERCEL_PREVIEW_URL}/api/pin/?username=${USER}&repo=${REPO}&${CACHE_BURST_STRING}`,
|
||||
),
|
||||
axios.get(`${VERCEL_PREVIEW_URL}/api/pin/?username=${USER}&repo=${REPO}`),
|
||||
).resolves.not.toThrow();
|
||||
|
||||
// Get local repo card.
|
||||
@@ -175,7 +164,7 @@ describe("Fetch Cards", () => {
|
||||
|
||||
// Get the Vercel preview repo card response.
|
||||
const serverRepoSvg = await axios.get(
|
||||
`${VERCEL_PREVIEW_URL}/api/pin/?username=${USER}&repo=${REPO}&${CACHE_BURST_STRING}`,
|
||||
`${VERCEL_PREVIEW_URL}/api/pin/?username=${USER}&repo=${REPO}`,
|
||||
);
|
||||
|
||||
// Check if Repo card from deployment matches the local Repo card.
|
||||
@@ -1,7 +1,7 @@
|
||||
import "@testing-library/jest-dom";
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import { fetchRepo } from "../src/fetchers/repo-fetcher.js";
|
||||
import { fetchRepo } from "../src/fetchers/repo-fetcher";
|
||||
|
||||
const data_repo = {
|
||||
repository: {
|
||||
@@ -23,7 +23,6 @@ const data_user = {
|
||||
organization: null,
|
||||
},
|
||||
};
|
||||
|
||||
const data_org = {
|
||||
data: {
|
||||
user: null,
|
||||
@@ -1,8 +1,8 @@
|
||||
import "@testing-library/jest-dom";
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import { calculateRank } from "../src/calculateRank.js";
|
||||
import { fetchStats } from "../src/fetchers/stats-fetcher.js";
|
||||
import { calculateRank } from "../src/calculateRank";
|
||||
import { fetchStats } from "../src/fetchers/stats-fetcher";
|
||||
|
||||
const data = {
|
||||
data: {
|
||||
@@ -97,9 +97,9 @@ beforeEach(() => {
|
||||
.onPost("https://api.github.com/graphql")
|
||||
.replyOnce(200, data)
|
||||
.onPost("https://api.github.com/graphql")
|
||||
.replyOnce(200, firstRepositoriesData);
|
||||
// .onPost("https://api.github.com/graphql") // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
// .replyOnce(200, secondRepositoriesData); // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
.replyOnce(200, firstRepositoriesData)
|
||||
.onPost("https://api.github.com/graphql")
|
||||
.replyOnce(200, secondRepositoriesData);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -114,8 +114,7 @@ describe("Test fetchStats", () => {
|
||||
totalRepos: 5,
|
||||
followers: 100,
|
||||
contributions: 61,
|
||||
// stargazers: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
stargazers: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
stargazers: 400,
|
||||
prs: 300,
|
||||
issues: 200,
|
||||
});
|
||||
@@ -126,8 +125,7 @@ describe("Test fetchStats", () => {
|
||||
totalCommits: 100,
|
||||
totalIssues: 200,
|
||||
totalPRs: 300,
|
||||
// totalStars: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
totalStars: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
totalStars: 400,
|
||||
rank,
|
||||
});
|
||||
});
|
||||
@@ -178,8 +176,7 @@ describe("Test fetchStats", () => {
|
||||
totalRepos: 5,
|
||||
followers: 100,
|
||||
contributions: 61,
|
||||
// stargazers: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
stargazers: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
stargazers: 400,
|
||||
prs: 300,
|
||||
issues: 200,
|
||||
});
|
||||
@@ -190,8 +187,7 @@ describe("Test fetchStats", () => {
|
||||
totalCommits: 150,
|
||||
totalIssues: 200,
|
||||
totalPRs: 300,
|
||||
// totalStars: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
totalStars: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
totalStars: 400,
|
||||
rank,
|
||||
});
|
||||
});
|
||||
@@ -207,8 +203,7 @@ describe("Test fetchStats", () => {
|
||||
totalRepos: 5,
|
||||
followers: 100,
|
||||
contributions: 61,
|
||||
// stargazers: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
stargazers: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
stargazers: 400,
|
||||
prs: 300,
|
||||
issues: 200,
|
||||
});
|
||||
@@ -219,8 +214,7 @@ describe("Test fetchStats", () => {
|
||||
totalCommits: 1050,
|
||||
totalIssues: 200,
|
||||
totalPRs: 300,
|
||||
// totalStars: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
totalStars: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
totalStars: 400,
|
||||
rank,
|
||||
});
|
||||
});
|
||||
@@ -236,8 +230,7 @@ describe("Test fetchStats", () => {
|
||||
totalRepos: 5,
|
||||
followers: 100,
|
||||
contributions: 61,
|
||||
// stargazers: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
stargazers: 200, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
stargazers: 300,
|
||||
prs: 300,
|
||||
issues: 200,
|
||||
});
|
||||
@@ -248,8 +241,7 @@ describe("Test fetchStats", () => {
|
||||
totalCommits: 1050,
|
||||
totalIssues: 200,
|
||||
totalPRs: 300,
|
||||
// totalStars: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
totalStars: 200, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||
totalStars: 300,
|
||||
rank,
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import "@testing-library/jest-dom";
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import { fetchTopLanguages } from "../src/fetchers/top-languages-fetcher.js";
|
||||
import { fetchTopLanguages } from "../src/fetchers/top-languages-fetcher";
|
||||
|
||||
const mock = new MockAdapter(axios);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "@testing-library/jest-dom";
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import { fetchWakatimeStats } from "../src/fetchers/wakatime-fetcher.js";
|
||||
import { fetchWakatimeStats } from "../src/fetchers/wakatime-fetcher";
|
||||
const mock = new MockAdapter(axios);
|
||||
|
||||
afterEach(() => {
|
||||
@@ -1,4 +1,4 @@
|
||||
import { flexLayout } from "../src/common/utils.js";
|
||||
import { flexLayout } from "../src/common/utils";
|
||||
|
||||
describe("flexLayout", () => {
|
||||
it("should work with row & col layouts", () => {
|
||||
@@ -2,9 +2,9 @@ import { jest } from "@jest/globals";
|
||||
import "@testing-library/jest-dom";
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import pin from "../api/pin.js";
|
||||
import { renderRepoCard } from "../src/cards/repo-card.js";
|
||||
import { renderError } from "../src/common/utils.js";
|
||||
import pin from "../api/pin";
|
||||
import { renderRepoCard } from "../src/cards/repo-card";
|
||||
import { renderError } from "../src/common/utils";
|
||||
|
||||
const data_repo = {
|
||||
repository: {
|
||||
@@ -1,9 +1,9 @@
|
||||
import { queryByTestId } from "@testing-library/dom";
|
||||
import "@testing-library/jest-dom";
|
||||
import { cssToObject } from "@uppercod/css-to-object";
|
||||
import { renderRepoCard } from "../src/cards/repo-card.js";
|
||||
import { renderRepoCard } from "../src/cards/repo-card";
|
||||
|
||||
import { themes } from "../themes/index.js";
|
||||
import { themes } from "../themes/index";
|
||||
|
||||
const data_repo = {
|
||||
repository: {
|
||||
@@ -4,23 +4,23 @@ import {
|
||||
queryByTestId,
|
||||
} from "@testing-library/dom";
|
||||
import { cssToObject } from "@uppercod/css-to-object";
|
||||
import { renderStatsCard } from "../src/cards/stats-card.js";
|
||||
import { renderStatsCard } from "../src/cards/stats-card";
|
||||
// adds special assertions like toHaveTextContent
|
||||
import "@testing-library/jest-dom";
|
||||
|
||||
import { themes } from "../themes/index.js";
|
||||
|
||||
const stats = {
|
||||
name: "Anurag Hazra",
|
||||
totalStars: 100,
|
||||
totalCommits: 200,
|
||||
totalIssues: 300,
|
||||
totalPRs: 400,
|
||||
contributedTo: 500,
|
||||
rank: { level: "A+", score: 40 },
|
||||
};
|
||||
import { themes } from "../themes/index";
|
||||
|
||||
describe("Test renderStatsCard", () => {
|
||||
const stats = {
|
||||
name: "Anurag Hazra",
|
||||
totalStars: 100,
|
||||
totalCommits: 200,
|
||||
totalIssues: 300,
|
||||
totalPRs: 400,
|
||||
contributedTo: 500,
|
||||
rank: { level: "A+", score: 40 },
|
||||
};
|
||||
|
||||
it("should render correctly", () => {
|
||||
document.body.innerHTML = renderStatsCard(stats);
|
||||
|
||||
@@ -3,31 +3,31 @@ import { cssToObject } from "@uppercod/css-to-object";
|
||||
import {
|
||||
MIN_CARD_WIDTH,
|
||||
renderTopLanguages,
|
||||
} from "../src/cards/top-languages-card.js";
|
||||
} from "../src/cards/top-languages-card";
|
||||
// adds special assertions like toHaveTextContent
|
||||
import "@testing-library/jest-dom";
|
||||
|
||||
import { themes } from "../themes/index.js";
|
||||
|
||||
const langs = {
|
||||
HTML: {
|
||||
color: "#0f0",
|
||||
name: "HTML",
|
||||
size: 200,
|
||||
},
|
||||
javascript: {
|
||||
color: "#0ff",
|
||||
name: "javascript",
|
||||
size: 200,
|
||||
},
|
||||
css: {
|
||||
color: "#ff0",
|
||||
name: "css",
|
||||
size: 100,
|
||||
},
|
||||
};
|
||||
import { themes } from "../themes/index";
|
||||
|
||||
describe("Test renderTopLanguages", () => {
|
||||
const langs = {
|
||||
HTML: {
|
||||
color: "#0f0",
|
||||
name: "HTML",
|
||||
size: 200,
|
||||
},
|
||||
javascript: {
|
||||
color: "#0ff",
|
||||
name: "javascript",
|
||||
size: 200,
|
||||
},
|
||||
css: {
|
||||
color: "#ff0",
|
||||
name: "css",
|
||||
size: 100,
|
||||
},
|
||||
};
|
||||
|
||||
it("should render correctly", () => {
|
||||
document.body.innerHTML = renderTopLanguages(langs);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { queryByTestId } from "@testing-library/dom";
|
||||
import "@testing-library/jest-dom";
|
||||
import { renderWakatimeCard } from "../src/cards/wakatime-card.js";
|
||||
import { getCardColors } from "../src/common/utils.js";
|
||||
import { wakaTimeData } from "./fetchWakatime.test.js";
|
||||
import { renderWakatimeCard } from "../src/cards/wakatime-card";
|
||||
import { getCardColors } from "../src/common/utils";
|
||||
import { wakaTimeData } from "./fetchWakatime.test";
|
||||
|
||||
describe("Test Render Wakatime Card", () => {
|
||||
it("should render correctly", () => {
|
||||
@@ -1,7 +1,7 @@
|
||||
import { jest } from "@jest/globals";
|
||||
import "@testing-library/jest-dom";
|
||||
import { retryer } from "../src/common/retryer.js";
|
||||
import { logger } from "../src/common/utils.js";
|
||||
import { retryer } from "../src/common/retryer";
|
||||
import { logger } from "../src/common/utils";
|
||||
|
||||
const fetcher = jest.fn((variables, token) => {
|
||||
logger.log(variables, token);
|
||||
@@ -2,9 +2,9 @@ import { jest } from "@jest/globals";
|
||||
import "@testing-library/jest-dom";
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import topLangs from "../api/top-langs.js";
|
||||
import { renderTopLanguages } from "../src/cards/top-languages-card.js";
|
||||
import { renderError } from "../src/common/utils.js";
|
||||
import topLangs from "../api/top-langs";
|
||||
import { renderTopLanguages } from "../src/cards/top-languages-card";
|
||||
import { renderError } from "../src/common/utils";
|
||||
|
||||
const data_langs = {
|
||||
data: {
|
||||
@@ -1,257 +0,0 @@
|
||||
import { queryByTestId } from "@testing-library/dom";
|
||||
import "@testing-library/jest-dom";
|
||||
import {
|
||||
chunkArray,
|
||||
clampValue,
|
||||
encodeHTML,
|
||||
fallbackColor,
|
||||
flexLayout,
|
||||
getCardColors,
|
||||
isValidGradient,
|
||||
isValidHexColor,
|
||||
kFormatter,
|
||||
lowercaseTrim,
|
||||
measureText,
|
||||
parseArray,
|
||||
parseBoolean,
|
||||
parseEmojis,
|
||||
renderError,
|
||||
wrapTextMultiline,
|
||||
} from "../src/common/utils.js";
|
||||
|
||||
describe("Test utils.js", () => {
|
||||
it("should test renderError", () => {
|
||||
document.body.innerHTML = renderError("Something went wrong");
|
||||
expect(
|
||||
queryByTestId(document.body, "message").children[0],
|
||||
).toHaveTextContent(/Something went wrong/gim);
|
||||
expect(
|
||||
queryByTestId(document.body, "message").children[1],
|
||||
).toBeEmptyDOMElement(2);
|
||||
|
||||
// Secondary message
|
||||
document.body.innerHTML = renderError(
|
||||
"Something went wrong",
|
||||
"Secondary Message",
|
||||
);
|
||||
expect(
|
||||
queryByTestId(document.body, "message").children[1],
|
||||
).toHaveTextContent(/Secondary Message/gim);
|
||||
});
|
||||
|
||||
it("should test encodeHTML", () => {
|
||||
expect(encodeHTML(`<html>hello world<,.#4^&^@%!))`)).toBe(
|
||||
"<html>hello world<,.#4^&^@%!))",
|
||||
);
|
||||
});
|
||||
|
||||
it("should test kFormatter", () => {
|
||||
expect(kFormatter(1)).toBe(1);
|
||||
expect(kFormatter(-1)).toBe(-1);
|
||||
expect(kFormatter(500)).toBe(500);
|
||||
expect(kFormatter(1000)).toBe("1k");
|
||||
expect(kFormatter(10000)).toBe("10k");
|
||||
expect(kFormatter(12345)).toBe("12.3k");
|
||||
expect(kFormatter(9900000)).toBe("9900k");
|
||||
});
|
||||
|
||||
it("should test isValidHexColor", () => {
|
||||
expect(isValidHexColor("00f000")).toBe(true);
|
||||
expect(isValidHexColor("00f0003f")).toBe(true);
|
||||
expect(isValidHexColor("0f03")).toBe(true);
|
||||
expect(isValidHexColor("0f0")).toBe(true);
|
||||
|
||||
expect(isValidHexColor("00f00X")).toBe(false);
|
||||
expect(isValidHexColor("#")).toBe(false);
|
||||
expect(isValidHexColor("")).toBe(false);
|
||||
expect(isValidHexColor("0")).toBe(false);
|
||||
expect(isValidHexColor("00")).toBe(false);
|
||||
expect(isValidHexColor("00f00")).toBe(false);
|
||||
expect(isValidHexColor("00f00")).toBe(false);
|
||||
});
|
||||
|
||||
it("should test parseBoolean", () => {
|
||||
expect(parseBoolean(true)).toBe(true);
|
||||
expect(parseBoolean(false)).toBe(false);
|
||||
|
||||
expect(parseBoolean("true")).toBe(true);
|
||||
expect(parseBoolean("false")).toBe(false);
|
||||
|
||||
expect(parseBoolean(undefined)).toBe(undefined);
|
||||
});
|
||||
|
||||
it("should test parseArray", () => {
|
||||
expect(parseArray("1")).toEqual(["1"]);
|
||||
expect(parseArray("1,2,3")).toEqual(["1", "2", "3"]);
|
||||
expect(parseArray("test")).toEqual(["test"]);
|
||||
});
|
||||
|
||||
it("should test clampValue", () => {
|
||||
expect(clampValue(1, 0, 2)).toBe(1);
|
||||
expect(clampValue(1, 2, 3)).toBe(2);
|
||||
expect(clampValue(1, 0, 0)).toBe(0);
|
||||
expect(clampValue(1, 0, -1)).toBe(0);
|
||||
});
|
||||
|
||||
it("should test isValidGradient", () => {
|
||||
expect(isValidGradient(["90", "fff", "000"])).toBe(true);
|
||||
expect(isValidGradient(["90", "fff", "000", "fff"])).toBe(true);
|
||||
|
||||
expect(isValidGradient(["fff", "fff", "fxf"])).toBe(false);
|
||||
expect(isValidGradient(["90", "fff", "fxf"])).toBe(false);
|
||||
expect(isValidGradient(["90", "fxf", ""])).toBe(false);
|
||||
});
|
||||
|
||||
it("should test fallbackColor", () => {
|
||||
expect(fallbackColor("fff", "000")).toBe("#fff");
|
||||
expect(fallbackColor("fxf", "#000")).toBe("#000");
|
||||
expect(fallbackColor("90,fff,000", "#000")).toStrictEqual([
|
||||
"90",
|
||||
"fff",
|
||||
"000",
|
||||
]);
|
||||
expect(fallbackColor("90,fff,fxf", "#000")).toBe("#000");
|
||||
});
|
||||
|
||||
it("should test flexLayout", () => {
|
||||
const items = Array(2).fill(`<g></g>`);
|
||||
|
||||
const columnLayout = flexLayout({
|
||||
items,
|
||||
gap: 25,
|
||||
direction: "column",
|
||||
sizes: [],
|
||||
});
|
||||
expect(columnLayout[0]).toContain("translate(0, 0)");
|
||||
expect(columnLayout[1]).toContain("translate(0, 25)");
|
||||
|
||||
const rowLayout = flexLayout({
|
||||
items,
|
||||
gap: 25,
|
||||
direction: "row",
|
||||
sizes: [],
|
||||
});
|
||||
expect(rowLayout[0]).toContain("translate(0, 0)");
|
||||
expect(rowLayout[1]).toContain("translate(25, 0)");
|
||||
|
||||
const sizedLayout = flexLayout({
|
||||
items,
|
||||
gap: 25,
|
||||
direction: "row",
|
||||
sizes: [25, 0],
|
||||
});
|
||||
expect(sizedLayout[0]).toContain("translate(0, 0)");
|
||||
expect(sizedLayout[1]).toContain("translate(50, 0)");
|
||||
});
|
||||
|
||||
it("getCardColors: should return expected values", () => {
|
||||
let colors = getCardColors({
|
||||
title_color: "f00",
|
||||
text_color: "0f0",
|
||||
icon_color: "00f",
|
||||
bg_color: "fff",
|
||||
border_color: "fff",
|
||||
theme: "dark",
|
||||
});
|
||||
expect(colors).toStrictEqual({
|
||||
titleColor: "#f00",
|
||||
textColor: "#0f0",
|
||||
iconColor: "#00f",
|
||||
bgColor: "#fff",
|
||||
borderColor: "#fff",
|
||||
});
|
||||
});
|
||||
|
||||
it("getCardColors: should fallback to default colors if color is invalid", () => {
|
||||
let colors = getCardColors({
|
||||
title_color: "invalidcolor",
|
||||
text_color: "0f0",
|
||||
icon_color: "00f",
|
||||
bg_color: "fff",
|
||||
border_color: "invalidColor",
|
||||
theme: "dark",
|
||||
});
|
||||
expect(colors).toStrictEqual({
|
||||
titleColor: "#2f80ed",
|
||||
textColor: "#0f0",
|
||||
iconColor: "#00f",
|
||||
bgColor: "#fff",
|
||||
borderColor: "#e4e2e2",
|
||||
});
|
||||
});
|
||||
|
||||
it("getCardColors: should fallback to specified theme colors if is not defined", () => {
|
||||
let colors = getCardColors({
|
||||
theme: "dark",
|
||||
});
|
||||
expect(colors).toStrictEqual({
|
||||
titleColor: "#fff",
|
||||
textColor: "#9f9f9f",
|
||||
iconColor: "#79ff97",
|
||||
bgColor: "#151515",
|
||||
borderColor: "#e4e2e2",
|
||||
});
|
||||
});
|
||||
|
||||
it("wrapTextMultiline: should not wrap small texts", () => {
|
||||
{
|
||||
let multiLineText = wrapTextMultiline("Small text should not wrap");
|
||||
expect(multiLineText).toEqual(["Small text should not wrap"]);
|
||||
}
|
||||
});
|
||||
|
||||
it("wrapTextMultiline: should wrap large texts", () => {
|
||||
let multiLineText = wrapTextMultiline(
|
||||
"Hello world long long long text",
|
||||
20,
|
||||
3,
|
||||
);
|
||||
expect(multiLineText).toEqual(["Hello world long", "long long text"]);
|
||||
});
|
||||
|
||||
it("wrapTextMultiline: should wrap large texts and limit max lines", () => {
|
||||
let multiLineText = wrapTextMultiline(
|
||||
"Hello world long long long text",
|
||||
10,
|
||||
2,
|
||||
);
|
||||
expect(multiLineText).toEqual(["Hello", "world long..."]);
|
||||
});
|
||||
|
||||
it("wrapTextMultiline: should wrap chinese by punctuation", () => {
|
||||
let multiLineText = wrapTextMultiline(
|
||||
"专门为刚开始刷题的同学准备的算法基地,没有最细只有更细,立志用动画将晦涩难懂的算法说的通俗易懂!",
|
||||
);
|
||||
expect(multiLineText.length).toEqual(3);
|
||||
expect(multiLineText[0].length).toEqual(18 * 8); // &#xxxxx; x 8
|
||||
});
|
||||
|
||||
it("should test measureText", () => {
|
||||
expect(measureText("test")).toEqual(16.078125);
|
||||
|
||||
expect(measureText("test", 12)).toEqual(19.293750000000003);
|
||||
});
|
||||
|
||||
it("should test lowercaseTrim", () => {
|
||||
expect(lowercaseTrim("test")).toEqual("test");
|
||||
expect(lowercaseTrim(" test ")).toEqual("test");
|
||||
expect(lowercaseTrim(" Test ")).toEqual("test");
|
||||
});
|
||||
|
||||
it("should test chunkArray", () => {
|
||||
expect(chunkArray([1, 2, 3], 1)).toEqual([[1], [2], [3]]);
|
||||
expect(chunkArray([1, 2, 3, 4, 5], 2)).toEqual([[1, 2], [3, 4], [5]]);
|
||||
expect(chunkArray([1, 2, 3, 4, 5, 6, 7], 3)).toEqual([
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7],
|
||||
]);
|
||||
});
|
||||
|
||||
it("should test parseEmojis", () => {
|
||||
expect(parseEmojis("test")).toEqual("test");
|
||||
expect(parseEmojis("test :smile:")).toEqual("test 😄");
|
||||
expect(parseEmojis("test :smile: :+1:")).toEqual("test 😄 👍");
|
||||
expect(parseEmojis("test :smile: :+1: :smile:")).toEqual("test 😄 👍 😄");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
import { queryByTestId } from "@testing-library/dom";
|
||||
import "@testing-library/jest-dom";
|
||||
import {
|
||||
encodeHTML,
|
||||
getCardColors,
|
||||
kFormatter,
|
||||
renderError,
|
||||
wrapTextMultiline,
|
||||
} from "../src/common/utils";
|
||||
|
||||
describe("Test utils", () => {
|
||||
it("should test kFormatter", () => {
|
||||
expect(kFormatter(1)).toBe(1);
|
||||
expect(kFormatter(-1)).toBe(-1);
|
||||
expect(kFormatter(500)).toBe(500);
|
||||
expect(kFormatter(1000)).toBe("1k");
|
||||
expect(kFormatter(10000)).toBe("10k");
|
||||
expect(kFormatter(12345)).toBe("12.3k");
|
||||
expect(kFormatter(9900000)).toBe("9900k");
|
||||
});
|
||||
|
||||
it("should test encodeHTML", () => {
|
||||
expect(encodeHTML(`<html>hello world<,.#4^&^@%!))`)).toBe(
|
||||
"<html>hello world<,.#4^&^@%!))",
|
||||
);
|
||||
});
|
||||
|
||||
it("should test renderError", () => {
|
||||
document.body.innerHTML = renderError("Something went wrong");
|
||||
expect(
|
||||
queryByTestId(document.body, "message").children[0],
|
||||
).toHaveTextContent(/Something went wrong/gim);
|
||||
expect(
|
||||
queryByTestId(document.body, "message").children[1],
|
||||
).toBeEmptyDOMElement(2);
|
||||
|
||||
// Secondary message
|
||||
document.body.innerHTML = renderError(
|
||||
"Something went wrong",
|
||||
"Secondary Message",
|
||||
);
|
||||
expect(
|
||||
queryByTestId(document.body, "message").children[1],
|
||||
).toHaveTextContent(/Secondary Message/gim);
|
||||
});
|
||||
|
||||
it("getCardColors: should return expected values", () => {
|
||||
let colors = getCardColors({
|
||||
title_color: "f00",
|
||||
text_color: "0f0",
|
||||
icon_color: "00f",
|
||||
bg_color: "fff",
|
||||
border_color: "fff",
|
||||
theme: "dark",
|
||||
});
|
||||
expect(colors).toStrictEqual({
|
||||
titleColor: "#f00",
|
||||
textColor: "#0f0",
|
||||
iconColor: "#00f",
|
||||
bgColor: "#fff",
|
||||
borderColor: "#fff",
|
||||
});
|
||||
});
|
||||
|
||||
it("getCardColors: should fallback to default colors if color is invalid", () => {
|
||||
let colors = getCardColors({
|
||||
title_color: "invalidcolor",
|
||||
text_color: "0f0",
|
||||
icon_color: "00f",
|
||||
bg_color: "fff",
|
||||
border_color: "invalidColor",
|
||||
theme: "dark",
|
||||
});
|
||||
expect(colors).toStrictEqual({
|
||||
titleColor: "#2f80ed",
|
||||
textColor: "#0f0",
|
||||
iconColor: "#00f",
|
||||
bgColor: "#fff",
|
||||
borderColor: "#e4e2e2",
|
||||
});
|
||||
});
|
||||
|
||||
it("getCardColors: should fallback to specified theme colors if is not defined", () => {
|
||||
let colors = getCardColors({
|
||||
theme: "dark",
|
||||
});
|
||||
expect(colors).toStrictEqual({
|
||||
titleColor: "#fff",
|
||||
textColor: "#9f9f9f",
|
||||
iconColor: "#79ff97",
|
||||
bgColor: "#151515",
|
||||
borderColor: "#e4e2e2",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("wrapTextMultiline", () => {
|
||||
it("should not wrap small texts", () => {
|
||||
{
|
||||
let multiLineText = wrapTextMultiline("Small text should not wrap");
|
||||
expect(multiLineText).toEqual(["Small text should not wrap"]);
|
||||
}
|
||||
});
|
||||
it("should wrap large texts", () => {
|
||||
let multiLineText = wrapTextMultiline(
|
||||
"Hello world long long long text",
|
||||
20,
|
||||
3,
|
||||
);
|
||||
expect(multiLineText).toEqual(["Hello world long", "long long text"]);
|
||||
});
|
||||
it("should wrap large texts and limit max lines", () => {
|
||||
let multiLineText = wrapTextMultiline(
|
||||
"Hello world long long long text",
|
||||
10,
|
||||
2,
|
||||
);
|
||||
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
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,27 @@
|
||||
export const themes = {
|
||||
|
||||
/** Theme properties. */
|
||||
interface ThemeProperties {
|
||||
/** Title color. */
|
||||
title_color: string;
|
||||
/** Icon color. */
|
||||
icon_color: string;
|
||||
/** Text color. */
|
||||
text_color: string;
|
||||
/** Background color. */
|
||||
bg_color: string;
|
||||
/** Border color. */
|
||||
border_color: string;
|
||||
}
|
||||
|
||||
/** Card theme. */
|
||||
interface Theme {
|
||||
[index: string]: ThemeProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Themes for the cards.
|
||||
*/
|
||||
export const themes: Theme = {
|
||||
default: {
|
||||
title_color: "2f80ed",
|
||||
icon_color: "4c71f2",
|
||||
@@ -11,348 +34,406 @@ export const themes = {
|
||||
icon_color: "586069", // icon color is different
|
||||
text_color: "434d58",
|
||||
bg_color: "fffefe",
|
||||
border_color: "",
|
||||
},
|
||||
transparent: {
|
||||
title_color: "006AFF",
|
||||
icon_color: "0579C3",
|
||||
text_color: "417E87",
|
||||
bg_color: "ffffff00",
|
||||
border_color: "",
|
||||
},
|
||||
dark: {
|
||||
title_color: "fff",
|
||||
icon_color: "79ff97",
|
||||
text_color: "9f9f9f",
|
||||
bg_color: "151515",
|
||||
border_color: "",
|
||||
},
|
||||
radical: {
|
||||
title_color: "fe428e",
|
||||
icon_color: "f8d847",
|
||||
text_color: "a9fef7",
|
||||
bg_color: "141321",
|
||||
border_color: "",
|
||||
},
|
||||
merko: {
|
||||
title_color: "abd200",
|
||||
icon_color: "b7d364",
|
||||
text_color: "68b587",
|
||||
bg_color: "0a0f0b",
|
||||
border_color: "",
|
||||
},
|
||||
gruvbox: {
|
||||
title_color: "fabd2f",
|
||||
icon_color: "fe8019",
|
||||
text_color: "8ec07c",
|
||||
bg_color: "282828",
|
||||
border_color: "",
|
||||
},
|
||||
gruvbox_light: {
|
||||
title_color: "b57614",
|
||||
icon_color: "af3a03",
|
||||
text_color: "427b58",
|
||||
bg_color: "fbf1c7",
|
||||
border_color: "",
|
||||
},
|
||||
tokyonight: {
|
||||
title_color: "70a5fd",
|
||||
icon_color: "bf91f3",
|
||||
text_color: "38bdae",
|
||||
bg_color: "1a1b27",
|
||||
border_color: "",
|
||||
},
|
||||
onedark: {
|
||||
title_color: "e4bf7a",
|
||||
icon_color: "8eb573",
|
||||
text_color: "df6d74",
|
||||
bg_color: "282c34",
|
||||
border_color: "",
|
||||
},
|
||||
cobalt: {
|
||||
title_color: "e683d9",
|
||||
icon_color: "0480ef",
|
||||
text_color: "75eeb2",
|
||||
bg_color: "193549",
|
||||
border_color: "",
|
||||
},
|
||||
synthwave: {
|
||||
title_color: "e2e9ec",
|
||||
icon_color: "ef8539",
|
||||
text_color: "e5289e",
|
||||
bg_color: "2b213a",
|
||||
border_color: "",
|
||||
},
|
||||
highcontrast: {
|
||||
title_color: "e7f216",
|
||||
icon_color: "00ffff",
|
||||
text_color: "fff",
|
||||
bg_color: "000",
|
||||
border_color: "",
|
||||
},
|
||||
dracula: {
|
||||
title_color: "ff6e96",
|
||||
icon_color: "79dafa",
|
||||
text_color: "f8f8f2",
|
||||
bg_color: "282a36",
|
||||
border_color: "",
|
||||
},
|
||||
prussian: {
|
||||
title_color: "bddfff",
|
||||
icon_color: "38a0ff",
|
||||
text_color: "6e93b5",
|
||||
bg_color: "172f45",
|
||||
border_color: "",
|
||||
},
|
||||
monokai: {
|
||||
title_color: "eb1f6a",
|
||||
icon_color: "e28905",
|
||||
text_color: "f1f1eb",
|
||||
bg_color: "272822",
|
||||
border_color: "",
|
||||
},
|
||||
vue: {
|
||||
title_color: "41b883",
|
||||
icon_color: "41b883",
|
||||
text_color: "273849",
|
||||
bg_color: "fffefe",
|
||||
border_color: "",
|
||||
},
|
||||
"vue-dark": {
|
||||
title_color: "41b883",
|
||||
icon_color: "41b883",
|
||||
text_color: "fffefe",
|
||||
bg_color: "273849",
|
||||
border_color: "",
|
||||
},
|
||||
"shades-of-purple": {
|
||||
title_color: "fad000",
|
||||
icon_color: "b362ff",
|
||||
text_color: "a599e9",
|
||||
bg_color: "2d2b55",
|
||||
border_color: "",
|
||||
},
|
||||
nightowl: {
|
||||
title_color: "c792ea",
|
||||
icon_color: "ffeb95",
|
||||
text_color: "7fdbca",
|
||||
bg_color: "011627",
|
||||
border_color: "",
|
||||
},
|
||||
buefy: {
|
||||
title_color: "7957d5",
|
||||
icon_color: "ff3860",
|
||||
text_color: "363636",
|
||||
bg_color: "ffffff",
|
||||
border_color: "",
|
||||
},
|
||||
"blue-green": {
|
||||
title_color: "2f97c1",
|
||||
icon_color: "f5b700",
|
||||
text_color: "0cf574",
|
||||
bg_color: "040f0f",
|
||||
border_color: "",
|
||||
},
|
||||
algolia: {
|
||||
title_color: "00AEFF",
|
||||
icon_color: "2DDE98",
|
||||
text_color: "FFFFFF",
|
||||
bg_color: "050F2C",
|
||||
border_color: "",
|
||||
},
|
||||
"great-gatsby": {
|
||||
title_color: "ffa726",
|
||||
icon_color: "ffb74d",
|
||||
text_color: "ffd95b",
|
||||
bg_color: "000000",
|
||||
border_color: "",
|
||||
},
|
||||
darcula: {
|
||||
title_color: "BA5F17",
|
||||
icon_color: "84628F",
|
||||
text_color: "BEBEBE",
|
||||
bg_color: "242424",
|
||||
border_color: "",
|
||||
},
|
||||
bear: {
|
||||
title_color: "e03c8a",
|
||||
icon_color: "00AEFF",
|
||||
text_color: "bcb28d",
|
||||
bg_color: "1f2023",
|
||||
border_color: "",
|
||||
},
|
||||
"solarized-dark": {
|
||||
title_color: "268bd2",
|
||||
icon_color: "b58900",
|
||||
text_color: "859900",
|
||||
bg_color: "002b36",
|
||||
border_color: "",
|
||||
},
|
||||
"solarized-light": {
|
||||
title_color: "268bd2",
|
||||
icon_color: "b58900",
|
||||
text_color: "859900",
|
||||
bg_color: "fdf6e3",
|
||||
border_color: "",
|
||||
},
|
||||
"chartreuse-dark": {
|
||||
title_color: "7fff00",
|
||||
icon_color: "00AEFF",
|
||||
text_color: "fff",
|
||||
bg_color: "000",
|
||||
border_color: "",
|
||||
},
|
||||
nord: {
|
||||
title_color: "81a1c1",
|
||||
text_color: "d8dee9",
|
||||
icon_color: "88c0d0",
|
||||
bg_color: "2e3440",
|
||||
border_color: "",
|
||||
},
|
||||
gotham: {
|
||||
title_color: "2aa889",
|
||||
icon_color: "599cab",
|
||||
text_color: "99d1ce",
|
||||
bg_color: "0c1014",
|
||||
border_color: "",
|
||||
},
|
||||
"material-palenight": {
|
||||
title_color: "c792ea",
|
||||
icon_color: "89ddff",
|
||||
text_color: "a6accd",
|
||||
bg_color: "292d3e",
|
||||
border_color: "",
|
||||
},
|
||||
graywhite: {
|
||||
title_color: "24292e",
|
||||
icon_color: "24292e",
|
||||
text_color: "24292e",
|
||||
bg_color: "ffffff",
|
||||
border_color: "",
|
||||
},
|
||||
"vision-friendly-dark": {
|
||||
title_color: "ffb000",
|
||||
icon_color: "785ef0",
|
||||
text_color: "ffffff",
|
||||
bg_color: "000000",
|
||||
border_color: "",
|
||||
},
|
||||
"ayu-mirage": {
|
||||
title_color: "f4cd7c",
|
||||
icon_color: "73d0ff",
|
||||
text_color: "c7c8c2",
|
||||
bg_color: "1f2430",
|
||||
border_color: "",
|
||||
},
|
||||
"midnight-purple": {
|
||||
title_color: "9745f5",
|
||||
icon_color: "9f4bff",
|
||||
text_color: "ffffff",
|
||||
bg_color: "000000",
|
||||
border_color: "",
|
||||
},
|
||||
calm: {
|
||||
title_color: "e07a5f",
|
||||
icon_color: "edae49",
|
||||
text_color: "ebcfb2",
|
||||
bg_color: "373f51",
|
||||
border_color: "",
|
||||
},
|
||||
"flag-india": {
|
||||
title_color: "ff8f1c",
|
||||
icon_color: "250E62",
|
||||
text_color: "509E2F",
|
||||
bg_color: "ffffff",
|
||||
border_color: "",
|
||||
},
|
||||
omni: {
|
||||
title_color: "FF79C6",
|
||||
icon_color: "e7de79",
|
||||
text_color: "E1E1E6",
|
||||
bg_color: "191622",
|
||||
border_color: "",
|
||||
},
|
||||
react: {
|
||||
title_color: "61dafb",
|
||||
icon_color: "61dafb",
|
||||
text_color: "ffffff",
|
||||
bg_color: "20232a",
|
||||
border_color: "",
|
||||
},
|
||||
jolly: {
|
||||
title_color: "ff64da",
|
||||
icon_color: "a960ff",
|
||||
text_color: "ffffff",
|
||||
bg_color: "291B3E",
|
||||
border_color: "",
|
||||
},
|
||||
maroongold: {
|
||||
title_color: "F7EF8A",
|
||||
icon_color: "F7EF8A",
|
||||
text_color: "E0AA3E",
|
||||
bg_color: "260000",
|
||||
border_color: "",
|
||||
},
|
||||
yeblu: {
|
||||
title_color: "ffff00",
|
||||
icon_color: "ffff00",
|
||||
text_color: "ffffff",
|
||||
bg_color: "002046",
|
||||
border_color: "",
|
||||
},
|
||||
blueberry: {
|
||||
title_color: "82aaff",
|
||||
icon_color: "89ddff",
|
||||
text_color: "27e8a7",
|
||||
bg_color: "242938",
|
||||
border_color: "",
|
||||
},
|
||||
slateorange: {
|
||||
title_color: "faa627",
|
||||
icon_color: "faa627",
|
||||
text_color: "ffffff",
|
||||
bg_color: "36393f",
|
||||
border_color: "",
|
||||
},
|
||||
kacho_ga: {
|
||||
title_color: "bf4a3f",
|
||||
icon_color: "a64833",
|
||||
text_color: "d9c8a9",
|
||||
bg_color: "402b23",
|
||||
border_color: "",
|
||||
},
|
||||
outrun: {
|
||||
title_color: "ffcc00",
|
||||
icon_color: "ff1aff",
|
||||
text_color: "8080ff",
|
||||
bg_color: "141439",
|
||||
border_color: "",
|
||||
},
|
||||
ocean_dark: {
|
||||
title_color: "8957B2",
|
||||
icon_color: "FFFFFF",
|
||||
text_color: "92D534",
|
||||
bg_color: "151A28",
|
||||
border_color: "",
|
||||
},
|
||||
city_lights: {
|
||||
title_color: "5D8CB3",
|
||||
icon_color: "4798FF",
|
||||
text_color: "718CA1",
|
||||
bg_color: "1D252C",
|
||||
border_color: "",
|
||||
},
|
||||
github_dark: {
|
||||
title_color: "58A6FF",
|
||||
icon_color: "1F6FEB",
|
||||
text_color: "C3D1D9",
|
||||
bg_color: "0D1117",
|
||||
border_color: "",
|
||||
},
|
||||
discord_old_blurple: {
|
||||
title_color: "7289DA",
|
||||
icon_color: "7289DA",
|
||||
text_color: "FFFFFF",
|
||||
bg_color: "2C2F33",
|
||||
border_color: "",
|
||||
},
|
||||
aura_dark: {
|
||||
title_color: "ff7372",
|
||||
icon_color: "6cffd0",
|
||||
text_color: "dbdbdb",
|
||||
bg_color: "252334",
|
||||
border_color: "",
|
||||
},
|
||||
panda: {
|
||||
title_color: "19f9d899",
|
||||
icon_color: "19f9d899",
|
||||
text_color: "FF75B5",
|
||||
bg_color: "31353a",
|
||||
border_color: "",
|
||||
},
|
||||
noctis_minimus: {
|
||||
title_color: "d3b692",
|
||||
icon_color: "72b7c0",
|
||||
text_color: "c5cdd3",
|
||||
bg_color: "1b2932",
|
||||
border_color: "",
|
||||
},
|
||||
cobalt2: {
|
||||
title_color: "ffc600",
|
||||
icon_color: "ffffff",
|
||||
text_color: "0088ff",
|
||||
bg_color: "193549",
|
||||
border_color: "",
|
||||
},
|
||||
swift: {
|
||||
title_color: "000000",
|
||||
icon_color: "f05237",
|
||||
text_color: "000000",
|
||||
bg_color: "f7f7f7",
|
||||
border_color: "",
|
||||
},
|
||||
aura: {
|
||||
title_color: "a277ff",
|
||||
icon_color: "ffca85",
|
||||
text_color: "61ffca",
|
||||
bg_color: "15141b",
|
||||
border_color: "",
|
||||
},
|
||||
apprentice: {
|
||||
title_color: "ffffff",
|
||||
icon_color: "ffffaf",
|
||||
text_color: "bcbcbc",
|
||||
bg_color: "262626",
|
||||
border_color: "",
|
||||
},
|
||||
moltack: {
|
||||
title_color: "86092C",
|
||||
icon_color: "86092C",
|
||||
text_color: "574038",
|
||||
bg_color: "F5E1C0",
|
||||
border_color: "",
|
||||
},
|
||||
codeSTACKr: {
|
||||
title_color: "ff652f",
|
||||
@@ -366,7 +447,8 @@ export const themes = {
|
||||
icon_color: "ebbcba",
|
||||
text_color: "e0def4",
|
||||
bg_color: "191724",
|
||||
border_color: "",
|
||||
},
|
||||
};
|
||||
|
||||
export default themes;
|
||||
export type ThemeEnum = keyof typeof themes;
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
"baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
"typeRoots": ["./node_modules/@types", "./src/types"],
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
"resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"functions": {
|
||||
"api/*.js": {
|
||||
"api/*.ts": {
|
||||
"memory": 128,
|
||||
"maxDuration": 30
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user