forked from mirrored/github-readme-stats
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
533fe040a6 |
@@ -11,11 +11,9 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Describe the bug
|
label: Describe the bug
|
||||||
description: A clear and concise description of what the bug is.
|
description: A clear and concise description of what the bug is.
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Expected behavior
|
label: Expected behaviour
|
||||||
description:
|
description:
|
||||||
A clear and concise description of what you expected to happen.
|
A clear and concise description of what you expected to happen.
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ body:
|
|||||||
description:
|
description:
|
||||||
A clear and concise description of what the problem is. Ex. I'm always
|
A clear and concise description of what the problem is. Ex. I'm always
|
||||||
frustrated when [...]
|
frustrated when [...]
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Describe the solution you'd like
|
label: Describe the solution you'd like
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
themes: themes/index.js
|
themes: themes/index.js
|
||||||
doc-translation: docs/*
|
doc-translation: docs/*
|
||||||
card-i18n: src/translations.js
|
card-i18n: src/translations.js
|
||||||
documentation: readme.md
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
file = open('./vercel.json', 'r')
|
|
||||||
str = file.read()
|
|
||||||
file = open('./vercel.json', 'w')
|
|
||||||
|
|
||||||
str = str.replace('"maxDuration": 10', '"maxDuration": 30')
|
|
||||||
|
|
||||||
file.write(str)
|
|
||||||
file.close()
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
name: Deployment Prep
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
config:
|
|
||||||
if: github.repository == 'anuraghazra/github-readme-stats'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Deployment Prep
|
|
||||||
run: python ./.github/workflows/deploy-prep.py
|
|
||||||
- uses: stefanzweifel/git-auto-commit-action@v4
|
|
||||||
with:
|
|
||||||
branch: vercel
|
|
||||||
create_branch: true
|
|
||||||
push_options: "--force"
|
|
||||||
@@ -5,7 +5,6 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
e2eTests:
|
e2eTests:
|
||||||
if:
|
if:
|
||||||
github.repository == 'anuraghazra/github-readme-stats' &&
|
|
||||||
github.event_name == 'deployment_status' &&
|
github.event_name == 'deployment_status' &&
|
||||||
github.event.deployment_status.state == 'success'
|
github.event.deployment_status.state == 'success'
|
||||||
name: Perform 2e2 tests
|
name: Perform 2e2 tests
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
closeEmptyIssuesAndTemplates:
|
closeEmptyIssuesAndTemplates:
|
||||||
if: github.repository == 'anuraghazra/github-readme-stats'
|
|
||||||
name: Close empty issues
|
name: Close empty issues
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -23,10 +23,6 @@ jobs:
|
|||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: npm
|
cache: npm
|
||||||
|
|
||||||
# Fix the unsafe repo error which was introduced by the CVE-2022-24765 git patches.
|
|
||||||
- name: Fix unsafe repo error
|
|
||||||
run: git config --global --add safe.directory ${{ github.workspace }}
|
|
||||||
|
|
||||||
- name: npm install, generate readme
|
- name: npm install, generate readme
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm ci
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
triage:
|
triage:
|
||||||
if: github.repository == 'anuraghazra/github-readme-stats'
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@v4
|
- uses: actions/labeler@v4
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
name: prs cache clean
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- closed
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
cleanup:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Cleanup
|
|
||||||
run: |
|
|
||||||
gh extension install actions/gh-actions-cache
|
|
||||||
|
|
||||||
REPO=${{ github.repository }}
|
|
||||||
BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge"
|
|
||||||
|
|
||||||
echo "Fetching list of cache key"
|
|
||||||
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 )
|
|
||||||
|
|
||||||
## Setting this to not fail the workflow while deleting cache keys.
|
|
||||||
set +e
|
|
||||||
echo "Deleting caches..."
|
|
||||||
for cacheKey in $cacheKeysForPR
|
|
||||||
do
|
|
||||||
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
|
|
||||||
done
|
|
||||||
echo "Done"
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
@@ -5,7 +5,6 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
closeOldThemePrs:
|
closeOldThemePrs:
|
||||||
if: github.repository == 'anuraghazra/github-readme-stats'
|
|
||||||
name: Close stale 'invalid' theme PRs
|
name: Close stale 'invalid' theme PRs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
@@ -27,5 +26,5 @@ jobs:
|
|||||||
|
|
||||||
- run: npm run close-stale-theme-prs
|
- run: npm run close-stale-theme-prs
|
||||||
env:
|
env:
|
||||||
STALE_DAYS: 20
|
STALE_DAYS: 15
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -34,4 +34,4 @@ jobs:
|
|||||||
npm run format:check
|
npm run format:check
|
||||||
|
|
||||||
- name: Code Coverage
|
- name: Code Coverage
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v1
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
showAndLabelTopIssues:
|
showAndLabelTopIssues:
|
||||||
if: github.repository == 'anuraghazra/github-readme-stats'
|
|
||||||
name: Update top issues Dashboard.
|
name: Update top issues Dashboard.
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
name: Update supported languages
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 */30 * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
updateLanguages:
|
|
||||||
if: github.repository == 'anuraghazra/github-readme-stats'
|
|
||||||
name: Update supported languages
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [16.x]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
cache: npm
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
env:
|
|
||||||
CI: true
|
|
||||||
|
|
||||||
- name: Run update-languages-json.js script
|
|
||||||
run: npm run generate-langs-json
|
|
||||||
|
|
||||||
- name: Create Pull Request if upstream language file is changed
|
|
||||||
uses: peter-evans/create-pull-request@v4
|
|
||||||
with:
|
|
||||||
commit-message: "refactor: update languages JSON"
|
|
||||||
branch: "update_langs/patch"
|
|
||||||
delete-branch: true
|
|
||||||
title: Update languages JSON
|
|
||||||
body:
|
|
||||||
"The
|
|
||||||
[update-langs](https://github.com/anuraghazra/github-readme-stats/actions/workflows/update-langs.yaml)
|
|
||||||
action found new/updated languages in the [upstream languages JSON
|
|
||||||
file](https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml)."
|
|
||||||
labels: "ci, lang-card"
|
|
||||||
@@ -10,5 +10,3 @@ vercel_token
|
|||||||
# IDE
|
# IDE
|
||||||
.vscode
|
.vscode
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
|
|
||||||
.vercel
|
|
||||||
|
|||||||
+8
-12
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
|
We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
|
||||||
|
|
||||||
- Reporting [an issue](https://github.com/anuraghazra/github-readme-stats/issues/new?assignees=&labels=bug&template=bug_report.yml).
|
- Reporting an issue
|
||||||
- [Discussing](https://github.com/anuraghazra/github-readme-stats/discussions) the current state of the code.
|
- Discussing the current state of the code
|
||||||
- Submitting [a fix](https://github.com/anuraghazra/github-readme-stats/compare).
|
- Submitting a fix
|
||||||
- Proposing [new features](https://github.com/anuraghazra/github-readme-stats/issues/new?assignees=&labels=enhancement&template=feature_request.yml).
|
- Proposing new features
|
||||||
- Becoming a maintainer.
|
- Becoming a maintainer
|
||||||
|
|
||||||
## All Changes Happen Through Pull Requests
|
## All Changes Happen Through Pull Requests
|
||||||
|
|
||||||
@@ -33,15 +33,11 @@ _(make sure you already have a [Vercel](https://vercel.com/) account)_
|
|||||||
1. Install [Vercel CLI](https://vercel.com/download).
|
1. Install [Vercel CLI](https://vercel.com/download).
|
||||||
2. Fork the repository and clone the code to your local machine.
|
2. Fork the repository and clone the code to your local machine.
|
||||||
3. Run `npm install` in the repository root.
|
3. Run `npm install` in the repository root.
|
||||||
4. Run the command `vercel` in the root and follow the steps there.
|
4. Run the command "vercel" in the root and follow the steps there.
|
||||||
5. Open `vercel.json` and set the maxDuration to 10.
|
5. Open `vercel.json` and set the maxDuration to 10.
|
||||||
6. Create a `.env` file in the root of the directory.
|
6. Create a `.env` file in the root of the directory.
|
||||||
7. In the .env file add a new variable named `PAT_1` with your [GitHub Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token).
|
7. In the .env file add a new variable named "PAT_1" with your [GitHub Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token).
|
||||||
8. Run the command `vercel dev` to start a development server at <https://localhost:3000>.
|
8. Run the command "vercel dev" to start a development server at <https://localhost:3000>.
|
||||||
9. The cards will then be available from this local endpoint (i.e. `https://localhost:3000/api?username=anuraghazra`).
|
|
||||||
|
|
||||||
> **Note**
|
|
||||||
> You can also debug any tests using the [VSCode Jest extension](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest). For more information see https://github.com/jest-community/vscode-jest/issues/912.
|
|
||||||
|
|
||||||
## Themes Contribution
|
## Themes Contribution
|
||||||
|
|
||||||
|
|||||||
+5
-13
@@ -1,3 +1,4 @@
|
|||||||
|
import * as dotenv from "dotenv";
|
||||||
import { renderStatsCard } from "../src/cards/stats-card.js";
|
import { renderStatsCard } from "../src/cards/stats-card.js";
|
||||||
import { blacklist } from "../src/common/blacklist.js";
|
import { blacklist } from "../src/common/blacklist.js";
|
||||||
import {
|
import {
|
||||||
@@ -10,6 +11,8 @@ import {
|
|||||||
import { fetchStats } from "../src/fetchers/stats-fetcher.js";
|
import { fetchStats } from "../src/fetchers/stats-fetcher.js";
|
||||||
import { isLocaleAvailable } from "../src/translations.js";
|
import { isLocaleAvailable } from "../src/translations.js";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const {
|
const {
|
||||||
username,
|
username,
|
||||||
@@ -23,7 +26,6 @@ export default async (req, res) => {
|
|||||||
include_all_commits,
|
include_all_commits,
|
||||||
line_height,
|
line_height,
|
||||||
title_color,
|
title_color,
|
||||||
ring_color,
|
|
||||||
icon_color,
|
icon_color,
|
||||||
text_color,
|
text_color,
|
||||||
text_bold,
|
text_bold,
|
||||||
@@ -35,9 +37,7 @@ export default async (req, res) => {
|
|||||||
locale,
|
locale,
|
||||||
disable_animations,
|
disable_animations,
|
||||||
border_radius,
|
border_radius,
|
||||||
number_format,
|
|
||||||
border_color,
|
border_color,
|
||||||
rank_icon,
|
|
||||||
} = req.query;
|
} = req.query;
|
||||||
res.setHeader("Content-Type", "image/svg+xml");
|
res.setHeader("Content-Type", "image/svg+xml");
|
||||||
|
|
||||||
@@ -63,12 +63,7 @@ export default async (req, res) => {
|
|||||||
CONSTANTS.ONE_DAY,
|
CONSTANTS.ONE_DAY,
|
||||||
);
|
);
|
||||||
|
|
||||||
res.setHeader(
|
res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
|
||||||
"Cache-Control",
|
|
||||||
`max-age=${
|
|
||||||
cacheSeconds / 2
|
|
||||||
}, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.send(
|
return res.send(
|
||||||
renderStatsCard(stats, {
|
renderStatsCard(stats, {
|
||||||
@@ -81,7 +76,6 @@ export default async (req, res) => {
|
|||||||
include_all_commits: parseBoolean(include_all_commits),
|
include_all_commits: parseBoolean(include_all_commits),
|
||||||
line_height,
|
line_height,
|
||||||
title_color,
|
title_color,
|
||||||
ring_color,
|
|
||||||
icon_color,
|
icon_color,
|
||||||
text_color,
|
text_color,
|
||||||
text_bold: parseBoolean(text_bold),
|
text_bold: parseBoolean(text_bold),
|
||||||
@@ -90,14 +84,12 @@ export default async (req, res) => {
|
|||||||
custom_title,
|
custom_title,
|
||||||
border_radius,
|
border_radius,
|
||||||
border_color,
|
border_color,
|
||||||
number_format,
|
|
||||||
locale: locale ? locale.toLowerCase() : null,
|
locale: locale ? locale.toLowerCase() : null,
|
||||||
disable_animations: parseBoolean(disable_animations),
|
disable_animations: parseBoolean(disable_animations),
|
||||||
rank_icon,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.setHeader("Cache-Control", `no-cache, no-store, must-revalidate`); // Don't cache error responses.
|
res.setHeader("Cache-Control", `no-store`); // Don't cache error responses.
|
||||||
return res.send(renderError(err.message, err.secondaryMessage));
|
return res.send(renderError(err.message, err.secondaryMessage));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-7
@@ -58,12 +58,7 @@ export default async (req, res) => {
|
|||||||
cacheSeconds = CONSTANTS.FOUR_HOURS;
|
cacheSeconds = CONSTANTS.FOUR_HOURS;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.setHeader(
|
res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
|
||||||
"Cache-Control",
|
|
||||||
`max-age=${
|
|
||||||
cacheSeconds / 2
|
|
||||||
}, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.send(
|
return res.send(
|
||||||
renderRepoCard(repoData, {
|
renderRepoCard(repoData, {
|
||||||
@@ -80,7 +75,7 @@ export default async (req, res) => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.setHeader("Cache-Control", `no-cache, no-store, must-revalidate`); // Don't cache error responses.
|
res.setHeader("Cache-Control", `no-store`); // Don't cache error responses.
|
||||||
return res.send(renderError(err.message, err.secondaryMessage));
|
return res.send(renderError(err.message, err.secondaryMessage));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Contains a simple cloud function that can be used to check which PATs are no
|
|
||||||
* longer working. It returns a list of valid PATs, expired PATs and PATs with errors.
|
|
||||||
*
|
|
||||||
* @description This function is currently rate limited to 1 request per 5 minutes.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { logger, request, dateDiff } from "../../src/common/utils.js";
|
|
||||||
export const RATE_LIMIT_SECONDS = 60 * 5; // 1 request per 5 minutes
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple uptime check fetcher for the PATs.
|
|
||||||
*
|
|
||||||
* @param {import('axios').AxiosRequestHeaders} variables
|
|
||||||
* @param {string} token
|
|
||||||
*/
|
|
||||||
const uptimeFetcher = (variables, token) => {
|
|
||||||
return request(
|
|
||||||
{
|
|
||||||
query: `
|
|
||||||
query {
|
|
||||||
rateLimit {
|
|
||||||
remaining
|
|
||||||
resetAt
|
|
||||||
},
|
|
||||||
}`,
|
|
||||||
variables,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Authorization: `bearer ${token}`,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAllPATs = () => {
|
|
||||||
return Object.keys(process.env).filter((key) => /PAT_\d*$/.exec(key));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether any of the PATs is expired.
|
|
||||||
*/
|
|
||||||
const getPATInfo = async (fetcher, variables) => {
|
|
||||||
const details = {};
|
|
||||||
const PATs = getAllPATs();
|
|
||||||
|
|
||||||
for (const pat of PATs) {
|
|
||||||
try {
|
|
||||||
const response = await fetcher(variables, process.env[pat]);
|
|
||||||
const errors = response.data.errors;
|
|
||||||
const hasErrors = Boolean(errors);
|
|
||||||
const errorType = errors?.[0]?.type;
|
|
||||||
const isRateLimited =
|
|
||||||
(hasErrors && errorType === "RATE_LIMITED") ||
|
|
||||||
response.data.data?.rateLimit?.remaining === 0;
|
|
||||||
|
|
||||||
// Store PATs with errors.
|
|
||||||
if (hasErrors && errorType !== "RATE_LIMITED") {
|
|
||||||
details[pat] = {
|
|
||||||
status: "error",
|
|
||||||
error: {
|
|
||||||
type: errors[0].type,
|
|
||||||
message: errors[0].message,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
continue;
|
|
||||||
} else if (isRateLimited) {
|
|
||||||
const date1 = new Date();
|
|
||||||
const date2 = new Date(response.data?.data?.rateLimit?.resetAt);
|
|
||||||
details[pat] = {
|
|
||||||
status: "exhausted",
|
|
||||||
remaining: 0,
|
|
||||||
resetIn: dateDiff(date2, date1) + " minutes",
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
details[pat] = {
|
|
||||||
status: "valid",
|
|
||||||
remaining: response.data.data.rateLimit.remaining,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// Store the PAT if it is expired.
|
|
||||||
const errorMessage = err.response?.data?.message?.toLowerCase();
|
|
||||||
if (errorMessage === "bad credentials") {
|
|
||||||
details[pat] = {
|
|
||||||
status: "expired",
|
|
||||||
};
|
|
||||||
} else if (errorMessage === "sorry. your account was suspended.") {
|
|
||||||
details[pat] = {
|
|
||||||
status: "suspended",
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterPATsByStatus = (status) => {
|
|
||||||
return Object.keys(details).filter((pat) => details[pat].status === status);
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortedDetails = Object.keys(details)
|
|
||||||
.sort()
|
|
||||||
.reduce((obj, key) => {
|
|
||||||
obj[key] = details[key];
|
|
||||||
return obj;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return {
|
|
||||||
validPATs: filterPATsByStatus("valid"),
|
|
||||||
expiredPATs: filterPATsByStatus("expired"),
|
|
||||||
exhaustedPATs: filterPATsByStatus("exhausted"),
|
|
||||||
suspendedPATs: filterPATsByStatus("suspended"),
|
|
||||||
errorPATs: filterPATsByStatus("error"),
|
|
||||||
details: sortedDetails,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cloud function that returns information about the used PATs.
|
|
||||||
*/
|
|
||||||
export default async (_, res) => {
|
|
||||||
res.setHeader("Content-Type", "application/json");
|
|
||||||
try {
|
|
||||||
// Add header to prevent abuse.
|
|
||||||
const PATsInfo = await getPATInfo(uptimeFetcher, {});
|
|
||||||
if (PATsInfo) {
|
|
||||||
res.setHeader(
|
|
||||||
"Cache-Control",
|
|
||||||
`max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
res.send(JSON.stringify(PATsInfo, null, 2));
|
|
||||||
} catch (err) {
|
|
||||||
// Throw error if something went wrong.
|
|
||||||
logger.error(err);
|
|
||||||
res.setHeader("Cache-Control", "no-store");
|
|
||||||
res.send("Something went wrong: " + err.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Contains a simple cloud function that can be used to check if the PATs are still
|
|
||||||
* functional.
|
|
||||||
*
|
|
||||||
* @description This function is currently rate limited to 1 request per 5 minutes.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import retryer from "../../src/common/retryer.js";
|
|
||||||
import { logger, request } from "../../src/common/utils.js";
|
|
||||||
|
|
||||||
export const RATE_LIMIT_SECONDS = 60 * 5; // 1 request per 5 minutes
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple uptime check fetcher for the PATs.
|
|
||||||
*
|
|
||||||
* @param {import('axios').AxiosRequestHeaders} variables
|
|
||||||
* @param {string} token
|
|
||||||
*/
|
|
||||||
const uptimeFetcher = (variables, token) => {
|
|
||||||
return request(
|
|
||||||
{
|
|
||||||
query: `
|
|
||||||
query {
|
|
||||||
rateLimit {
|
|
||||||
remaining
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Authorization: `bearer ${token}`,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates Json response that can be used for shields.io dynamic card generation.
|
|
||||||
*
|
|
||||||
* @param {*} up Whether the PATs are up or not.
|
|
||||||
* @returns Dynamic shields.io JSON response object.
|
|
||||||
*
|
|
||||||
* @see https://shields.io/endpoint.
|
|
||||||
*/
|
|
||||||
const shieldsUptimeBadge = (up) => {
|
|
||||||
const schemaVersion = 1;
|
|
||||||
const isError = true;
|
|
||||||
const label = "Public Instance";
|
|
||||||
const message = up ? "up" : "down";
|
|
||||||
const color = up ? "brightgreen" : "red";
|
|
||||||
return {
|
|
||||||
schemaVersion,
|
|
||||||
label,
|
|
||||||
message,
|
|
||||||
color,
|
|
||||||
isError,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cloud function that returns whether the PATs are still functional.
|
|
||||||
*/
|
|
||||||
export default async (req, res) => {
|
|
||||||
let { type } = req.query;
|
|
||||||
type = type ? type.toLowerCase() : "boolean";
|
|
||||||
|
|
||||||
res.setHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
try {
|
|
||||||
let PATsValid = true;
|
|
||||||
try {
|
|
||||||
await retryer(uptimeFetcher, {});
|
|
||||||
} catch (err) {
|
|
||||||
PATsValid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PATsValid) {
|
|
||||||
res.setHeader(
|
|
||||||
"Cache-Control",
|
|
||||||
`max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
res.setHeader("Cache-Control", "no-store");
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case "shields":
|
|
||||||
res.send(shieldsUptimeBadge(PATsValid));
|
|
||||||
break;
|
|
||||||
case "json":
|
|
||||||
res.send({ up: PATsValid });
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
res.send(PATsValid);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// Return fail boolean if something went wrong.
|
|
||||||
logger.error(err);
|
|
||||||
res.setHeader("Cache-Control", "no-store");
|
|
||||||
res.send("Something went wrong: " + err.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
+5
-15
@@ -1,3 +1,4 @@
|
|||||||
|
import * as dotenv from "dotenv";
|
||||||
import { renderTopLanguages } from "../src/cards/top-languages-card.js";
|
import { renderTopLanguages } from "../src/cards/top-languages-card.js";
|
||||||
import { blacklist } from "../src/common/blacklist.js";
|
import { blacklist } from "../src/common/blacklist.js";
|
||||||
import {
|
import {
|
||||||
@@ -10,6 +11,8 @@ import {
|
|||||||
import { fetchTopLanguages } from "../src/fetchers/top-languages-fetcher.js";
|
import { fetchTopLanguages } from "../src/fetchers/top-languages-fetcher.js";
|
||||||
import { isLocaleAvailable } from "../src/translations.js";
|
import { isLocaleAvailable } from "../src/translations.js";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const {
|
const {
|
||||||
username,
|
username,
|
||||||
@@ -25,14 +28,10 @@ export default async (req, res) => {
|
|||||||
layout,
|
layout,
|
||||||
langs_count,
|
langs_count,
|
||||||
exclude_repo,
|
exclude_repo,
|
||||||
size_weight,
|
|
||||||
count_weight,
|
|
||||||
custom_title,
|
custom_title,
|
||||||
locale,
|
locale,
|
||||||
border_radius,
|
border_radius,
|
||||||
border_color,
|
border_color,
|
||||||
disable_animations,
|
|
||||||
hide_progress,
|
|
||||||
} = req.query;
|
} = req.query;
|
||||||
res.setHeader("Content-Type", "image/svg+xml");
|
res.setHeader("Content-Type", "image/svg+xml");
|
||||||
|
|
||||||
@@ -48,8 +47,6 @@ export default async (req, res) => {
|
|||||||
const topLangs = await fetchTopLanguages(
|
const topLangs = await fetchTopLanguages(
|
||||||
username,
|
username,
|
||||||
parseArray(exclude_repo),
|
parseArray(exclude_repo),
|
||||||
size_weight,
|
|
||||||
count_weight,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const cacheSeconds = clampValue(
|
const cacheSeconds = clampValue(
|
||||||
@@ -58,12 +55,7 @@ export default async (req, res) => {
|
|||||||
CONSTANTS.ONE_DAY,
|
CONSTANTS.ONE_DAY,
|
||||||
);
|
);
|
||||||
|
|
||||||
res.setHeader(
|
res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
|
||||||
"Cache-Control",
|
|
||||||
`max-age=${
|
|
||||||
cacheSeconds / 2
|
|
||||||
}, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.send(
|
return res.send(
|
||||||
renderTopLanguages(topLangs, {
|
renderTopLanguages(topLangs, {
|
||||||
@@ -81,12 +73,10 @@ export default async (req, res) => {
|
|||||||
border_radius,
|
border_radius,
|
||||||
border_color,
|
border_color,
|
||||||
locale: locale ? locale.toLowerCase() : null,
|
locale: locale ? locale.toLowerCase() : null,
|
||||||
disable_animations: parseBoolean(disable_animations),
|
|
||||||
hide_progress: parseBoolean(hide_progress),
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.setHeader("Cache-Control", `no-cache, no-store, must-revalidate`); // Don't cache error responses.
|
res.setHeader("Cache-Control", `no-store`); // Don't cache error responses.
|
||||||
return res.send(renderError(err.message, err.secondaryMessage));
|
return res.send(renderError(err.message, err.secondaryMessage));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+5
-7
@@ -1,3 +1,4 @@
|
|||||||
|
import * as dotenv from "dotenv";
|
||||||
import { renderWakatimeCard } from "../src/cards/wakatime-card.js";
|
import { renderWakatimeCard } from "../src/cards/wakatime-card.js";
|
||||||
import {
|
import {
|
||||||
clampValue,
|
clampValue,
|
||||||
@@ -9,6 +10,8 @@ import {
|
|||||||
import { fetchWakatimeStats } from "../src/fetchers/wakatime-fetcher.js";
|
import { fetchWakatimeStats } from "../src/fetchers/wakatime-fetcher.js";
|
||||||
import { isLocaleAvailable } from "../src/translations.js";
|
import { isLocaleAvailable } from "../src/translations.js";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
const {
|
const {
|
||||||
username,
|
username,
|
||||||
@@ -52,12 +55,7 @@ export default async (req, res) => {
|
|||||||
cacheSeconds = CONSTANTS.FOUR_HOURS;
|
cacheSeconds = CONSTANTS.FOUR_HOURS;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.setHeader(
|
res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
|
||||||
"Cache-Control",
|
|
||||||
`max-age=${
|
|
||||||
cacheSeconds / 2
|
|
||||||
}, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.send(
|
return res.send(
|
||||||
renderWakatimeCard(stats, {
|
renderWakatimeCard(stats, {
|
||||||
@@ -80,7 +78,7 @@ export default async (req, res) => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.setHeader("Cache-Control", `no-cache, no-store, must-revalidate`); // Don't cache error responses.
|
res.setHeader("Cache-Control", `no-store`); // Don't cache error responses.
|
||||||
return res.send(renderError(err.message, err.secondaryMessage));
|
return res.send(renderError(err.message, err.secondaryMessage));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-4
@@ -53,8 +53,6 @@
|
|||||||
<a href="/docs/readme_nl.md">Nederlands</a>
|
<a href="/docs/readme_nl.md">Nederlands</a>
|
||||||
.
|
.
|
||||||
<a href="/docs/readme_np.md">नेपाली</a>
|
<a href="/docs/readme_np.md">नेपाली</a>
|
||||||
.
|
|
||||||
<a href="/docs/readme_tr.md">Türkçe</a>
|
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">喜欢这个项目?请考虑<a href="https://www.paypal.me/anuraghazra">捐赠</a>来帮助它完善!
|
<p align="center">喜欢这个项目?请考虑<a href="https://www.paypal.me/anuraghazra">捐赠</a>来帮助它完善!
|
||||||
@@ -140,7 +138,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr
|
|||||||
- `bg_color` - 卡片背景颜色 _(十六进制色码)_ **或者** 以 _angle,start,end_ 的形式渐变
|
- `bg_color` - 卡片背景颜色 _(十六进制色码)_ **或者** 以 _angle,start,end_ 的形式渐变
|
||||||
- `hide_border` - 隐藏卡的边框 _(布尔值)_
|
- `hide_border` - 隐藏卡的边框 _(布尔值)_
|
||||||
- `theme` - 主题名称,从[所有可用主题](../themes/README.md)中选择
|
- `theme` - 主题名称,从[所有可用主题](../themes/README.md)中选择
|
||||||
- `cache_seconds` - 手动设置缓存头 _(最小值: 14400,最大值: 86400)_
|
- `cache_seconds` - 手动设置缓存头 _(最小值: 1800,最大值: 86400)_
|
||||||
- `locale` - 在卡片中设置语言 _(例如 cn, de, es, 等等)_
|
- `locale` - 在卡片中设置语言 _(例如 cn, de, es, 等等)_
|
||||||
|
|
||||||
##### bg_color 渐变
|
##### bg_color 渐变
|
||||||
@@ -171,7 +169,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr
|
|||||||
|
|
||||||
- `hide` - 从卡片中隐藏指定语言 _(Comma seperated values)_
|
- `hide` - 从卡片中隐藏指定语言 _(Comma seperated values)_
|
||||||
- `hide_title` - _(boolean)_
|
- `hide_title` - _(boolean)_
|
||||||
- `layout` - 提供四種佈局 `normal` & `compact` & `donut` & `pie` 间切换
|
- `layout` - 在两个可用布局 `default` & `compact` 间切换
|
||||||
- `card_width` - 手动设置卡片的宽度 _(number)_
|
- `card_width` - 手动设置卡片的宽度 _(number)_
|
||||||
|
|
||||||
> :warning: **重要:**
|
> :warning: **重要:**
|
||||||
|
|||||||
+6
-8
@@ -54,8 +54,6 @@
|
|||||||
<a href="/docs/readme_nl.md">Nederlands</a>
|
<a href="/docs/readme_nl.md">Nederlands</a>
|
||||||
.
|
.
|
||||||
<a href="/docs/readme_np.md">नेपाली</a>
|
<a href="/docs/readme_np.md">नेपाली</a>
|
||||||
.
|
|
||||||
<a href="/docs/readme_tr.md">Türkçe</a>
|
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">Du magst das Projekt? Wie wäre es mit einer kleinen <a href="https://www.paypal.me/anuraghazra">Spende</a> um es weiterhin am Leben zu erhalten?
|
<p align="center">Du magst das Projekt? Wie wäre es mit einer kleinen <a href="https://www.paypal.me/anuraghazra">Spende</a> um es weiterhin am Leben zu erhalten?
|
||||||
@@ -130,7 +128,7 @@ Du kannst das Erscheinungsbild deiner `Stats Card` oder `Repo Card`, mithilfe vo
|
|||||||
- `bg_color` - Hintergrundfarbe _(hex color)_ **oder** ein Farbverlauf in der Form von _winkel,start,ende_
|
- `bg_color` - Hintergrundfarbe _(hex color)_ **oder** ein Farbverlauf in der Form von _winkel,start,ende_
|
||||||
- `hide_border` - Blendet den Rand der Karte aus _(Boolean)_
|
- `hide_border` - Blendet den Rand der Karte aus _(Boolean)_
|
||||||
- `theme` - Name des Erscheinungsbildes/Themes [alle verfügbaren Themes](../themes/README.md)
|
- `theme` - Name des Erscheinungsbildes/Themes [alle verfügbaren Themes](../themes/README.md)
|
||||||
- `cache_seconds` - manuelles festlegen der Cachezeiten _(min: 14400, max: 86400)_
|
- `cache_seconds` - manuelles festlegen der Cachezeiten _(min: 1800, max: 86400)_
|
||||||
- `locale` - Stellen Sie die Sprache auf der Karte ein _(z.B. cn, de, es, etc.)_
|
- `locale` - Stellen Sie die Sprache auf der Karte ein _(z.B. cn, de, es, etc.)_
|
||||||
|
|
||||||
##### Farbverlauf in bg_color
|
##### Farbverlauf in bg_color
|
||||||
@@ -161,7 +159,7 @@ Du kannst mehrere, mit Kommas separierte, Werte in der bg_color Option angeben,
|
|||||||
|
|
||||||
- `hide` - Verbirgt die angegebenen Sprachen von der Karte _(Komma separierte Werte)_
|
- `hide` - Verbirgt die angegebenen Sprachen von der Karte _(Komma separierte Werte)_
|
||||||
- `hide_title` - _(Boolean)_
|
- `hide_title` - _(Boolean)_
|
||||||
- `layout` - Wechseln Sie zwischen den vier verfügbaren Layouts `normal` & `compact` & `donut` & `pie`
|
- `layout` - Wechsel zwischen den zwei verfügbaren Layouts `default` & `compact`
|
||||||
- `card_width` - Lege die Breite der Karte manuell fest _(Zahl)_
|
- `card_width` - Lege die Breite der Karte manuell fest _(Zahl)_
|
||||||
|
|
||||||
> :warning: **Wichtig:**
|
> :warning: **Wichtig:**
|
||||||
@@ -253,18 +251,18 @@ Du kannst die `&layout=compact` Option nutzen, um das Kartendesign zu ändern.
|
|||||||
Ändere `?username=` in den eigenen [Wakatime](https://wakatime.com)-Benutzernamen.
|
Ändere `?username=` in den eigenen [Wakatime](https://wakatime.com)-Benutzernamen.
|
||||||
|
|
||||||
```md
|
```md
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Beispiel
|
### Beispiel
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
- Kompaktes Layout
|
- Kompaktes Layout
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+7
-7
@@ -142,7 +142,7 @@ Puedes personalizar el aspecto de tu `Tarjeta de Estadísticas` o `Tarjeta de Re
|
|||||||
- `bg_color` - Color de fondo _(hex color)_
|
- `bg_color` - Color de fondo _(hex color)_
|
||||||
- `hide_border` - Oculta el borde de la tarjeta _(booleano)_
|
- `hide_border` - Oculta el borde de la tarjeta _(booleano)_
|
||||||
- `theme` - Nombre del tema, elige uno de [todos los temas disponible ](../themes/README.md)
|
- `theme` - Nombre del tema, elige uno de [todos los temas disponible ](../themes/README.md)
|
||||||
- `cache_seconds` - Cache _(min: 14400, max: 86400)_
|
- `cache_seconds` - Cache _(min: 1800, max: 86400)_
|
||||||
- `locale` - configurar el idioma en la tarjeta _(p.ej. cn, de, es, etc.)_
|
- `locale` - configurar el idioma en la tarjeta _(p.ej. cn, de, es, etc.)_
|
||||||
|
|
||||||
##### Gradiente en `bg_color`
|
##### Gradiente en `bg_color`
|
||||||
@@ -175,7 +175,7 @@ Puedes pasar mútliples valores separados por coma en la opción `bg_color` para
|
|||||||
|
|
||||||
- `hide` - Oculta de la tarjeta los lenguajes especificados _(valores separados por comas)_
|
- `hide` - Oculta de la tarjeta los lenguajes especificados _(valores separados por comas)_
|
||||||
- `hide_title` - _(booleano)_
|
- `hide_title` - _(booleano)_
|
||||||
- `layout` - Cambiar entre los cuatro diseños disponibles `normal` & `compact` & `donut` & `pie`
|
- `layout` - Cambia entre los dos diseños disponibles `default` & `compact`
|
||||||
- `card_width` - Establece el ancho de la tarjeta manualmente _(número)_
|
- `card_width` - Establece el ancho de la tarjeta manualmente _(número)_
|
||||||
- `langs_count` - Muestra más lenguajes en la tarjeta, entre 1-10, por defecto 5 _(número)_
|
- `langs_count` - Muestra más lenguajes en la tarjeta, entre 1-10, por defecto 5 _(número)_
|
||||||
- `exclude_repo` - Excluye los repositorios especificados _(valores separados por comas)_
|
- `exclude_repo` - Excluye los repositorios especificados _(valores separados por comas)_
|
||||||
@@ -282,18 +282,18 @@ Puedes usar la opción `& layout = compact` para cambiar el diseño de la tarjet
|
|||||||
cambia el valor del parámetro `?username=` a tu username en [Wakatime](https://wakatime.com).
|
cambia el valor del parámetro `?username=` a tu username en [Wakatime](https://wakatime.com).
|
||||||
|
|
||||||
```md
|
```md
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ejemplo
|
### Ejemplo
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
- Diseño compacto
|
- Diseño compacto
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -343,7 +343,7 @@ Escoja cualquiera de los [temas por defecto](#themes)
|
|||||||
|
|
||||||
- Tarjeta de Wakatime
|
- Tarjeta de Wakatime
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+15
-5
@@ -53,8 +53,6 @@
|
|||||||
<a href="/docs/readme_nl.md">Nederlands</a>
|
<a href="/docs/readme_nl.md">Nederlands</a>
|
||||||
.
|
.
|
||||||
<a href="/docs/readme_np.md">नेपाली</a>
|
<a href="/docs/readme_np.md">नेपाली</a>
|
||||||
.
|
|
||||||
<a href="/docs/readme_tr.md">Türkçe</a>
|
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">Vous aimez ce projet? Pensez <a href="https://www.paypal.me/anuraghazra">à faire un don</a> pour l'améliorer!
|
<p align="center">Vous aimez ce projet? Pensez <a href="https://www.paypal.me/anuraghazra">à faire un don</a> pour l'améliorer!
|
||||||
@@ -90,6 +88,18 @@ Pour masquer des statistiques spécifiques, vous pouvez passer un paramètre de
|
|||||||

|

|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Ajouter le compte des contributions privées au compte des commits totaux
|
||||||
|
|
||||||
|
Vous pouvez ajouter le compte de toutes vos contributions privées au compte total des engagements en utilisant le paramètre de requête `?count_private=true`.
|
||||||
|
|
||||||
|
_Note: Si vous déployez vous-même ce projet, les contributions privées seront comptées par défaut ; sinon, vous devez choisir de partager les comptes de vos contributions privées._
|
||||||
|
|
||||||
|
> Options: `&count_private=true`
|
||||||
|
|
||||||
|
```md
|
||||||
|

|
||||||
|
```
|
||||||
|
|
||||||
### Afficher les icônes
|
### Afficher les icônes
|
||||||
|
|
||||||
Pour activer les icônes, vous pouvez passer `show_icons=true` dans le paramètre de requête, comme ceci :
|
Pour activer les icônes, vous pouvez passer `show_icons=true` dans le paramètre de requête, comme ceci :
|
||||||
@@ -128,7 +138,7 @@ Vous pouvez personnaliser l'apparence de votre `Carte des stats` ou `Carte de d
|
|||||||
- `bg_color` - Couleur du fond de la carte _(hex color)_ **ou** un gradiant de la forme _angle,start,end_
|
- `bg_color` - Couleur du fond de la carte _(hex color)_ **ou** un gradiant de la forme _angle,start,end_
|
||||||
- `hide_border` - Cache la bordure de la carte _(booléen)_
|
- `hide_border` - Cache la bordure de la carte _(booléen)_
|
||||||
- `theme` - Nom du thème, parmis [tous les thèmes disponibles](../themes/README.md)
|
- `theme` - Nom du thème, parmis [tous les thèmes disponibles](../themes/README.md)
|
||||||
- `cache_seconds` - Paramétrer le cache manuellement _(min: 14400, max: 86400)_
|
- `cache_seconds` - Paramétrer le cache manuellement _(min: 1800, max: 86400)_
|
||||||
- `locale` - définir la langue de la carte _(par exemple. cn, de, es, etc.)_
|
- `locale` - définir la langue de la carte _(par exemple. cn, de, es, etc.)_
|
||||||
|
|
||||||
##### Gradient in bg_color
|
##### Gradient in bg_color
|
||||||
@@ -148,7 +158,7 @@ Vous pouvez fournir plusieurs valeurs (suivie d'une virgule) dans l'option bg_co
|
|||||||
- `hide_rank` - Masquer le rang _(boolean)_
|
- `hide_rank` - Masquer le rang _(boolean)_
|
||||||
- `show_icons` - Afficher les icônes _(boolean)_
|
- `show_icons` - Afficher les icônes _(boolean)_
|
||||||
- `include_all_commits` - Compter le total de commits au lieu de ne compter que les commits de l'année en cours _(boolean)_
|
- `include_all_commits` - Compter le total de commits au lieu de ne compter que les commits de l'année en cours _(boolean)_
|
||||||
- `count_private` - Compter les contributions privées _(boolean)_
|
- `count_private` - Compter les commits privés _(boolean)_
|
||||||
- `line_height` - Fixer la hauteur de la ligne entre les textes _(number)_
|
- `line_height` - Fixer la hauteur de la ligne entre les textes _(number)_
|
||||||
|
|
||||||
#### Repo Card Exclusive Options:
|
#### Repo Card Exclusive Options:
|
||||||
@@ -159,7 +169,7 @@ Vous pouvez fournir plusieurs valeurs (suivie d'une virgule) dans l'option bg_co
|
|||||||
|
|
||||||
- `hide` - Masquer les langages spécifiés sur la carte _(Comma seperated values)_
|
- `hide` - Masquer les langages spécifiés sur la carte _(Comma seperated values)_
|
||||||
- `hide_title` - Masquer le titre _(boolean)_
|
- `hide_title` - Masquer le titre _(boolean)_
|
||||||
- `layout` - Alterner entre 4 mise en page `normal` & `compact` & `donut` & `pie`
|
- `layout` - Alterner entre 2 mise en page `default` & `compact`
|
||||||
- `card_width` - Fixer la largeur de la carte manuellement _(number)_
|
- `card_width` - Fixer la largeur de la carte manuellement _(number)_
|
||||||
|
|
||||||
> :warning: **Important:**
|
> :warning: **Important:**
|
||||||
|
|||||||
+2
-4
@@ -53,8 +53,6 @@
|
|||||||
<a href="/docs/readme_nl.md">Nederlands</a>
|
<a href="/docs/readme_nl.md">Nederlands</a>
|
||||||
.
|
.
|
||||||
<a href="/docs/readme_np.md">नेपाली</a>
|
<a href="/docs/readme_np.md">नेपाली</a>
|
||||||
.
|
|
||||||
<a href="/docs/readme_tr.md">Türkçe</a>
|
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">Se ti piace questo progetto, considera la possibilità di <a href="https://www.paypal.me/anuraghazra">donare</a> per aiutare a renderlo migliore!
|
<p align="center">Se ti piace questo progetto, considera la possibilità di <a href="https://www.paypal.me/anuraghazra">donare</a> per aiutare a renderlo migliore!
|
||||||
@@ -140,7 +138,7 @@ Puoi personalizzare l'aspetto delle tue `Stats Card` o delle `Repo Card` in qual
|
|||||||
- `bg_color` - Colore dello sfondo _(in esadecimale)_ **oppure** un gradiente nella forma _angolo,inizio,fine_
|
- `bg_color` - Colore dello sfondo _(in esadecimale)_ **oppure** un gradiente nella forma _angolo,inizio,fine_
|
||||||
- `hide_border` - Nasconde il bordo della carta _(booleano)_
|
- `hide_border` - Nasconde il bordo della carta _(booleano)_
|
||||||
- `theme` - Nome del tema, dai un'occhiata a [tutti i temi disponibili](../themes/README.md)
|
- `theme` - Nome del tema, dai un'occhiata a [tutti i temi disponibili](../themes/README.md)
|
||||||
- `cache_seconds` - Specifica manualmente il valore di cache, in secondi _(min: 14400, max: 86400)_
|
- `cache_seconds` - Specifica manualmente il valore di cache, in secondi _(min: 1800, max: 86400)_
|
||||||
- `locale` - Impostare la lingua nella scheda _(per esempio. cn, de, es, eccetera.)_
|
- `locale` - Impostare la lingua nella scheda _(per esempio. cn, de, es, eccetera.)_
|
||||||
|
|
||||||
##### Gradiente nello sfondo
|
##### Gradiente nello sfondo
|
||||||
@@ -171,7 +169,7 @@ Puoi fornire valori separati da virgola nel parametro bg_color per creare un gra
|
|||||||
|
|
||||||
- `hide` - Nasconde un linguaggio specifico _(valori separati da virgola)_
|
- `hide` - Nasconde un linguaggio specifico _(valori separati da virgola)_
|
||||||
- `hide_title` - Nasconde il titolo _(booleano)_
|
- `hide_title` - Nasconde il titolo _(booleano)_
|
||||||
- `layout` - Specificare il tipo di layout, `normal` (esteso), `compact` (compatto), `donut` (ciambella) e `pie` (torta)
|
- `layout` - Specifica il tipo di layout, `default` (esteso) o `compact` (compatto)
|
||||||
- `card_width` - Specifica il valore della larghezza _(numero)_
|
- `card_width` - Specifica il valore della larghezza _(numero)_
|
||||||
|
|
||||||
> :warning: **Importante:**
|
> :warning: **Importante:**
|
||||||
|
|||||||
+2
-4
@@ -53,8 +53,6 @@
|
|||||||
<a href="/docs/readme_nl.md">Nederlands</a>
|
<a href="/docs/readme_nl.md">Nederlands</a>
|
||||||
.
|
.
|
||||||
<a href="/docs/readme_np.md">नेपाली</a>
|
<a href="/docs/readme_np.md">नेपाली</a>
|
||||||
.
|
|
||||||
<a href="/docs/readme_tr.md">Türkçe</a>
|
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">このプロジェクトを気に入っていただけましたか?<br>もしよろしければ、プロジェクトのさらなる改善のために<a href="https://www.paypal.me/anuraghazra">寄付</a>を検討して頂けると嬉しいです!</p>
|
<p align="center">このプロジェクトを気に入っていただけましたか?<br>もしよろしければ、プロジェクトのさらなる改善のために<a href="https://www.paypal.me/anuraghazra">寄付</a>を検討して頂けると嬉しいです!</p>
|
||||||
@@ -141,7 +139,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr
|
|||||||
- `bg_color` - 背景の色 _(16 進数カラーコード)_ **または** _angle,start,end_ の形式でグラデーションを指定することも可
|
- `bg_color` - 背景の色 _(16 進数カラーコード)_ **または** _angle,start,end_ の形式でグラデーションを指定することも可
|
||||||
- `hide_border` - カードの境界線を非表示にします _(ブール値)_
|
- `hide_border` - カードの境界線を非表示にします _(ブール値)_
|
||||||
- `theme` - [使用可能なテーマ一覧](../themes/README.md) から選んだテーマ名
|
- `theme` - [使用可能なテーマ一覧](../themes/README.md) から選んだテーマ名
|
||||||
- `cache_seconds` - キャッシュ時間の秒数 _(最小値: 14400, 最大値: 86400)_
|
- `cache_seconds` - キャッシュ時間の秒数 _(最小値: 1800, 最大値: 86400)_
|
||||||
- `locale` - カードに言語を設定する _(例えば cn, de, es, 等)_
|
- `locale` - カードに言語を設定する _(例えば cn, de, es, 等)_
|
||||||
|
|
||||||
##### bg_color の グラデーション指定
|
##### bg_color の グラデーション指定
|
||||||
@@ -174,7 +172,7 @@ bg_color オプションで複数のカンマ区切りの値を指定してグ
|
|||||||
|
|
||||||
- `hide` - 特定の言語を隠す _(カンマ区切りで指定)_
|
- `hide` - 特定の言語を隠す _(カンマ区切りで指定)_
|
||||||
- `hide_title` - _(boolean)_
|
- `hide_title` - _(boolean)_
|
||||||
- `layout` - `normal` & `compact` & `donut` & `pie` のいずれかのレイアウトに切り替える
|
- `layout` - `default` か `compact` のいずれかのレイアウトに切り替える
|
||||||
- `card_width` - カードの横幅 _(number)_
|
- `card_width` - カードの横幅 _(number)_
|
||||||
- `langs_count` - 表示される言語の数 _(1 ~ 10, 初期値 5)_
|
- `langs_count` - 表示される言語の数 _(1 ~ 10, 初期値 5)_
|
||||||
- `exclude_repo` - 指定されたリポジトリを除外する _(カンマ区切りで指定)_
|
- `exclude_repo` - 指定されたリポジトリを除外する _(カンマ区切りで指定)_
|
||||||
|
|||||||
+10
-12
@@ -1,7 +1,7 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img width="100px" src="https://res.cloudinary.com/anuraghazra/image/upload/v1594908242/logo_ccswme.svg" align="center" alt="GitHub Readme Stats" />
|
<img width="100px" src="https://res.cloudinary.com/anuraghazra/image/upload/v1594908242/logo_ccswme.svg" align="center" alt="GitHub Readme Stats" />
|
||||||
<h2 align="center">GitHub Readme Stats</h2>
|
<h2 align="center">GitHub Readme Stats</h2>
|
||||||
<p align="center">동적으로 생성되는 GitHub 사용량 통계를 여러분의 README 에 추가해보세요!</p>
|
<p align="center">동적으로 생성되는 Github 사용량 통계를 여러분의 README 에 추가해보세요!</p>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/anuraghazra/github-readme-stats/actions">
|
<a href="https://github.com/anuraghazra/github-readme-stats/actions">
|
||||||
@@ -53,8 +53,6 @@
|
|||||||
<a href="/docs/readme_nl.md">Nederlands</a>
|
<a href="/docs/readme_nl.md">Nederlands</a>
|
||||||
.
|
.
|
||||||
<a href="/docs/readme_np.md">नेपाली</a>
|
<a href="/docs/readme_np.md">नेपाली</a>
|
||||||
.
|
|
||||||
<a href="/docs/readme_tr.md">Türkçe</a>
|
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">기능들이 마음에 드시나요? 괜찮으시다면, 서비스 개선을 위해 <a href="https://www.paypal.me/anuraghazra">기부</a>를 고려해주세요!
|
<p align="center">기능들이 마음에 드시나요? 괜찮으시다면, 서비스 개선을 위해 <a href="https://www.paypal.me/anuraghazra">기부</a>를 고려해주세요!
|
||||||
@@ -74,7 +72,7 @@
|
|||||||
|
|
||||||
아래 코드를 복사해서 마크다운 파일에 붙여넣으면 끝이에요, 아주 간단해요!
|
아래 코드를 복사해서 마크다운 파일에 붙여넣으면 끝이에요, 아주 간단해요!
|
||||||
|
|
||||||
`?username=` 속성의 값을 GitHub 계정의 사용자 명(닉네임)으로 바꿔주세요.
|
`?username=` 속성의 값을 Github 계정의 사용자 명(닉네임)으로 바꿔주세요.
|
||||||
|
|
||||||
```md
|
```md
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
@@ -151,7 +149,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr
|
|||||||
- `bg_color` - 카드의 배경 색상 _(hex color)_ **혹은** 다음 양식으로 그라데이션 주기 _angle,start,end_
|
- `bg_color` - 카드의 배경 색상 _(hex color)_ **혹은** 다음 양식으로 그라데이션 주기 _angle,start,end_
|
||||||
- `hide_border` - 카드의 테두리 표시 여부 _(boolean)_
|
- `hide_border` - 카드의 테두리 표시 여부 _(boolean)_
|
||||||
- `theme` - 테마의 이름, [사용 가능한 모든 테마](../themes/README.md) 에서 선택
|
- `theme` - 테마의 이름, [사용 가능한 모든 테마](../themes/README.md) 에서 선택
|
||||||
- `cache_seconds` - 수동으로 캐시 헤더 설정 _(min: 14400, max: 86400)_
|
- `cache_seconds` - 수동으로 캐시 헤더 설정 _(min: 1800, max: 86400)_
|
||||||
- `locale` - 카드에 표시할 언어 _(e.g. kr, cn, de, es, etc.)_
|
- `locale` - 카드에 표시할 언어 _(e.g. kr, cn, de, es, etc.)_
|
||||||
|
|
||||||
##### 배경에 그라데이션 주기
|
##### 배경에 그라데이션 주기
|
||||||
@@ -189,7 +187,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr
|
|||||||
|
|
||||||
- `hide` - 카드에서 특정 언어 제외 _(Comma-separated values)_
|
- `hide` - 카드에서 특정 언어 제외 _(Comma-separated values)_
|
||||||
- `hide_title` - 타이틀 제외 _(boolean)_
|
- `hide_title` - 타이틀 제외 _(boolean)_
|
||||||
- `layout` - 4가지 값 사용 가능, `normal` & `compact` & `donut` & `pie` 중 표시 형태 선택
|
- `layout` - 사용 가능한 두 가지 값, `default` & `compact` 중 표시 형태 선택
|
||||||
- `card_width` - 카드 너비 직접 설정 _(number)_
|
- `card_width` - 카드 너비 직접 설정 _(number)_
|
||||||
- `langs_count` - 카드에 표시할 언어의 수 (1-10 사이, 기본 값 : 5) _(number)_
|
- `langs_count` - 카드에 표시할 언어의 수 (1-10 사이, 기본 값 : 5) _(number)_
|
||||||
- `exclude_repo` - 통계에 제외할 저장소 지정 _(Comma-separated values)_
|
- `exclude_repo` - 통계에 제외할 저장소 지정 _(Comma-separated values)_
|
||||||
@@ -237,7 +235,7 @@ GitHub 저장소 여분 핀을 이용하면, 6개 이상의 저장소 핀을 여
|
|||||||
|
|
||||||
# 언어 사용량 통계
|
# 언어 사용량 통계
|
||||||
|
|
||||||
언어 사용량 통계 카드는 GitHub 사용자가 가장 많이 사용한 언어가 표시됩니다.
|
언어 사용량 통계 카드는 Github 사용자가 가장 많이 사용한 언어가 표시됩니다.
|
||||||
|
|
||||||
_참고:
|
_참고:
|
||||||
언어 사용량 통계는 GitHub 에서 가장 많이 사용된 언어의 표기일 뿐입니다.
|
언어 사용량 통계는 GitHub 에서 가장 많이 사용된 언어의 표기일 뿐입니다.
|
||||||
@@ -298,18 +296,18 @@ _참고:
|
|||||||
`?username=` 속성의 값을 [Wakatime](https://wakatime.com) 계정의 사용자 명(닉네임)으로 바꿔주세요.
|
`?username=` 속성의 값을 [Wakatime](https://wakatime.com) 계정의 사용자 명(닉네임)으로 바꿔주세요.
|
||||||
|
|
||||||
```md
|
```md
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 미리보기
|
### 미리보기
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
- 컴팩트한 레이아웃
|
- 컴팩트한 레이아웃
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -359,7 +357,7 @@ _참고:
|
|||||||
|
|
||||||
- Wakatime 카드
|
- Wakatime 카드
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+6
-8
@@ -53,8 +53,6 @@
|
|||||||
<a href="/docs/readme_nl.md">Nederlands</a>
|
<a href="/docs/readme_nl.md">Nederlands</a>
|
||||||
.
|
.
|
||||||
<a href="/docs/readme_np.md">नेपाली</a>
|
<a href="/docs/readme_np.md">नेपाली</a>
|
||||||
.
|
|
||||||
<a href="/docs/readme_tr.md">Türkçe</a>
|
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">Bevalt het project? <a href="https://www.paypal.me/anuraghazra">Doneer</a> om het te verbeteren!
|
<p align="center">Bevalt het project? <a href="https://www.paypal.me/anuraghazra">Doneer</a> om het te verbeteren!
|
||||||
@@ -144,7 +142,7 @@ Je kan het uiterlijk van je `Statistieken kaart` of `Repo kaart` aanpassen hoe j
|
|||||||
- `bg_color` - Achtergrond kleur van de kaart _(hex kleur)_ **of** een verloop van kleuren in het formaat van _graden,start,einde_
|
- `bg_color` - Achtergrond kleur van de kaart _(hex kleur)_ **of** een verloop van kleuren in het formaat van _graden,start,einde_
|
||||||
- `hide_border` - Verbergt de rand van de kaart _(boolean)_
|
- `hide_border` - Verbergt de rand van de kaart _(boolean)_
|
||||||
- `theme` - Naam van het thema, kies uit [alle beschikbare thema\'s](../themes/README.md)
|
- `theme` - Naam van het thema, kies uit [alle beschikbare thema\'s](../themes/README.md)
|
||||||
- `cache_seconds` - Stel de cache header handmatig in _(min: 14400, max: 86400)_
|
- `cache_seconds` - Stel de cache header handmatig in _(min: 1800, max: 86400)_
|
||||||
- `locale` - Stel taal van de kaart in _(e.g. cn, de, es, etc.)_
|
- `locale` - Stel taal van de kaart in _(e.g. cn, de, es, etc.)_
|
||||||
|
|
||||||
##### Kleurenverloop in bg_color (achtergrond kleur):
|
##### Kleurenverloop in bg_color (achtergrond kleur):
|
||||||
@@ -176,7 +174,7 @@ Je kan meerdere komma verdeelde waarden in de bg_color optie geven om een kleure
|
|||||||
|
|
||||||
- `hide` - Verbergt specifieke talen van de kaart _(komma gescheiden waardes)_
|
- `hide` - Verbergt specifieke talen van de kaart _(komma gescheiden waardes)_
|
||||||
- `hide_title` - _(boolean)_
|
- `hide_title` - _(boolean)_
|
||||||
- `layout` - Keuze uit de vier beschikbare indelingen `normal` & `compact` & `donut` & `pie`
|
- `layout` - Keuze voor de twee beschikbare layouts `default` & `compact`
|
||||||
- `card_width` - Stelt de breedte van de kaart handmatig in. _(nummer)_
|
- `card_width` - Stelt de breedte van de kaart handmatig in. _(nummer)_
|
||||||
- `langs_count` - Laat meer talen op de kaart zien, waarde tussen 1-10, staat standaard op to 5 _(nummer)_
|
- `langs_count` - Laat meer talen op de kaart zien, waarde tussen 1-10, staat standaard op to 5 _(nummer)_
|
||||||
- `exclude_repo` - Verbergt specifieke repositories _(komma gescheiden waardes)_
|
- `exclude_repo` - Verbergt specifieke repositories _(komma gescheiden waardes)_
|
||||||
@@ -283,14 +281,14 @@ Je kan de `&layout=compact` optie gebruiken om het kaart ontwerp aan te passen.
|
|||||||
Verander de `?username=` waarde naar je [Wakatime](https://wakatime.com) gebruikersnaam.
|
Verander de `?username=` waarde naar je [Wakatime](https://wakatime.com) gebruikersnaam.
|
||||||
|
|
||||||
```md
|
```md
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Demo
|
### Demo
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -340,7 +338,7 @@ Kies uit de [standaard thema\'s](#themes)
|
|||||||
|
|
||||||
- Wakatime kaart
|
- Wakatime kaart
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+8
-12
@@ -49,12 +49,8 @@
|
|||||||
<a href="/docs/readme_it.md">Italiano</a>
|
<a href="/docs/readme_it.md">Italiano</a>
|
||||||
·
|
·
|
||||||
<a href="/docs/readme_kr.md">한국어</a>
|
<a href="/docs/readme_kr.md">한국어</a>
|
||||||
.
|
|
||||||
<a href="/docs/readme_nl.md">Nederlands</a>
|
|
||||||
·
|
·
|
||||||
<a href="/docs/readme_np.md">नेपाली</a>
|
<a href="/docs/readme_np.md">नेपाली</a>
|
||||||
.
|
|
||||||
<a href="/docs/readme_tr.md">Türkçe</a>
|
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">परियोजना मनपर्यो? तपाईं मद्दत गर्न सक्नुहुन्छ <a href="https://www.paypal.me/anuraghazra">यो परियोजना</a> बढ्न
|
<p align="center">परियोजना मनपर्यो? तपाईं मद्दत गर्न सक्नुहुन्छ <a href="https://www.paypal.me/anuraghazra">यो परियोजना</a> बढ्न
|
||||||
@@ -142,7 +138,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr
|
|||||||
- `bg_color` - Card's background color _(hex color)_ **or** a gradient in the form of _angle,start,end_
|
- `bg_color` - Card's background color _(hex color)_ **or** a gradient in the form of _angle,start,end_
|
||||||
- `hide_border` - Hides the card's border _(boolean)_
|
- `hide_border` - Hides the card's border _(boolean)_
|
||||||
- `theme` - name of the theme, choose from [all available themes](./themes/README.md)
|
- `theme` - name of the theme, choose from [all available themes](./themes/README.md)
|
||||||
- `cache_seconds` - set the cache header manually _(min: 14400, max: 86400)_
|
- `cache_seconds` - set the cache header manually _(min: 1800, max: 86400)_
|
||||||
- `locale` - set the language in the card _(e.g. cn, de, es, etc.)_
|
- `locale` - set the language in the card _(e.g. cn, de, es, etc.)_
|
||||||
|
|
||||||
##### Gradient in bg_color
|
##### Gradient in bg_color
|
||||||
@@ -174,7 +170,7 @@ You can provide multiple comma-separated values in bg_color option to render a g
|
|||||||
|
|
||||||
- `hide` - Hide the languages specified from the card _(Comma-separated values)_
|
- `hide` - Hide the languages specified from the card _(Comma-separated values)_
|
||||||
- `hide_title` - _(boolean)_
|
- `hide_title` - _(boolean)_
|
||||||
- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `pie`. Default: `normal`.
|
- `layout` - Switch between two available layouts `default` & `compact`
|
||||||
- `card_width` - Set the card's width manually _(number)_
|
- `card_width` - Set the card's width manually _(number)_
|
||||||
- `langs_count` - Show more languages on the card, between 1-10, defaults to 5 _(number)_
|
- `langs_count` - Show more languages on the card, between 1-10, defaults to 5 _(number)_
|
||||||
- `exclude_repo` - Exclude specified repositories _(Comma-separated values)_
|
- `exclude_repo` - Exclude specified repositories _(Comma-separated values)_
|
||||||
@@ -223,7 +219,7 @@ Use [show_owner](#customization) variable to include the repo's owner username
|
|||||||
|
|
||||||
टोप भाषाकार्डले github परयोग गर्नेहरुको प्रोग्रम्मिंग भाषाहरु देखाऊने गर्दछ |.
|
टोप भाषाकार्डले github परयोग गर्नेहरुको प्रोग्रम्मिंग भाषाहरु देखाऊने गर्दछ |.
|
||||||
|
|
||||||
_NOTE: टोप भाषाहरुले आफ्नो सिपलाए संकेत गरेको होईन | योचै GitHub Metricबाट धेरै कुन भाषा परयोग भाकोलाए संकेत गरेको हो |
|
_NOTE: टोप भाषाहरुले आफ्नो सिपलाए संकेत गरेको होईन | योचै Github Metricबाट धेरै कुन भाषा परयोग भाकोलाए संकेत गरेको हो |
|
||||||
### प्रयोग
|
### प्रयोग
|
||||||
|
|
||||||
कोदलाए कपी- पेसेत readme मा गर्नु होला र लिंक परिवतन गर्नु होला |
|
कोदलाए कपी- पेसेत readme मा गर्नु होला र लिंक परिवतन गर्नु होला |
|
||||||
@@ -279,14 +275,14 @@ You can use the `&langs_count=` option to increase or decrease the number of lan
|
|||||||
Change the `?username=` value to your [Wakatime](https://wakatime.com) username.
|
Change the `?username=` value to your [Wakatime](https://wakatime.com) username.
|
||||||
|
|
||||||
```md
|
```md
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
### डेमो
|
### डेमो
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -336,7 +332,7 @@ Change the `?username=` value to your [Wakatime](https://wakatime.com) username.
|
|||||||
|
|
||||||
- वक समय कार्ड
|
- वक समय कार्ड
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -393,7 +389,7 @@ NOTE: Since [#58](https://github.com/anuraghazra/github-readme-stats/pull/58) we
|
|||||||
येदि तपाइले यो प्रोजेक्ट चलाउनु बाकोक्ष बने र मलाई अझै प्रसंसा गर्ने हो बने तपाइले थुप्रै तरिका ले गर्नु सक्नु हुने छ :-
|
येदि तपाइले यो प्रोजेक्ट चलाउनु बाकोक्ष बने र मलाई अझै प्रसंसा गर्ने हो बने तपाइले थुप्रै तरिका ले गर्नु सक्नु हुने छ :-
|
||||||
|
|
||||||
- यो प्रोजेक्टमा तपाइले प्रहयोग गर्दा मलाई क्रेडिट दिन सक्नु हुनेक्ष ।
|
- यो प्रोजेक्टमा तपाइले प्रहयोग गर्दा मलाई क्रेडिट दिन सक्नु हुनेक्ष ।
|
||||||
- तपाइले GitHub ReadMe Stats स्तार्रेड गर्न सक्नु हुनेक्ष :rocket:
|
- तपाइले Github ReadMe Stats स्तार्रेड गर्न सक्नु हुनेक्ष :rocket:
|
||||||
- [](https://www.paypal.me/anuraghazra) - तपाइले पेपाल बाट पनि सहयोग (डक्क्षिन) गर्न सक्नु हुनेक्ष | म ~~कोफी ~~ चिया . :tea: किन्न सक्क्षु ।
|
- [](https://www.paypal.me/anuraghazra) - तपाइले पेपाल बाट पनि सहयोग (डक्क्षिन) गर्न सक्नु हुनेक्ष | म ~~कोफी ~~ चिया . :tea: किन्न सक्क्षु ।
|
||||||
|
|
||||||
धन्याबाद! :heart:
|
धन्याबाद! :heart:
|
||||||
|
|||||||
@@ -53,8 +53,6 @@
|
|||||||
<a href="/docs/readme_nl.md">Nederlands</a>
|
<a href="/docs/readme_nl.md">Nederlands</a>
|
||||||
.
|
.
|
||||||
<a href="/docs/readme_np.md">नेपाली</a>
|
<a href="/docs/readme_np.md">नेपाली</a>
|
||||||
.
|
|
||||||
<a href="/docs/readme_tr.md">Türkçe</a>
|
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">Gostou do projeto? Por favor considere <a href="https://www.paypal.me/anuraghazra">fazer uma doação</a> para ajudar a melhorá-lo!
|
<p align="center">Gostou do projeto? Por favor considere <a href="https://www.paypal.me/anuraghazra">fazer uma doação</a> para ajudar a melhorá-lo!
|
||||||
@@ -141,10 +139,10 @@ Personalize a aparência do seu `Stats Card` ou `Repo Card` da maneira que desej
|
|||||||
- `bg_color` - Cor de fundo do cartão _(hex color)_
|
- `bg_color` - Cor de fundo do cartão _(hex color)_
|
||||||
- `hide_border` - Esconde a borda do cartão _(boleano)_
|
- `hide_border` - Esconde a borda do cartão _(boleano)_
|
||||||
- `theme` - Nome do tema, escolha em [todos os temas disponíveis](../themes/README.md)
|
- `theme` - Nome do tema, escolha em [todos os temas disponíveis](../themes/README.md)
|
||||||
- `cache_seconds` - Defina o cabeçalho do cache manualmente _(min: 14400, max: 86400)_
|
- `cache_seconds` - Defina o cabeçalho do cache manualmente _(min: 1800, max: 86400)_
|
||||||
- `locale` - defina o idioma no cartão _(por exemplo. cn, de, es, etc.)_
|
- `locale` - defina o idioma no cartão _(por exemplo. cn, de, es, etc.)_
|
||||||
|
|
||||||
> Nota sobre o cache: Cartões de repositório tem um cache padrão de 30 minutos (1800 segundos), se o número a contagem de forks e contagem de estrelas é menor que 1 mil o padrão é 2 horas (7200 segundos). Note também que o cache é limitado a um mínimo de 30 minutos e um máximo de 24 horas.
|
> Nota sobre o cache: Cartões de repositório tem um cache padrão de 30 minutos (1800 segundos), se o número a contagem de forks e contagem de estrelas é menor que 1 mil o padrão é 2 horas (7200). Note também que o cache é limitado a um mínimo de 30 minutos e um máximo de 24 horas.
|
||||||
|
|
||||||
#### Opções exclusivas do cartão de estatísticas:
|
#### Opções exclusivas do cartão de estatísticas:
|
||||||
|
|
||||||
@@ -164,12 +162,12 @@ Personalize a aparência do seu `Stats Card` ou `Repo Card` da maneira que desej
|
|||||||
|
|
||||||
- `hide` - Oculta linguagens específicas _(Valores separados por vírgulas)_
|
- `hide` - Oculta linguagens específicas _(Valores separados por vírgulas)_
|
||||||
- `hide_title` - Oculta o título _(boolean)_
|
- `hide_title` - Oculta o título _(boolean)_
|
||||||
- `layout` - Alterna entre os quatro layouts disponíveis `normal` & `compact` & `donut` & `pie`
|
- `layout` - Alterna entre os dois layouts disponíveis `default` & `compact`
|
||||||
- `card_width` - Define a largura do cartão manualmente _(number)_
|
- `card_width` - Define a largura do cartão manualmente _(number)_
|
||||||
|
|
||||||
> :warning: **Importante:**
|
> :warning: **Importante:**
|
||||||
> Nomes de linguagens devem ser uma sequência escapada de URI, como específicado em [Codificação por cento](https://pt.wikipedia.org/wiki/Codificação_por_cento)
|
> Nomes de linguagens devem ser uma sequência escapada de URI, como específicado em [Codificação por cento](https://pt.wikipedia.org/wiki/Codificação_por_cento)
|
||||||
> (Ou seja: `c++` deve se tornar `c%2B%2B`, `jupyter notebook` deve se tornar `jupyter%20notebook`, etc.)
|
> (Isso é: `c++` deve se tornar `c%2B%2B`, `jupyter notebook` deve se tornar `jupyter%20notebook`, etc.)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -242,14 +240,14 @@ Utilize a opção `&layout=compact` para mudar o layout do cartão.
|
|||||||
Altere o valor de `?username=` para o seu username do Wakatime.
|
Altere o valor de `?username=` para o seu username do Wakatime.
|
||||||
|
|
||||||
```md
|
```md
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Demonstração
|
### Demonstração
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+15
-15
@@ -73,10 +73,10 @@
|
|||||||
|
|
||||||
Alt kısımdaki kodu Kopyalayın ve yapıştırın. İşte bu kadar. Çok basit!
|
Alt kısımdaki kodu Kopyalayın ve yapıştırın. İşte bu kadar. Çok basit!
|
||||||
|
|
||||||
`?username=` değerini kendi GitHub kullanıcı adınız ile değiştirin.
|
`?username=` değerini kendi Github kullanıcı adınız ile değiştirin.
|
||||||
|
|
||||||
```md
|
```md
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
```
|
```
|
||||||
_Not: Şu sıralamalar mevcut: S+ (en üst 1%), S (en üst 25%), A++ (en üst 45%), A+ (en üst 60%), and B+ (herkes).
|
_Not: Şu sıralamalar mevcut: S+ (en üst 1%), S (en üst 25%), A++ (en üst 45%), A+ (en üst 60%), and B+ (herkes).
|
||||||
Buradaki değerler [cumulative distribution function](https://en.wikipedia.org/wiki/Cumulative_distribution_function) ile hesaplanırken; commitler, katkılar, hatalar, yıldızlar, çekme istekleri, takipçiler ve sahip olunan depolar (repository) göz önünde bulundurulamaktadır.
|
Buradaki değerler [cumulative distribution function](https://en.wikipedia.org/wiki/Cumulative_distribution_function) ile hesaplanırken; commitler, katkılar, hatalar, yıldızlar, çekme istekleri, takipçiler ve sahip olunan depolar (repository) göz önünde bulundurulamaktadır.
|
||||||
@@ -143,7 +143,7 @@ dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontr
|
|||||||
- `bg_color` - Kartın arkaplan rengi _(hex color / hex rengi)_ **ya da** gradient şeklinde _açı,başlangıç,bitiş_
|
- `bg_color` - Kartın arkaplan rengi _(hex color / hex rengi)_ **ya da** gradient şeklinde _açı,başlangıç,bitiş_
|
||||||
- `hide_border` - Kartın çerçevelerini gizler _(boolean)_
|
- `hide_border` - Kartın çerçevelerini gizler _(boolean)_
|
||||||
- `theme` - Temanın rengi [tüm temalar](./themes/README.md)
|
- `theme` - Temanın rengi [tüm temalar](./themes/README.md)
|
||||||
- `cache_seconds` - Manuel olarak cache'i belirleyebilirsiniz _(en az: 14400, en fazla: 86400)_
|
- `cache_seconds` - Manuel olarak cache'i belirleyebilirsiniz _(en az: 1800, en fazla: 86400)_
|
||||||
- `locale` - Karttaki dili seçebilirsiniz _(örneğin; tr, cn, de, es, vb.)_
|
- `locale` - Karttaki dili seçebilirsiniz _(örneğin; tr, cn, de, es, vb.)_
|
||||||
|
|
||||||
##### bg_color'da Gradient
|
##### bg_color'da Gradient
|
||||||
@@ -176,7 +176,7 @@ bg_color içerisinde birden fazla rengi gradient olarak göstermek için virgül
|
|||||||
|
|
||||||
- `hide` - Belirli bir dili listede gizler _(Virgül ile ayırılmış değerlerle)_
|
- `hide` - Belirli bir dili listede gizler _(Virgül ile ayırılmış değerlerle)_
|
||||||
- `hide_title` - _(boolean)_
|
- `hide_title` - _(boolean)_
|
||||||
- `layout` - Dört uygun tasarım / düzen arasında geçiş `normal` & `compact` & `donut` & `pie`
|
- `layout` - Uygun olan iki tasarım / layout arasında değişiklik yapar `default` & `compact`
|
||||||
- `card_width` - Kartın genişliğini manuel olarak belirler _(number)_
|
- `card_width` - Kartın genişliğini manuel olarak belirler _(number)_
|
||||||
- `langs_count` - 1-10 arasında istediğiniz kadar dil gösterebilirsiniz. Varsayılan: 5 _(number)_
|
- `langs_count` - 1-10 arasında istediğiniz kadar dil gösterebilirsiniz. Varsayılan: 5 _(number)_
|
||||||
- `exclude_repo` - Belirli repoları listeden çıkartır _(Virgül ile ayırılmış değerlerle)_
|
- `exclude_repo` - Belirli repoları listeden çıkartır _(Virgül ile ayırılmış değerlerle)_
|
||||||
@@ -199,7 +199,7 @@ bg_color içerisinde birden fazla rengi gradient olarak göstermek için virgül
|
|||||||
|
|
||||||
# GitHub Ekstra Pinler
|
# GitHub Ekstra Pinler
|
||||||
|
|
||||||
GitHub ekstra pinler profilinize 6'dan fazla repoyu / depoyu profilinizde pinleyebilirsiniz.
|
Github ekstra pinler profilinize 6'dan fazla repoyu / depoyu profilinizde pinleyebilirsiniz.
|
||||||
|
|
||||||
Hey! Artık 6 pin ile kısıtlı kalmayacaksınız!
|
Hey! Artık 6 pin ile kısıtlı kalmayacaksınız!
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ Endpoint: `api/pin?username=mustafacagri&repo=github-readme-stats`
|
|||||||
|
|
||||||
En çok kullanılan diller kartı kullanıcının en çok kullandığı dilleri gösterir.
|
En çok kullanılan diller kartı kullanıcının en çok kullandığı dilleri gösterir.
|
||||||
|
|
||||||
_NOTE: En çok kullanılan dillerde yer alan bilgiler sizin yeteneğinizi ve benzeri şeyleri göstermek. Bu, kodlarınızda en çok kullandığınız dilleri gösteren bir GitHub metriğidir. Ayrıca, github-readme-stats'ın yeni özelliğidir.
|
_NOTE: En çok kullanılan dillerde yer alan bilgiler sizin yeteneğinizi ve benzeri şeyleri göstermek. Bu, kodlarınızda en çok kullandığınız dilleri gösteren bir Github metriğidir. Ayrıca, github-readme-stats'ın yeni özelliğidir.
|
||||||
|
|
||||||
### Kullanım
|
### Kullanım
|
||||||
|
|
||||||
@@ -282,18 +282,18 @@ Endpoint: `api/top-langs?username=mustafacagri`
|
|||||||
`?username=` değerini [Wakatime](https://wakatime.com)'daki kullanıcı adınızla değiştirin.
|
`?username=` değerini [Wakatime](https://wakatime.com)'daki kullanıcı adınızla değiştirin.
|
||||||
|
|
||||||
```md
|
```md
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Demo
|
### Demo
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
- Kompakt Düzen
|
- Kompakt Düzen
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -343,7 +343,7 @@ Endpoint: `api/top-langs?username=mustafacagri`
|
|||||||
|
|
||||||
- Wakatime kart
|
- Wakatime kart
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -365,7 +365,7 @@ Genellikle resimleri yan yana düzenleyemezsiniz. Bunu yapmak için şu yaklaş
|
|||||||
|
|
||||||
#### [@codeSTACKr'ın Yayınladığı Video Eğitimine Göz Atın](https://youtu.be/n6d4KHSKqGk?t=107)
|
#### [@codeSTACKr'ın Yayınladığı Video Eğitimine Göz Atın](https://youtu.be/n6d4KHSKqGk?t=107)
|
||||||
|
|
||||||
GitHub API saatte sadece 5.000 isteğe izin verdiği için `https://github-readme-stats.vercel.app/api` adresindeki API'm bu limite muhtemelen takılmış olabilir. Eğer projeyi kendi Vercel sunucunuzda yayınlarsanız, böyle bir sorun yaşamayabilirsiniz. Deploy butonuna tıkla ve deploy başlasın!
|
Github API saatte sadece 5.000 isteğe izin verdiği için `https://github-readme-stats.vercel.app/api` adresindeki API'm bu limite muhtemelen takılmış olabilir. Eğer projeyi kendi Vercel sunucunuzda yayınlarsanız, böyle bir sorun yaşamayabilirsiniz. Deploy butonuna tıkla ve deploy başlasın!
|
||||||
|
|
||||||
|
|
||||||
NOT: [#58](https://github.com/anuraghazra/github-readme-stats/pull/58) geliştirmesi sonrasında anlamadığımız bir şekilde 5.000 istek limitine takılmıyoruz :)
|
NOT: [#58](https://github.com/anuraghazra/github-readme-stats/pull/58) geliştirmesi sonrasında anlamadığımız bir şekilde 5.000 istek limitine takılmıyoruz :)
|
||||||
@@ -378,9 +378,9 @@ NOT: [#58](https://github.com/anuraghazra/github-readme-stats/pull/58) geliştir
|
|||||||
1. [vercel.com](https://vercel.com/) adresine gidin
|
1. [vercel.com](https://vercel.com/) adresine gidin
|
||||||
1. `Log in`'e tıklayın
|
1. `Log in`'e tıklayın
|
||||||

|

|
||||||
1. `Continue with GitHub`'e basarak GitHub ile giriş yapın
|
1. `Continue with GitHub`'e basarak Github ile giriş yapın
|
||||||

|

|
||||||
1. GitHub'a giriş yapın ve eğer çıkarsa tüm repolara izin verin.
|
1. Github'a giriş yapın ve eğer çıkarsa tüm repolara izin verin.
|
||||||
1. Bu repoyu fork'layın
|
1. Bu repoyu fork'layın
|
||||||
1. [Vercel dashboard](https://vercel.com/dashboard)'unuza geri dönün.
|
1. [Vercel dashboard](https://vercel.com/dashboard)'unuza geri dönün.
|
||||||
1. `Import Project`'i seçin.
|
1. `Import Project`'i seçin.
|
||||||
@@ -409,7 +409,7 @@ Teşekkürler! :heart:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[](https://vercel.com?utm_source=github_readme_stats_team&utm_campaign=oss)
|
[](https://vercel.com?utm_source=github_readme_stats_team&utm_campaign=oss)
|
||||||
|
|
||||||
|
|
||||||
Katkılara açığız! <3
|
Katkılara açığız! <3
|
||||||
|
|||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
import statsCard from "./api/index.js";
|
|
||||||
import repoCard from "./api/pin.js";
|
|
||||||
import langCard from "./api/top-langs.js";
|
|
||||||
import wakatimeCard from "./api/wakatime.js";
|
|
||||||
import express from "express";
|
|
||||||
import dotenv from "dotenv";
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
const app = express();
|
|
||||||
app.listen(process.env.port || 9000);
|
|
||||||
|
|
||||||
app.get("/", statsCard);
|
|
||||||
app.get("/pin", repoCard);
|
|
||||||
app.get("/top-langs", langCard);
|
|
||||||
app.get("/wakatime", wakatimeCard);
|
|
||||||
Generated
+116
-43
@@ -22,7 +22,7 @@
|
|||||||
"@testing-library/dom": "^8.17.1",
|
"@testing-library/dom": "^8.17.1",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@uppercod/css-to-object": "^1.1.1",
|
"@uppercod/css-to-object": "^1.1.1",
|
||||||
"axios-mock-adapter": "^1.21.2",
|
"axios-mock-adapter": "^1.18.1",
|
||||||
"color-contrast-checker": "^2.1.0",
|
"color-contrast-checker": "^2.1.0",
|
||||||
"hjson": "^3.2.2",
|
"hjson": "^3.2.2",
|
||||||
"husky": "^8.0.0",
|
"husky": "^8.0.0",
|
||||||
@@ -1495,19 +1495,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/acorn-globals": {
|
"node_modules/acorn-globals": {
|
||||||
"version": "7.0.1",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz",
|
||||||
"integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
|
"integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "^8.1.0",
|
"acorn": "^7.1.1",
|
||||||
"acorn-walk": "^8.0.2"
|
"acorn-walk": "^7.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/acorn-globals/node_modules/acorn": {
|
||||||
|
"version": "7.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||||
|
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"acorn": "bin/acorn"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/acorn-walk": {
|
"node_modules/acorn-walk": {
|
||||||
"version": "8.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
|
||||||
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
|
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
@@ -1766,6 +1778,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/browser-process-hrtime": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/browserslist": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.21.4",
|
"version": "4.21.4",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
|
||||||
@@ -2491,6 +2509,20 @@
|
|||||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
@@ -3833,18 +3865,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jsdom": {
|
"node_modules/jsdom": {
|
||||||
"version": "20.0.1",
|
"version": "20.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz",
|
||||||
"integrity": "sha512-pksjj7Rqoa+wdpkKcLzQRHhJCEE42qQhl/xLMUKHgoSejaKOdaXEAnqs6uDNwMl/fciHTzKeR8Wm8cw7N+g98A==",
|
"integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"abab": "^2.0.6",
|
"abab": "^2.0.6",
|
||||||
"acorn": "^8.8.0",
|
"acorn": "^8.7.1",
|
||||||
"acorn-globals": "^7.0.0",
|
"acorn-globals": "^6.0.0",
|
||||||
"cssom": "^0.5.0",
|
"cssom": "^0.5.0",
|
||||||
"cssstyle": "^2.3.0",
|
"cssstyle": "^2.3.0",
|
||||||
"data-urls": "^3.0.2",
|
"data-urls": "^3.0.2",
|
||||||
"decimal.js": "^10.4.1",
|
"decimal.js": "^10.3.1",
|
||||||
"domexception": "^4.0.0",
|
"domexception": "^4.0.0",
|
||||||
"escodegen": "^2.0.0",
|
"escodegen": "^2.0.0",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
@@ -3852,17 +3884,18 @@
|
|||||||
"http-proxy-agent": "^5.0.0",
|
"http-proxy-agent": "^5.0.0",
|
||||||
"https-proxy-agent": "^5.0.1",
|
"https-proxy-agent": "^5.0.1",
|
||||||
"is-potential-custom-element-name": "^1.0.1",
|
"is-potential-custom-element-name": "^1.0.1",
|
||||||
"nwsapi": "^2.2.2",
|
"nwsapi": "^2.2.0",
|
||||||
"parse5": "^7.1.1",
|
"parse5": "^7.0.0",
|
||||||
"saxes": "^6.0.0",
|
"saxes": "^6.0.0",
|
||||||
"symbol-tree": "^3.2.4",
|
"symbol-tree": "^3.2.4",
|
||||||
"tough-cookie": "^4.1.2",
|
"tough-cookie": "^4.0.0",
|
||||||
|
"w3c-hr-time": "^1.0.2",
|
||||||
"w3c-xmlserializer": "^3.0.0",
|
"w3c-xmlserializer": "^3.0.0",
|
||||||
"webidl-conversions": "^7.0.0",
|
"webidl-conversions": "^7.0.0",
|
||||||
"whatwg-encoding": "^2.0.0",
|
"whatwg-encoding": "^2.0.0",
|
||||||
"whatwg-mimetype": "^3.0.0",
|
"whatwg-mimetype": "^3.0.0",
|
||||||
"whatwg-url": "^11.0.0",
|
"whatwg-url": "^11.0.0",
|
||||||
"ws": "^8.9.0",
|
"ws": "^8.8.0",
|
||||||
"xml-name-validator": "^4.0.0"
|
"xml-name-validator": "^4.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -5364,6 +5397,15 @@
|
|||||||
"node": ">=10.12.0"
|
"node": ">=10.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/w3c-hr-time": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"browser-process-hrtime": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/w3c-xmlserializer": {
|
"node_modules/w3c-xmlserializer": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz",
|
||||||
@@ -5539,9 +5581,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "2.2.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz",
|
||||||
"integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==",
|
"integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
@@ -6788,19 +6830,27 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"acorn-globals": {
|
"acorn-globals": {
|
||||||
"version": "7.0.1",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz",
|
||||||
"integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
|
"integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"acorn": "^8.1.0",
|
"acorn": "^7.1.1",
|
||||||
"acorn-walk": "^8.0.2"
|
"acorn-walk": "^7.1.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"acorn": {
|
||||||
|
"version": "7.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||||
|
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"acorn-walk": {
|
"acorn-walk": {
|
||||||
"version": "8.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
|
||||||
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
|
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"agent-base": {
|
"agent-base": {
|
||||||
@@ -6999,6 +7049,12 @@
|
|||||||
"fill-range": "^7.0.1"
|
"fill-range": "^7.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"browser-process-hrtime": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"version": "4.21.4",
|
"version": "4.21.4",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
|
||||||
@@ -7534,6 +7590,13 @@
|
|||||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"function-bind": {
|
"function-bind": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
@@ -8546,18 +8609,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jsdom": {
|
"jsdom": {
|
||||||
"version": "20.0.1",
|
"version": "20.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz",
|
||||||
"integrity": "sha512-pksjj7Rqoa+wdpkKcLzQRHhJCEE42qQhl/xLMUKHgoSejaKOdaXEAnqs6uDNwMl/fciHTzKeR8Wm8cw7N+g98A==",
|
"integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"abab": "^2.0.6",
|
"abab": "^2.0.6",
|
||||||
"acorn": "^8.8.0",
|
"acorn": "^8.7.1",
|
||||||
"acorn-globals": "^7.0.0",
|
"acorn-globals": "^6.0.0",
|
||||||
"cssom": "^0.5.0",
|
"cssom": "^0.5.0",
|
||||||
"cssstyle": "^2.3.0",
|
"cssstyle": "^2.3.0",
|
||||||
"data-urls": "^3.0.2",
|
"data-urls": "^3.0.2",
|
||||||
"decimal.js": "^10.4.1",
|
"decimal.js": "^10.3.1",
|
||||||
"domexception": "^4.0.0",
|
"domexception": "^4.0.0",
|
||||||
"escodegen": "^2.0.0",
|
"escodegen": "^2.0.0",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
@@ -8565,17 +8628,18 @@
|
|||||||
"http-proxy-agent": "^5.0.0",
|
"http-proxy-agent": "^5.0.0",
|
||||||
"https-proxy-agent": "^5.0.1",
|
"https-proxy-agent": "^5.0.1",
|
||||||
"is-potential-custom-element-name": "^1.0.1",
|
"is-potential-custom-element-name": "^1.0.1",
|
||||||
"nwsapi": "^2.2.2",
|
"nwsapi": "^2.2.0",
|
||||||
"parse5": "^7.1.1",
|
"parse5": "^7.0.0",
|
||||||
"saxes": "^6.0.0",
|
"saxes": "^6.0.0",
|
||||||
"symbol-tree": "^3.2.4",
|
"symbol-tree": "^3.2.4",
|
||||||
"tough-cookie": "^4.1.2",
|
"tough-cookie": "^4.0.0",
|
||||||
|
"w3c-hr-time": "^1.0.2",
|
||||||
"w3c-xmlserializer": "^3.0.0",
|
"w3c-xmlserializer": "^3.0.0",
|
||||||
"webidl-conversions": "^7.0.0",
|
"webidl-conversions": "^7.0.0",
|
||||||
"whatwg-encoding": "^2.0.0",
|
"whatwg-encoding": "^2.0.0",
|
||||||
"whatwg-mimetype": "^3.0.0",
|
"whatwg-mimetype": "^3.0.0",
|
||||||
"whatwg-url": "^11.0.0",
|
"whatwg-url": "^11.0.0",
|
||||||
"ws": "^8.9.0",
|
"ws": "^8.8.0",
|
||||||
"xml-name-validator": "^4.0.0"
|
"xml-name-validator": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -9660,6 +9724,15 @@
|
|||||||
"convert-source-map": "^1.6.0"
|
"convert-source-map": "^1.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"w3c-hr-time": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"browser-process-hrtime": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"w3c-xmlserializer": {
|
"w3c-xmlserializer": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz",
|
||||||
@@ -9782,9 +9855,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"yaml": {
|
"yaml": {
|
||||||
"version": "2.2.2",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz",
|
||||||
"integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==",
|
"integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"yargs": {
|
"yargs": {
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@
|
|||||||
"@testing-library/dom": "^8.17.1",
|
"@testing-library/dom": "^8.17.1",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@uppercod/css-to-object": "^1.1.1",
|
"@uppercod/css-to-object": "^1.1.1",
|
||||||
"axios-mock-adapter": "^1.21.2",
|
"axios-mock-adapter": "^1.18.1",
|
||||||
"color-contrast-checker": "^2.1.0",
|
"color-contrast-checker": "^2.1.0",
|
||||||
"hjson": "^3.2.2",
|
"hjson": "^3.2.2",
|
||||||
"husky": "^8.0.0",
|
"husky": "^8.0.0",
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
<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!</p>
|
||||||
|
|
||||||
<a href="https://indiafightscorona.giveindia.org">
|
<a href="https://indiafightscorona.giveindia.org">
|
||||||
<img src="https://cfstatic.give.do/logo.png" alt="Give india logo" width="200" />
|
<img src="https://indiaspora.org/wp-content/uploads/2021/04/give-India-logo.png" alt="Give india logo" width="200" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
Are you considering supporting the project by donating? Please DO NOT!!
|
Are you considering supporting the project by donating? Please DO NOT!!
|
||||||
@@ -85,17 +85,14 @@ Visit <https://indiafightscorona.giveindia.org> and make a small donation to hel
|
|||||||
- [Top Languages Card](#top-languages-card)
|
- [Top Languages Card](#top-languages-card)
|
||||||
- [Wakatime Week Stats](#wakatime-week-stats)
|
- [Wakatime Week Stats](#wakatime-week-stats)
|
||||||
- [Themes](#themes)
|
- [Themes](#themes)
|
||||||
- [Responsive Card Theme](#responsive-card-theme)
|
|
||||||
- [Customization](#customization)
|
- [Customization](#customization)
|
||||||
- [Common Options](#common-options)
|
- [Common Options](#common-options)
|
||||||
- [Stats Card Exclusive Options](#stats-card-exclusive-options)
|
- [Stats Card Exclusive Options](#stats-card-exclusive-options)
|
||||||
- [Repo Card Exclusive Options](#repo-card-exclusive-options)
|
- [Repo Card Exclusive Options](#repo-card-exclusive-options)
|
||||||
- [Language Card Exclusive Options](#language-card-exclusive-options)
|
- [Language Card Exclusive Options](#language-card-exclusive-options)
|
||||||
- [Wakatime Card Exclusive Option](#wakatime-card-exclusive-options)
|
- [Wakatime Card Exclusive Option](#wakatime-card-exclusive-options)
|
||||||
- [Deploy Yourself](#deploy-on-your-own)
|
- [Deploy Yourself](#deploy-on-your-own-vercel-instance)
|
||||||
- [On Vercel](#on-vercel)
|
- [Keep your fork up to date](#keep-your-fork-up-to-date)
|
||||||
- [On other platforms](#on-other-platforms)
|
|
||||||
- [Keep your fork up to date](#keep-your-fork-up-to-date)
|
|
||||||
|
|
||||||
# GitHub Stats Card
|
# GitHub Stats Card
|
||||||
|
|
||||||
@@ -120,9 +117,22 @@ You can pass a query parameter `&hide=` to hide any specific stats with comma-se
|
|||||||

|

|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Adding private contributions count to total commits count
|
||||||
|
|
||||||
|
You can add the count of all your private contributions to the total commits count by using the query parameter `&count_private=true`.
|
||||||
|
|
||||||
|
> **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`
|
||||||
|
|
||||||
|
```md
|
||||||
|

|
||||||
|
```
|
||||||
|
|
||||||
### Showing icons
|
### Showing icons
|
||||||
|
|
||||||
To enable icons, you can pass `&show_icons=true` in the query param, like so:
|
To enable icons, you can pass `show_icons=true` in the query param, like so:
|
||||||
|
|
||||||
```md
|
```md
|
||||||

|

|
||||||
@@ -140,101 +150,12 @@ Use `&theme=THEME_NAME` parameter like so :
|
|||||||
|
|
||||||
#### All inbuilt themes
|
#### All inbuilt themes
|
||||||
|
|
||||||
GitHub readme stats comes with several built-in themes (e.g. `dark`, `radical`, `merko`, `gruvbox`, `tokyonight`, `onedark`, `cobalt`, `synthwave`, `highcontrast`, `dracula`).
|
Github readme stats comes with several built-in themes (e.g. `dark`, `radical`, `merko`, `gruvbox`, `tokyonight`, `onedark`, `cobalt`, `synthwave`, `highcontrast`, `dracula`).
|
||||||
|
|
||||||
<img src="https://res.cloudinary.com/anuraghazra/image/upload/v1595174536/grs-themes_l4ynja.png" alt="GitHub Readme Stats Themes" width="600px"/>
|
<img src="https://res.cloudinary.com/anuraghazra/image/upload/v1595174536/grs-themes_l4ynja.png" alt="GitHub Readme Stats Themes" width="600px"/>
|
||||||
|
|
||||||
You can look at a preview for [all available themes](./themes/README.md) or checkout the [theme config file](./themes/index.js) & **you can also contribute new themes** if you like :D
|
You can look at a preview for [all available themes](./themes/README.md) or checkout the [theme config file](./themes/index.js) & **you can also contribute new themes** if you like :D
|
||||||
|
|
||||||
#### Responsive Card Theme
|
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats#gh-dark-mode-only)
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats#gh-light-mode-only)
|
|
||||||
|
|
||||||
Since GitHub will re-upload the cards and serve them from their [CDN](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-anonymized-urls), we can not infer the browser/GitHub theme on the server side. There are, however, four methods you can use to create dynamics themes on the client side.
|
|
||||||
|
|
||||||
##### Use the transparent theme
|
|
||||||
|
|
||||||
We have included a `transparent` theme that has a transparent background. This theme is optimized to look good on GitHub's dark and light default themes. You can enable this theme using the `&theme=transparent` parameter like so:
|
|
||||||
|
|
||||||
```md
|
|
||||||

|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>:eyes: Show example</summary>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
##### Add transparent alpha channel to a themes bg_color
|
|
||||||
|
|
||||||
You can use the `bg_color` parameter to make any of [the available themes](./themes/README.md) transparent. This is done by setting the `bg_color` to a color with a transparent alpha channel (i.e. `bg_color=00000000`):
|
|
||||||
|
|
||||||
```md
|
|
||||||

|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>:eyes: Show example</summary>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
##### Use GitHub's theme context tag
|
|
||||||
|
|
||||||
You can use [GitHub's theme context](https://github.blog/changelog/2021-11-24-specify-theme-context-for-images-in-markdown/) tags to switch the theme based on the user GitHub theme automatically. This is done by appending `#gh-dark-mode-only` or `#gh-light-mode-only` to the end of an image URL. This tag will define whether the image specified in the markdown is only shown to viewers using a light or a dark GitHub theme:
|
|
||||||
|
|
||||||
```md
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats#gh-dark-mode-only)
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats#gh-light-mode-only)
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>:eyes: Show example</summary>
|
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats#gh-dark-mode-only)
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats#gh-light-mode-only)
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
##### Use GitHub's new media feature
|
|
||||||
|
|
||||||
You can use [GitHub's new media feature](https://github.blog/changelog/2022-05-19-specify-theme-context-for-images-in-markdown-beta/) in HTML to specify whether to display images for light or dark themes. This is done using the HTML `<picture>` element in combination with the `prefers-color-scheme` media feature.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<picture>
|
|
||||||
<source
|
|
||||||
srcset="https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&theme=dark"
|
|
||||||
media="(prefers-color-scheme: dark)"
|
|
||||||
/>
|
|
||||||
<source
|
|
||||||
srcset="https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true"
|
|
||||||
media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
|
|
||||||
/>
|
|
||||||
<img src="https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true" />
|
|
||||||
</picture>
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>:eyes: Show example</summary>
|
|
||||||
|
|
||||||
<picture>
|
|
||||||
<source
|
|
||||||
srcset="https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&theme=dark"
|
|
||||||
media="(prefers-color-scheme: dark)"
|
|
||||||
/>
|
|
||||||
<source
|
|
||||||
srcset="https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true"
|
|
||||||
media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
|
|
||||||
/>
|
|
||||||
<img src="https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true" />
|
|
||||||
</picture>
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### Customization
|
### Customization
|
||||||
|
|
||||||
You can customize the appearance of your `Stats Card` or `Repo Card` however you wish with URL parameters.
|
You can customize the appearance of your `Stats Card` or `Repo Card` however you wish with URL parameters.
|
||||||
@@ -247,13 +168,13 @@ You can customize the appearance of your `Stats Card` or `Repo Card` however you
|
|||||||
- `border_color` - Card's border color _(hex color)_. Default: `e4e2e2` (Does not apply when `hide_border` is enabled).
|
- `border_color` - Card's border color _(hex color)_. Default: `e4e2e2` (Does not apply when `hide_border` is enabled).
|
||||||
- `bg_color` - Card's background color _(hex color)_ **or** a gradient in the form of _angle,start,end_. Default: `fffefe`
|
- `bg_color` - Card's background color _(hex color)_ **or** a gradient in the form of _angle,start,end_. Default: `fffefe`
|
||||||
- `hide_border` - Hides the card's border _(boolean)_. Default: `false`
|
- `hide_border` - Hides the card's border _(boolean)_. Default: `false`
|
||||||
- `theme` - name of the theme, choose from [all available themes](./themes/README.md). Default: `default` theme.
|
- `theme` - name of the theme, choose from [all available themes](./themes/README.md). Default: `default` theme.
|
||||||
- `cache_seconds` - set the cache header manually _(min: 14400, max: 86400)_. Default: `14400 seconds (4 hours)`.
|
- `cache_seconds` - set the cache header manually _(min: 7200, max: 86400)_. Default: `14400 seconds (4 hours)`.
|
||||||
- `locale` - set the language in the card _(e.g. cn, de, es, etc.)_. Default: `en`.
|
- `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`.
|
- `border_radius` - Corner rounding on the card. Default: `4.5`.
|
||||||
|
|
||||||
> **Warning**
|
> **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.
|
> 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.
|
||||||
|
|
||||||
##### Gradient in bg_color
|
##### Gradient in bg_color
|
||||||
|
|
||||||
@@ -267,17 +188,14 @@ You can provide multiple comma-separated values in the bg_color option to render
|
|||||||
- `hide_title` - _(boolean)_. Default: `false`.
|
- `hide_title` - _(boolean)_. Default: `false`.
|
||||||
- `card_width` - Set the card's width manually _(number)_. Default: `500px (approx.)`.
|
- `card_width` - Set the card's width manually _(number)_. Default: `500px (approx.)`.
|
||||||
- `hide_rank` - _(boolean)_ hides the rank and automatically resizes the card width. Default: `false`.
|
- `hide_rank` - _(boolean)_ hides the rank and automatically resizes the card width. Default: `false`.
|
||||||
- `rank_icon` - Shows alternative rank icon (i.e. `github` or `default`). Default: `default`.
|
|
||||||
- `show_icons` - _(boolean)_. Default: `false`.
|
- `show_icons` - _(boolean)_. Default: `false`.
|
||||||
- `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_. Default: `false`.
|
- `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_. Default: `false`.
|
||||||
- `count_private` - Count private contributions _(boolean)_. Default: `false`.
|
- `count_private` - Count private commits _(boolean)_. Default: `false`.
|
||||||
- `line_height` - Sets the line height between text _(number)_. Default: `25`.
|
- `line_height` - Sets the line height between text _(number)_. Default: `25`.
|
||||||
- `exclude_repo` - Exclude stars from specified repositories _(Comma-separated values)_. Default: `[] (blank array)`.
|
- `exclude_repo` - Exclude stars from specified repositories _(Comma-separated values)_. Default: `[] (blank array)`.
|
||||||
- `custom_title` - Sets a custom title for the card. Default: `<username> GitHub Stats`.
|
- `custom_title` - Sets a custom title for the card. Default: `<username> Github Stats`.
|
||||||
- `text_bold` - Use bold text _(boolean)_. Default: `true`.
|
- `text_bold` - Use bold text _(boolean)_. Default: `true`.
|
||||||
- `disable_animations` - Disables all animations in the card _(boolean)_. Default: `false`.
|
- `disable_animations` - Disables all animations in the card _(boolean)_. Default: `false`.
|
||||||
- `ring_color` - Color of the rank circle _(hex color)_. Defaults to the theme ring color if it exists and otherwise the title color.
|
|
||||||
- `number_format` - Switch between two available formats for displaying the card values `short` (i.e. `6.6k`) and `long` (i.e. `6626`). Default: `short`.
|
|
||||||
|
|
||||||
> **Note**
|
> **Note**
|
||||||
> When hide_rank=`true`, the minimum card width is 270 px + the title length and padding.
|
> When hide_rank=`true`, the minimum card width is 270 px + the title length and padding.
|
||||||
@@ -290,15 +208,11 @@ You can provide multiple comma-separated values in the bg_color option to render
|
|||||||
|
|
||||||
- `hide` - Hide the languages specified from the card _(Comma-separated values)_. Default: `[] (blank array)`.
|
- `hide` - Hide the languages specified from the card _(Comma-separated values)_. Default: `[] (blank array)`.
|
||||||
- `hide_title` - _(boolean)_. Default: `false`.
|
- `hide_title` - _(boolean)_. Default: `false`.
|
||||||
- `layout` - Switch between four available layouts `normal` & `compact` & `donut` & `pie`. Default: `normal`.
|
- `layout` - Switch between two available layouts `default` & `compact`. Default: `default`.
|
||||||
- `card_width` - Set the card's width manually _(number)_. Default `300`.
|
- `card_width` - Set the card's width manually _(number)_. Default `300`.
|
||||||
- `langs_count` - Show more languages on the card, between 1-10 _(number)_. Default `5`.
|
- `langs_count` - Show more languages on the card, between 1-10 _(number)_. Default `5`.
|
||||||
- `exclude_repo` - Exclude specified repositories _(Comma-separated values)_. Default: `[] (blank array)`.
|
- `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`.
|
- `custom_title` - Sets a custom title for the card _(string)_. Default `Most Used Languages`.
|
||||||
- `disable_animations` - Disables all animations in the card _(boolean)_. Default: `false`.
|
|
||||||
- `hide_progress` - It uses the compact layout option, hides percentages, and removes the bars. Default: `false`.
|
|
||||||
- `size_weight` - Configures language stats algorithm _(number)_ (see [Language stats algorithm](#Language-stats-algorithm)), defaults to 1.
|
|
||||||
- `count_weight` - Configures language stats algorithm _(number)_ (see [Language stats algorithm](#Language-stats-algorithm)), defaults to 0.
|
|
||||||
|
|
||||||
> **Warning**
|
> **Warning**
|
||||||
> Language names should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding)
|
> Language names should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding)
|
||||||
@@ -348,7 +262,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.
|
The top languages card shows a GitHub user's most frequently used top language.
|
||||||
|
|
||||||
> **Note**
|
> **Note**
|
||||||
> Top Languages does not indicate the user's 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.
|
> 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
|
### Usage
|
||||||
|
|
||||||
@@ -360,24 +274,6 @@ Endpoint: `api/top-langs?username=anuraghazra`
|
|||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Language stats algorithm
|
|
||||||
|
|
||||||
We use the following algorithm to calculate the languages percentages on the language card:
|
|
||||||
|
|
||||||
```js
|
|
||||||
ranking_index = (byte_count ^ size_weight) * (repo_count ^ count_weight)
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, only the byte count is used for determining the languages percentages shown on the language card (i.e. `size_weight=1` and `count_weight=0`). You can, however, use the `&size_weight=` and `&count_weight=` options to weight the language usage calculation. The values must be positive real numbers. [More details about the algorithm can be found here](https://github.com/anuraghazra/github-readme-stats/issues/1600#issuecomment-1046056305).
|
|
||||||
|
|
||||||
- `&size_weight=1&count_weight=0` - _(default)_ Orders by byte count.
|
|
||||||
- `&size_weight=0.5&count_weight=0.5` - _(recommended)_ Uses both byte and repo count for ranking
|
|
||||||
- `&size_weight=0&count_weight=1` - Orders by repo count
|
|
||||||
|
|
||||||
```md
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Exclude individual repositories
|
### Exclude individual repositories
|
||||||
|
|
||||||
You can use the `&exclude_repo=repo1,repo2` parameter to exclude individual repositories.
|
You can use the `&exclude_repo=repo1,repo2` parameter to exclude individual repositories.
|
||||||
@@ -410,30 +306,6 @@ You can use the `&layout=compact` option to change the card design.
|
|||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Donut Chart Language Card Layout
|
|
||||||
|
|
||||||
You can use the `&layout=donut` option to change the card design.
|
|
||||||
|
|
||||||
```md
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pie Chart Language Card Layout
|
|
||||||
|
|
||||||
You can use the `&layout=pie` option to change the card design.
|
|
||||||
|
|
||||||
```md
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Hide Progress Bars
|
|
||||||
|
|
||||||
You can use the `&hide_progress=true` option to hide the percentages and the progress bars (layout will be automatically set to `compact`).
|
|
||||||
|
|
||||||
```md
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Demo
|
### Demo
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
@@ -442,24 +314,12 @@ You can use the `&hide_progress=true` option to hide the percentages and the pro
|
|||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
- Donut Chart layout
|
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
|
||||||
|
|
||||||
- Pie Chart layout
|
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
|
||||||
|
|
||||||
- Hidden progress bars
|
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
|
||||||
|
|
||||||
# Wakatime Week Stats
|
# Wakatime Week Stats
|
||||||
|
|
||||||
Change the `?username=` value to your [Wakatime](https://wakatime.com) username.
|
Change the `?username=` value to your [Wakatime](https://wakatime.com) username.
|
||||||
|
|
||||||
```md
|
```md
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note**:
|
> **Note**:
|
||||||
@@ -467,13 +327,13 @@ Change the `?username=` value to your [Wakatime](https://wakatime.com) username.
|
|||||||
|
|
||||||
### Demo
|
### Demo
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
- Compact layout
|
- Compact layout
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
* * *
|
* * *
|
||||||
|
|
||||||
@@ -491,10 +351,6 @@ Change the `?username=` value to your [Wakatime](https://wakatime.com) username.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
- Shows Github logo instead rank level
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
- Customize Border Color
|
- Customize Border Color
|
||||||
|
|
||||||

|

|
||||||
@@ -531,7 +387,7 @@ Choose from any of the [default themes](#themes)
|
|||||||
|
|
||||||
- WakaTime card
|
- WakaTime card
|
||||||
|
|
||||||
[](https://github.com/anuraghazra/github-readme-stats)
|
[](https://github.com/anuraghazra/github-readme-stats)
|
||||||
|
|
||||||
* * *
|
* * *
|
||||||
|
|
||||||
@@ -548,11 +404,9 @@ By default, GitHub does not lay out the cards side by side. To do that, you can
|
|||||||
</a>
|
</a>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deploy on your own
|
## Deploy on your own Vercel instance
|
||||||
|
|
||||||
### On Vercel
|
#### [Check Out Step By Step Video Tutorial By @codeSTACKr](https://youtu.be/n6d4KHSKqGk?t=107)
|
||||||
|
|
||||||
#### :film_projector: [Check Out Step By Step Video Tutorial By @codeSTACKr](https://youtu.be/n6d4KHSKqGk?t=107)
|
|
||||||
|
|
||||||
> **Warning**
|
> **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).
|
> 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).
|
||||||
@@ -587,23 +441,6 @@ Since the GitHub API only allows 5k requests per hour, my `https://github-readme
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### On other platforms
|
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
> This way of using GRS is not officially supported and was added to cater to some particular use cases where Vercel could not be used (e.g. #2341). The support for this method, therefore, is limited.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><b>:hammer_and_wrench: Step-by-step guide for deploying on other platforms</b></summary>
|
|
||||||
|
|
||||||
1. Fork or clone this repo as per your needs
|
|
||||||
2. Add `express` to the dependencies section of `package.json`
|
|
||||||
<https://github.com/anuraghazra/github-readme-stats/blob/ba7c2f8b55eac8452e479c8bd38b044d204d0424/package.json#L54-L61>
|
|
||||||
3. Run `npm i` if needed (initial setup)
|
|
||||||
4. Run `node express.js` to start the server, or set the entry point to `express.js` in `package.json` if you're deploying on a managed service
|
|
||||||
<https://github.com/anuraghazra/github-readme-stats/blob/ba7c2f8b55eac8452e479c8bd38b044d204d0424/package.json#L11>
|
|
||||||
5. You're done 🎉
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### Keep your fork up to date
|
### 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.
|
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.
|
||||||
|
|||||||
@@ -9,28 +9,19 @@ import github from "@actions/github";
|
|||||||
import { RequestError } from "@octokit/request-error";
|
import { RequestError } from "@octokit/request-error";
|
||||||
import { getGithubToken, getRepoInfo } from "./helpers.js";
|
import { getGithubToken, getRepoInfo } from "./helpers.js";
|
||||||
|
|
||||||
|
// Script parameters
|
||||||
const CLOSING_COMMENT = `
|
const CLOSING_COMMENT = `
|
||||||
\rThis theme PR has been automatically closed due to inactivity. Please reopen it if you want to continue working on it.\
|
\rThis PR has been automatically closed due to inactivity. Please feel free to reopen it if you need to continue working on it.\
|
||||||
\rThank you for your contributions.
|
\rThank you for your contributions.
|
||||||
`;
|
`;
|
||||||
const REVIEWER = "github-actions[bot]";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the review user.
|
|
||||||
* @returns {string} review user.
|
|
||||||
*/
|
|
||||||
const getReviewer = () => {
|
|
||||||
return process.env.REVIEWER ? process.env.REVIEWER : REVIEWER;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch open PRs from a given repository.
|
* Fetch open PRs from a given repository.
|
||||||
* @param user The user name of the repository owner.
|
* @param user The user name of the repository owner.
|
||||||
* @param repo The name of the repository.
|
* @param repo The name of the repository.
|
||||||
* @param reviewer The reviewer to filter by.
|
|
||||||
* @returns The open PRs.
|
* @returns The open PRs.
|
||||||
*/
|
*/
|
||||||
export const fetchOpenPRs = async (octokit, user, repo, reviewer) => {
|
export const fetchOpenPRs = async (octokit, user, repo) => {
|
||||||
const openPRs = [];
|
const openPRs = [];
|
||||||
let hasNextPage = true;
|
let hasNextPage = true;
|
||||||
let endCursor;
|
let endCursor;
|
||||||
@@ -58,9 +49,9 @@ export const fetchOpenPRs = async (octokit, user, repo, reviewer) => {
|
|||||||
name
|
name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reviews(first: 100, states: CHANGES_REQUESTED, author: "${reviewer}") {
|
reviews(first: 1, states: CHANGES_REQUESTED, author: "github-actions[bot]") {
|
||||||
nodes {
|
nodes {
|
||||||
submittedAt
|
updatedAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,13 +99,11 @@ const isStale = (pullRequest, staleDays) => {
|
|||||||
pullRequest.commits.nodes[0].commit.pushedDate,
|
pullRequest.commits.nodes[0].commit.pushedDate,
|
||||||
);
|
);
|
||||||
if (pullRequest.reviews.nodes[0]) {
|
if (pullRequest.reviews.nodes[0]) {
|
||||||
const lastReviewDate = new Date(
|
const lastReviewDate = new Date(pullRequest.reviews.nodes[0].updatedAt);
|
||||||
pullRequest.reviews.nodes.sort((a, b) => (a < b ? 1 : -1))[0].submittedAt,
|
|
||||||
);
|
|
||||||
const lastUpdateDate =
|
const lastUpdateDate =
|
||||||
lastCommitDate >= lastReviewDate ? lastCommitDate : lastReviewDate;
|
lastCommitDate >= lastReviewDate ? lastCommitDate : lastReviewDate;
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
return (now - lastUpdateDate) / (1000 * 60 * 60 * 24) >= staleDays;
|
return now - lastUpdateDate > 1000 * 60 * 60 * 24 * staleDays;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -127,15 +116,14 @@ const run = async () => {
|
|||||||
try {
|
try {
|
||||||
// Create octokit client.
|
// Create octokit client.
|
||||||
const dryRun = process.env.DRY_RUN === "true" || false;
|
const dryRun = process.env.DRY_RUN === "true" || false;
|
||||||
const staleDays = process.env.STALE_DAYS || 20;
|
const staleDays = process.env.STALE_DAYS || 15;
|
||||||
debug("Creating octokit client...");
|
debug("Creating octokit client...");
|
||||||
const octokit = github.getOctokit(getGithubToken());
|
const octokit = github.getOctokit(getGithubToken());
|
||||||
const { owner, repo } = getRepoInfo(github.context);
|
const { owner, repo } = getRepoInfo(github.context);
|
||||||
const reviewer = getReviewer();
|
|
||||||
|
|
||||||
// Retrieve all theme pull requests.
|
// Retrieve all theme pull requests.
|
||||||
debug("Retrieving all theme pull requests...");
|
debug("Retrieving all theme pull requests...");
|
||||||
const prs = await fetchOpenPRs(octokit, owner, repo, reviewer);
|
const prs = await fetchOpenPRs(octokit, owner, repo);
|
||||||
const themePRs = pullsWithLabel(prs, "themes");
|
const themePRs = pullsWithLabel(prs, "themes");
|
||||||
const invalidThemePRs = pullsWithLabel(themePRs, "invalid");
|
const invalidThemePRs = pullsWithLabel(themePRs, "invalid");
|
||||||
debug("Retrieving stale theme PRs...");
|
debug("Retrieving stale theme PRs...");
|
||||||
|
|||||||
+2
-1
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import { getInput } from "@actions/core";
|
import { getInput } from "@actions/core";
|
||||||
|
|
||||||
|
// Script variables.
|
||||||
const OWNER = "anuraghazra";
|
const OWNER = "anuraghazra";
|
||||||
const REPO = "github-readme-stats";
|
const REPO = "github-readme-stats";
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ export const getRepoInfo = (ctx) => {
|
|||||||
/**
|
/**
|
||||||
* Retrieve github token and throw error if it is not found.
|
* Retrieve github token and throw error if it is not found.
|
||||||
*
|
*
|
||||||
* @returns {string} GitHub token.
|
* @returns {string} Github token.
|
||||||
*/
|
*/
|
||||||
export const getGithubToken = () => {
|
export const getGithubToken = () => {
|
||||||
const token = getInput("github_token") || process.env.GITHUB_TOKEN;
|
const token = getInput("github_token") || process.env.GITHUB_TOKEN;
|
||||||
|
|||||||
+66
-133
@@ -16,6 +16,7 @@ import { isValidHexColor } from "../src/common/utils.js";
|
|||||||
import { themes } from "../themes/index.js";
|
import { themes } from "../themes/index.js";
|
||||||
import { getGithubToken, getRepoInfo } from "./helpers.js";
|
import { getGithubToken, getRepoInfo } from "./helpers.js";
|
||||||
|
|
||||||
|
// Script variables.
|
||||||
const COMMENTER = "github-actions[bot]";
|
const COMMENTER = "github-actions[bot]";
|
||||||
|
|
||||||
const COMMENT_TITLE = "Automated Theme Preview";
|
const COMMENT_TITLE = "Automated Theme Preview";
|
||||||
@@ -24,11 +25,11 @@ const THEME_PR_SUCCESS_TEXT =
|
|||||||
":heavy_check_mark: Theme PR does adhere to our guidelines.";
|
":heavy_check_mark: Theme PR does adhere to our guidelines.";
|
||||||
const FAIL_TEXT = `
|
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 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\
|
||||||
\r PR again. This pull request will **automatically close in 20 days** if no changes are made. After this time, you must re-open the PR for it to be reviewed.
|
\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_GUIDELINES = `
|
const THEME_CONTRIB_GUIDELINESS = `
|
||||||
\rHi, thanks for the theme contribution. Please read our theme [contribution guidelines](https://github.com/anuraghazra/github-readme-stats/blob/master/CONTRIBUTING.md#themes-contribution).
|
\rHi, thanks for the theme contribution. Please read our theme [contribution guidelines](https://github.com/anuraghazra/github-readme-stats/blob/master/CONTRIBUTING.md#themes-contribution).
|
||||||
\rWe are currently only accepting color combinations from any VSCode theme or themes with good color combinations to minimize bloating the themes collection.
|
\rWe are currently only accepting color combinations from any VSCode theme or themes with good colour combinations to minimize bloating the themes collection.
|
||||||
|
|
||||||
\r> Also, note that if this theme is exclusively for your personal use, then instead of adding it to our theme collection, you can use card [customization options](https://github.com/anuraghazra/github-readme-stats#customization).
|
\r> Also, note that if this theme is exclusively for your personal use, then instead of adding it to our theme collection, you can use card [customization options](https://github.com/anuraghazra/github-readme-stats#customization).
|
||||||
`;
|
`;
|
||||||
@@ -43,23 +44,6 @@ const ACCEPTED_COLOR_PROPS = Object.keys(COLOR_PROPS);
|
|||||||
const REQUIRED_COLOR_PROPS = ACCEPTED_COLOR_PROPS.slice(0, 4);
|
const REQUIRED_COLOR_PROPS = ACCEPTED_COLOR_PROPS.slice(0, 4);
|
||||||
const INVALID_REVIEW_COMMENT = (commentUrl) =>
|
const INVALID_REVIEW_COMMENT = (commentUrl) =>
|
||||||
`Some themes are invalid. See the [Automated Theme Preview](${commentUrl}) comment above for more information.`;
|
`Some themes are invalid. See the [Automated Theme Preview](${commentUrl}) comment above for more information.`;
|
||||||
var OCTOKIT;
|
|
||||||
var OWNER;
|
|
||||||
var REPO;
|
|
||||||
var PULL_REQUEST_ID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Incorrect JSON format error.
|
|
||||||
* @extends Error
|
|
||||||
* @param {string} message Error message.
|
|
||||||
* @returns {Error} IncorrectJsonFormatError.
|
|
||||||
*/
|
|
||||||
class IncorrectJsonFormatError extends Error {
|
|
||||||
constructor(message) {
|
|
||||||
super(message);
|
|
||||||
this.name = "IncorrectJsonFormatError";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve PR number from the event payload.
|
* Retrieve PR number from the event payload.
|
||||||
@@ -109,7 +93,7 @@ const isPreviewComment = (inputs, comment) => {
|
|||||||
* @param {number} issueNumber Issue number.
|
* @param {number} issueNumber Issue number.
|
||||||
* @param {string} repo Repository name.
|
* @param {string} repo Repository name.
|
||||||
* @param {string} owner Owner of the repository.
|
* @param {string} owner Owner of the repository.
|
||||||
* @returns {Object} The GitHub comment object.
|
* @returns {Object} The Github comment object.
|
||||||
*/
|
*/
|
||||||
const findComment = async (octokit, issueNumber, owner, repo, commenter) => {
|
const findComment = async (octokit, issueNumber, owner, repo, commenter) => {
|
||||||
const parameters = {
|
const parameters = {
|
||||||
@@ -143,36 +127,15 @@ const findComment = async (octokit, issueNumber, owner, repo, commenter) => {
|
|||||||
* Create or update the preview comment.
|
* Create or update the preview comment.
|
||||||
*
|
*
|
||||||
* @param {Object} octokit Octokit instance.
|
* @param {Object} octokit Octokit instance.
|
||||||
* @param {number} issueNumber Issue number.
|
* @param {Object} props Comment properties.
|
||||||
* @param {Object} repo Repository name.
|
|
||||||
* @param {Object} owner Owner of the repository.
|
|
||||||
* @param {number} commentId Comment ID.
|
|
||||||
* @param {string} body Comment body.
|
|
||||||
* @return {string} The comment URL.
|
* @return {string} The comment URL.
|
||||||
*/
|
*/
|
||||||
const upsertComment = async (
|
const upsertComment = async (octokit, props) => {
|
||||||
octokit,
|
|
||||||
issueNumber,
|
|
||||||
repo,
|
|
||||||
owner,
|
|
||||||
commentId,
|
|
||||||
body,
|
|
||||||
) => {
|
|
||||||
let resp;
|
let resp;
|
||||||
if (commentId !== undefined) {
|
if (props.comment_id !== undefined) {
|
||||||
resp = await octokit.issues.updateComment({
|
resp = await octokit.issues.updateComment(props);
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
comment_id: commentId,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
resp = await octokit.issues.createComment({
|
resp = await octokit.issues.createComment(props);
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: issueNumber,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return resp.data.html_url;
|
return resp.data.html_url;
|
||||||
};
|
};
|
||||||
@@ -307,38 +270,22 @@ const parseJSON = (json) => {
|
|||||||
if (typeof parsedJson === "object") {
|
if (typeof parsedJson === "object") {
|
||||||
return parsedJson;
|
return parsedJson;
|
||||||
} else {
|
} else {
|
||||||
throw new IncorrectJsonFormatError(
|
throw new Error("PR diff is not a valid theme JSON object.");
|
||||||
"PR diff is not a valid theme JSON object.",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Remove trailing commas (if any).
|
let parsedJson = json
|
||||||
let parsedJson = json.replace(/(,\s*})/g, "}");
|
|
||||||
|
|
||||||
// Remove JS comments (if any).
|
|
||||||
parsedJson = parsedJson.replace(/\/\/[A-z\s]*\s/g, "");
|
|
||||||
|
|
||||||
// Fix incorrect open bracket (if any).
|
|
||||||
const splitJson = parsedJson
|
|
||||||
.split(/([\s\r\s]*}[\s\r\s]*,[\s\r\s]*)(?=[\w"-]+:)/)
|
.split(/([\s\r\s]*}[\s\r\s]*,[\s\r\s]*)(?=[\w"-]+:)/)
|
||||||
.filter((x) => typeof x !== "string" || !!x.trim()); // Split json into array of strings and objects.
|
.filter((x) => typeof x !== "string" || !!x.trim());
|
||||||
if (splitJson[0].replace(/\s+/g, "") === "},") {
|
if (parsedJson[0].replace(/\s+/g, "") === "},") {
|
||||||
splitJson[0] = "},";
|
parsedJson[0] = "},";
|
||||||
if (!/\s*}\s*,?\s*$/.test(splitJson[1])) {
|
if (!/\s*}\s*,?\s*$/.test(parsedJson[1])) {
|
||||||
splitJson.push(splitJson.shift());
|
parsedJson.push(parsedJson.shift());
|
||||||
} else {
|
} else {
|
||||||
splitJson.shift();
|
parsedJson.shift();
|
||||||
}
|
}
|
||||||
parsedJson = splitJson.join("");
|
return Hjson.parse(parsedJson.join(""));
|
||||||
}
|
} else {
|
||||||
|
throw error;
|
||||||
// Try to parse the fixed json.
|
|
||||||
try {
|
|
||||||
return Hjson.parse(parsedJson);
|
|
||||||
} catch (error) {
|
|
||||||
throw new IncorrectJsonFormatError(
|
|
||||||
`Theme JSON file could not be parsed: ${error.message}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -352,64 +299,53 @@ const themeNameAlreadyExists = (name) => {
|
|||||||
return themes[name] !== undefined;
|
return themes[name] !== undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DRY_RUN = process.env.DRY_RUN === "true" || false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main function.
|
* Main function.
|
||||||
*/
|
*/
|
||||||
export const run = async () => {
|
export const run = async (prNumber) => {
|
||||||
try {
|
try {
|
||||||
|
const dryRun = process.env.DRY_RUN === "true" || false;
|
||||||
debug("Retrieve action information from context...");
|
debug("Retrieve action information from context...");
|
||||||
debug(`Context: ${inspect(github.context)}`);
|
debug(`Context: ${inspect(github.context)}`);
|
||||||
let commentBody = `
|
let commentBody = `
|
||||||
\r# ${COMMENT_TITLE}
|
\r# ${COMMENT_TITLE}
|
||||||
\r${THEME_CONTRIB_GUIDELINES}
|
\r${THEME_CONTRIB_GUIDELINESS}
|
||||||
`;
|
`;
|
||||||
const ccc = new ColorContrastChecker();
|
const ccc = new ColorContrastChecker();
|
||||||
OCTOKIT = github.getOctokit(getGithubToken());
|
const octokit = github.getOctokit(getGithubToken());
|
||||||
PULL_REQUEST_ID = getPrNumber();
|
const pullRequestId = prNumber ? prNumber : getPrNumber();
|
||||||
const { owner, repo } = getRepoInfo(github.context);
|
|
||||||
OWNER = owner;
|
|
||||||
REPO = repo;
|
|
||||||
const commenter = getCommenter();
|
const commenter = getCommenter();
|
||||||
PULL_REQUEST_ID = getPrNumber();
|
const { owner, repo } = getRepoInfo(github.context);
|
||||||
debug(`Owner: ${OWNER}`);
|
debug(`Owner: ${owner}`);
|
||||||
debug(`Repo: ${REPO}`);
|
debug(`Repo: ${repo}`);
|
||||||
debug(`Commenter: ${commenter}`);
|
debug(`Commenter: ${commenter}`);
|
||||||
|
|
||||||
// Retrieve the PR diff and preview-theme comment.
|
// Retrieve the PR diff and preview-theme comment.
|
||||||
debug("Retrieve PR diff...");
|
debug("Retrieve PR diff...");
|
||||||
const res = await OCTOKIT.pulls.get({
|
const res = await octokit.pulls.get({
|
||||||
owner: OWNER,
|
owner,
|
||||||
repo: REPO,
|
repo,
|
||||||
pull_number: PULL_REQUEST_ID,
|
pull_number: pullRequestId,
|
||||||
mediaType: {
|
mediaType: {
|
||||||
format: "diff",
|
format: "diff",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
debug("Retrieve preview-theme comment...");
|
debug("Retrieve preview-theme comment...");
|
||||||
const comment = await findComment(
|
const comment = await findComment(
|
||||||
OCTOKIT,
|
octokit,
|
||||||
PULL_REQUEST_ID,
|
pullRequestId,
|
||||||
OWNER,
|
owner,
|
||||||
REPO,
|
repo,
|
||||||
commenter,
|
commenter,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Retrieve theme changes from the PR diff.
|
// Retrieve theme changes from the PR diff.
|
||||||
debug("Retrieve themes...");
|
debug("Retrieve themes...");
|
||||||
const diff = parse(res.data);
|
const diff = parse(res.data);
|
||||||
|
|
||||||
// Retrieve all theme changes from the PR diff and convert to JSON.
|
|
||||||
debug("Retrieve theme changes...");
|
|
||||||
const content = diff
|
const content = diff
|
||||||
.find((file) => file.to === "themes/index.js")
|
.find((file) => file.to === "themes/index.js")
|
||||||
.chunks.map((chunk) =>
|
.chunks[0].changes.filter((c) => c.type === "add")
|
||||||
chunk.changes
|
.map((c) => c.content.replace("+", ""))
|
||||||
.filter((c) => c.type === "add")
|
|
||||||
.map((c) => c.content.replace("+", ""))
|
|
||||||
.join(""),
|
|
||||||
)
|
|
||||||
.join("");
|
.join("");
|
||||||
const themeObject = parseJSON(content);
|
const themeObject = parseJSON(content);
|
||||||
if (
|
if (
|
||||||
@@ -578,15 +514,14 @@ export const run = async () => {
|
|||||||
// Create or update theme-preview comment.
|
// Create or update theme-preview comment.
|
||||||
debug("Create or update theme-preview comment...");
|
debug("Create or update theme-preview comment...");
|
||||||
let comment_url;
|
let comment_url;
|
||||||
if (!DRY_RUN) {
|
if (!dryRun) {
|
||||||
comment_url = await upsertComment(
|
comment_url = await upsertComment(octokit, {
|
||||||
OCTOKIT,
|
comment_id: comment?.id,
|
||||||
PULL_REQUEST_ID,
|
issue_number: pullRequestId,
|
||||||
REPO,
|
owner,
|
||||||
OWNER,
|
repo,
|
||||||
comment?.id,
|
body: commentBody,
|
||||||
commentBody,
|
});
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
info(`DRY_RUN: Comment body: ${commentBody}`);
|
info(`DRY_RUN: Comment body: ${commentBody}`);
|
||||||
comment_url = "";
|
comment_url = "";
|
||||||
@@ -601,20 +536,20 @@ export const run = async () => {
|
|||||||
const reviewReason = themesValid
|
const reviewReason = themesValid
|
||||||
? undefined
|
? undefined
|
||||||
: INVALID_REVIEW_COMMENT(comment_url);
|
: INVALID_REVIEW_COMMENT(comment_url);
|
||||||
if (!DRY_RUN) {
|
if (!dryRun) {
|
||||||
await addReview(
|
await addReview(
|
||||||
OCTOKIT,
|
octokit,
|
||||||
PULL_REQUEST_ID,
|
pullRequestId,
|
||||||
OWNER,
|
owner,
|
||||||
REPO,
|
repo,
|
||||||
reviewState,
|
reviewState,
|
||||||
reviewReason,
|
reviewReason,
|
||||||
);
|
);
|
||||||
await addRemoveLabel(
|
await addRemoveLabel(
|
||||||
OCTOKIT,
|
octokit,
|
||||||
PULL_REQUEST_ID,
|
pullRequestId,
|
||||||
OWNER,
|
owner,
|
||||||
REPO,
|
repo,
|
||||||
"invalid",
|
"invalid",
|
||||||
!themesValid,
|
!themesValid,
|
||||||
);
|
);
|
||||||
@@ -624,22 +559,20 @@ export const run = async () => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
debug("Set review state to `REQUEST_CHANGES` and add `invalid` label...");
|
debug("Set review state to `REQUEST_CHANGES` and add `invalid` label...");
|
||||||
if (!DRY_RUN) {
|
if (!dryRun) {
|
||||||
await addReview(
|
await addReview(
|
||||||
OCTOKIT,
|
octokit,
|
||||||
PULL_REQUEST_ID,
|
pullRequestId,
|
||||||
OWNER,
|
owner,
|
||||||
REPO,
|
repo,
|
||||||
"REQUEST_CHANGES",
|
"REQUEST_CHANGES",
|
||||||
"**Something went wrong in the theme preview action:** `" +
|
error.message,
|
||||||
error.message +
|
|
||||||
"`",
|
|
||||||
);
|
);
|
||||||
await addRemoveLabel(
|
await addRemoveLabel(
|
||||||
OCTOKIT,
|
octokit,
|
||||||
PULL_REQUEST_ID,
|
pullRequestId,
|
||||||
OWNER,
|
owner,
|
||||||
REPO,
|
repo,
|
||||||
"invalid",
|
"invalid",
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ set -e
|
|||||||
export BRANCH_NAME=updated-theme-readme
|
export BRANCH_NAME=updated-theme-readme
|
||||||
git --version
|
git --version
|
||||||
git config --global user.email "no-reply@githubreadmestats.com"
|
git config --global user.email "no-reply@githubreadmestats.com"
|
||||||
git config --global user.name "GitHub Readme Stats Bot"
|
git config --global user.name "Github Readme Stats Bot"
|
||||||
git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
|
||||||
git branch -d $BRANCH_NAME || true
|
git branch -d $BRANCH_NAME || true
|
||||||
git checkout -b $BRANCH_NAME
|
git checkout -b $BRANCH_NAME
|
||||||
git add --all
|
git add --all
|
||||||
git commit --no-verify --message "docs(theme): Auto update theme readme"
|
git commit --message "docs(theme): Auto update theme readme" || exit 0
|
||||||
git remote add origin-$BRANCH_NAME https://${PERSONAL_TOKEN}@github.com/${GH_REPO}.git
|
git remote add origin-$BRANCH_NAME https://${PERSONAL_TOKEN}@github.com/${GH_REPO}.git
|
||||||
git push --force --quiet --set-upstream origin-$BRANCH_NAME $BRANCH_NAME
|
git push --force --quiet --set-upstream origin-$BRANCH_NAME $BRANCH_NAME
|
||||||
|
|||||||
+78
-50
@@ -1,71 +1,99 @@
|
|||||||
function expsf(x, lambda = 1) {
|
/**
|
||||||
return 2 ** (-lambda * x);
|
* Calculates the probability of x taking on x or a value less than x in a normal distribution
|
||||||
|
* with mean and standard deviation.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
* @returns {number} Probability.
|
||||||
|
*/
|
||||||
|
function normalcdf(mean, sigma, to) {
|
||||||
|
var z = (to - mean) / Math.sqrt(2 * sigma * sigma);
|
||||||
|
var t = 1 / (1 + 0.3275911 * Math.abs(z));
|
||||||
|
var a1 = 0.254829592;
|
||||||
|
var a2 = -0.284496736;
|
||||||
|
var a3 = 1.421413741;
|
||||||
|
var a4 = -1.453152027;
|
||||||
|
var a5 = 1.061405429;
|
||||||
|
var erf =
|
||||||
|
1 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-z * z);
|
||||||
|
var sign = 1;
|
||||||
|
if (z < 0) {
|
||||||
|
sign = -1;
|
||||||
|
}
|
||||||
|
return (1 / 2) * (1 + sign * erf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the users rank.
|
* Calculates the users rank.
|
||||||
*
|
*
|
||||||
* @param {object} params Parameters on which the user's rank depends.
|
* @param {number} totalRepos Total number of repos.
|
||||||
* @param {boolean} params.all_commits Whether `include_all_commits` was used.
|
* @param {number} totalCommits Total number of commits.
|
||||||
* @param {number} params.commits Number of commits.
|
* @param {number} contributions The number of contributions.
|
||||||
* @param {number} params.prs The number of pull requests.
|
* @param {number} followers The number of followers.
|
||||||
* @param {number} params.issues The number of issues.
|
* @param {number} prs The number of pull requests.
|
||||||
* @param {number} params.repos Total number of repos.
|
* @param {number} issues The number of issues.
|
||||||
* @param {number} params.stars The number of stars.
|
* @param {number} stargazers The number of stars.
|
||||||
* @param {number} params.followers The number of followers.
|
|
||||||
* @returns {{level: string, score: number}}} The users rank.
|
* @returns {{level: string, score: number}}} The users rank.
|
||||||
*/
|
*/
|
||||||
function calculateRank({
|
function calculateRank({
|
||||||
all_commits,
|
totalRepos,
|
||||||
commits,
|
totalCommits,
|
||||||
|
contributions,
|
||||||
|
followers,
|
||||||
prs,
|
prs,
|
||||||
issues,
|
issues,
|
||||||
repos, // unused
|
stargazers,
|
||||||
stars,
|
|
||||||
followers,
|
|
||||||
}) {
|
}) {
|
||||||
const COMMITS_MEAN = all_commits ? 1000 : 250,
|
const COMMITS_OFFSET = 1.65;
|
||||||
COMMITS_WEIGHT = 2;
|
const CONTRIBS_OFFSET = 1.65;
|
||||||
const PRS_MEAN = 50,
|
const ISSUES_OFFSET = 1;
|
||||||
PRS_WEIGHT = 3;
|
const STARS_OFFSET = 0.75;
|
||||||
const ISSUES_MEAN = 25,
|
const PRS_OFFSET = 0.5;
|
||||||
ISSUES_WEIGHT = 1;
|
const FOLLOWERS_OFFSET = 0.45;
|
||||||
const STARS_MEAN = 250,
|
const REPO_OFFSET = 1;
|
||||||
STARS_WEIGHT = 4;
|
|
||||||
const FOLLOWERS_MEAN = 25,
|
|
||||||
FOLLOWERS_WEIGHT = 1;
|
|
||||||
|
|
||||||
const TOTAL_WEIGHT =
|
const ALL_OFFSETS =
|
||||||
COMMITS_WEIGHT +
|
CONTRIBS_OFFSET +
|
||||||
PRS_WEIGHT +
|
ISSUES_OFFSET +
|
||||||
ISSUES_WEIGHT +
|
STARS_OFFSET +
|
||||||
STARS_WEIGHT +
|
PRS_OFFSET +
|
||||||
FOLLOWERS_WEIGHT;
|
FOLLOWERS_OFFSET +
|
||||||
|
REPO_OFFSET;
|
||||||
|
|
||||||
const rank =
|
const RANK_S_VALUE = 1;
|
||||||
(COMMITS_WEIGHT * expsf(commits, 1 / COMMITS_MEAN) +
|
const RANK_DOUBLE_A_VALUE = 25;
|
||||||
PRS_WEIGHT * expsf(prs, 1 / PRS_MEAN) +
|
const RANK_A2_VALUE = 45;
|
||||||
ISSUES_WEIGHT * expsf(issues, 1 / ISSUES_MEAN) +
|
const RANK_A3_VALUE = 60;
|
||||||
STARS_WEIGHT * expsf(stars, 1 / STARS_MEAN) +
|
const RANK_B_VALUE = 100;
|
||||||
FOLLOWERS_WEIGHT * expsf(followers, 1 / FOLLOWERS_MEAN)) /
|
|
||||||
TOTAL_WEIGHT;
|
|
||||||
|
|
||||||
const RANK_S_PLUS = 0.025;
|
const TOTAL_VALUES =
|
||||||
const RANK_S = 0.1;
|
RANK_S_VALUE + RANK_A2_VALUE + RANK_A3_VALUE + RANK_B_VALUE;
|
||||||
const RANK_A_PLUS = 0.25;
|
|
||||||
const RANK_A = 0.5;
|
// prettier-ignore
|
||||||
const RANK_B_PLUS = 0.75;
|
const score = (
|
||||||
|
totalCommits * COMMITS_OFFSET +
|
||||||
|
contributions * CONTRIBS_OFFSET +
|
||||||
|
issues * ISSUES_OFFSET +
|
||||||
|
stargazers * STARS_OFFSET +
|
||||||
|
prs * PRS_OFFSET +
|
||||||
|
followers * FOLLOWERS_OFFSET +
|
||||||
|
totalRepos * REPO_OFFSET
|
||||||
|
) / 100;
|
||||||
|
|
||||||
|
const normalizedScore = normalcdf(score, TOTAL_VALUES, ALL_OFFSETS) * 100;
|
||||||
|
|
||||||
const level = (() => {
|
const level = (() => {
|
||||||
if (rank <= RANK_S_PLUS) return "S+";
|
if (normalizedScore < RANK_S_VALUE) return "S+";
|
||||||
if (rank <= RANK_S) return "S";
|
if (normalizedScore < RANK_DOUBLE_A_VALUE) return "S";
|
||||||
if (rank <= RANK_A_PLUS) return "A+";
|
if (normalizedScore < RANK_A2_VALUE) return "A++";
|
||||||
if (rank <= RANK_A) return "A";
|
if (normalizedScore < RANK_A3_VALUE) return "A+";
|
||||||
if (rank <= RANK_B_PLUS) return "B+";
|
return "B+";
|
||||||
return "B";
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return { level, score: rank * 100 };
|
return { level, score: normalizedScore };
|
||||||
}
|
}
|
||||||
|
|
||||||
export { calculateRank };
|
export { calculateRank };
|
||||||
|
|||||||
+30
-46
@@ -1,7 +1,7 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
import { Card } from "../common/Card.js";
|
import { Card } from "../common/Card.js";
|
||||||
import { I18n } from "../common/I18n.js";
|
import { I18n } from "../common/I18n.js";
|
||||||
import { icons, rankIcon } from "../common/icons.js";
|
import { icons } from "../common/icons.js";
|
||||||
import {
|
import {
|
||||||
clampValue,
|
clampValue,
|
||||||
flexLayout,
|
flexLayout,
|
||||||
@@ -12,24 +12,17 @@ import {
|
|||||||
import { getStyles } from "../getStyles.js";
|
import { getStyles } from "../getStyles.js";
|
||||||
import { statCardLocales } from "../translations.js";
|
import { statCardLocales } from "../translations.js";
|
||||||
|
|
||||||
const CARD_MIN_WIDTH = 287;
|
|
||||||
const CARD_DEFAULT_WIDTH = 287;
|
|
||||||
const RANK_CARD_MIN_WIDTH = 420;
|
|
||||||
const RANK_CARD_DEFAULT_WIDTH = 450;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a stats card text item.
|
* Create a stats card text item.
|
||||||
*
|
*
|
||||||
* @param {object} createTextNodeParams Object that contains the createTextNode parameters.
|
* @param {object[]} createTextNodeParams Object that contains the createTextNode parameters.
|
||||||
* @param {string} createTextNodeParams.icon The icon to display.
|
|
||||||
* @param {string} createTextNodeParams.label The label to display.
|
* @param {string} createTextNodeParams.label The label to display.
|
||||||
* @param {number} createTextNodeParams.value The value to display.
|
* @param {string} createTextNodeParams.value The value to display.
|
||||||
* @param {string} createTextNodeParams.id The id of the stat.
|
* @param {string} createTextNodeParams.id The id of the stat.
|
||||||
* @param {number} createTextNodeParams.index The index of the stat.
|
* @param {number} createTextNodeParams.index The index of the stat.
|
||||||
* @param {boolean} createTextNodeParams.showIcons Whether to show icons.
|
* @param {boolean} createTextNodeParams.showIcons Whether to show icons.
|
||||||
* @param {number} createTextNodeParams.shiftValuePos Number of pixels the value has to be shifted to the right.
|
* @param {number} createTextNodeParams.shiftValuePos Number of pixels the value has to be shifted to the right.
|
||||||
* @param {boolean} createTextNodeParams.bold Whether to bold the label.
|
* @param {boolean} createTextNodeParams.bold Whether to bold the label.
|
||||||
* @param {string} createTextNodeParams.number_format The format of numbers on card.
|
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const createTextNode = ({
|
const createTextNode = ({
|
||||||
@@ -41,10 +34,8 @@ const createTextNode = ({
|
|||||||
showIcons,
|
showIcons,
|
||||||
shiftValuePos,
|
shiftValuePos,
|
||||||
bold,
|
bold,
|
||||||
number_format,
|
|
||||||
}) => {
|
}) => {
|
||||||
const kValue =
|
const kValue = kFormatter(value);
|
||||||
number_format.toLowerCase() === "long" ? value : kFormatter(value);
|
|
||||||
const staggerDelay = (index + 3) * 150;
|
const staggerDelay = (index + 3) * 150;
|
||||||
|
|
||||||
const labelOffset = showIcons ? `x="25"` : "";
|
const labelOffset = showIcons ? `x="25"` : "";
|
||||||
@@ -98,7 +89,6 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
include_all_commits = false,
|
include_all_commits = false,
|
||||||
line_height = 25,
|
line_height = 25,
|
||||||
title_color,
|
title_color,
|
||||||
ring_color,
|
|
||||||
icon_color,
|
icon_color,
|
||||||
text_color,
|
text_color,
|
||||||
text_bold = true,
|
text_bold = true,
|
||||||
@@ -107,23 +97,20 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
custom_title,
|
custom_title,
|
||||||
border_radius,
|
border_radius,
|
||||||
border_color,
|
border_color,
|
||||||
number_format = "short",
|
|
||||||
locale,
|
locale,
|
||||||
disable_animations = false,
|
disable_animations = false,
|
||||||
rank_icon = "default",
|
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const lheight = parseInt(String(line_height), 10);
|
const lheight = parseInt(String(line_height), 10);
|
||||||
|
|
||||||
// returns theme based colors with proper overrides and defaults
|
// returns theme based colors with proper overrides and defaults
|
||||||
const { titleColor, iconColor, textColor, bgColor, borderColor, ringColor } =
|
const { titleColor, textColor, iconColor, bgColor, borderColor } =
|
||||||
getCardColors({
|
getCardColors({
|
||||||
title_color,
|
title_color,
|
||||||
text_color,
|
|
||||||
icon_color,
|
icon_color,
|
||||||
|
text_color,
|
||||||
bg_color,
|
bg_color,
|
||||||
border_color,
|
border_color,
|
||||||
ring_color,
|
|
||||||
theme,
|
theme,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -165,7 +152,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
},
|
},
|
||||||
contribs: {
|
contribs: {
|
||||||
icon: icons.contribs,
|
icon: icons.contribs,
|
||||||
label: i18n.t("statcard.contribs") + " (last year)",
|
label: i18n.t("statcard.contribs"),
|
||||||
value: contributedTo,
|
value: contributedTo,
|
||||||
id: "contribs",
|
id: "contribs",
|
||||||
},
|
},
|
||||||
@@ -183,9 +170,8 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
"pl",
|
"pl",
|
||||||
"de",
|
"de",
|
||||||
"nl",
|
"nl",
|
||||||
"zh-tw",
|
|
||||||
];
|
];
|
||||||
const isLongLocale = longLocales.includes(locale);
|
const isLongLocale = longLocales.includes(locale) === true;
|
||||||
|
|
||||||
// filter out hidden stats defined by user & create the text nodes
|
// filter out hidden stats defined by user & create the text nodes
|
||||||
const statItems = Object.keys(STATS)
|
const statItems = Object.keys(STATS)
|
||||||
@@ -196,9 +182,9 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
...STATS[key],
|
...STATS[key],
|
||||||
index,
|
index,
|
||||||
showIcons: show_icons,
|
showIcons: show_icons,
|
||||||
shiftValuePos: 79.01 + (isLongLocale ? 50 : 0),
|
shiftValuePos:
|
||||||
|
(!include_all_commits ? 50 : 35) + (isLongLocale ? 50 : 0),
|
||||||
bold: text_bold,
|
bold: text_bold,
|
||||||
number_format,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -214,7 +200,6 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
const progress = 100 - rank.score;
|
const progress = 100 - rank.score;
|
||||||
const cssStyles = getStyles({
|
const cssStyles = getStyles({
|
||||||
titleColor,
|
titleColor,
|
||||||
ringColor,
|
|
||||||
textColor,
|
textColor,
|
||||||
iconColor,
|
iconColor,
|
||||||
show_icons,
|
show_icons,
|
||||||
@@ -230,17 +215,11 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
When hide_rank=false, the minimum card_width is 340 px + the icon width (if show_icons=true).
|
When hide_rank=false, the minimum card_width is 340 px + the icon width (if show_icons=true).
|
||||||
Numbers are picked by looking at existing dimensions on production.
|
Numbers are picked by looking at existing dimensions on production.
|
||||||
*/
|
*/
|
||||||
const iconWidth = show_icons ? 16 + /* padding */ 1 : 0;
|
const iconWidth = show_icons ? 16 : 0;
|
||||||
const minCardWidth =
|
const minCardWidth = hide_rank
|
||||||
(hide_rank
|
? clampValue(50 /* padding */ + calculateTextWidth() * 2, 270, Infinity)
|
||||||
? clampValue(
|
: 340 + iconWidth;
|
||||||
50 /* padding */ + calculateTextWidth() * 2,
|
const defaultCardWidth = hide_rank ? 270 : 495;
|
||||||
CARD_MIN_WIDTH,
|
|
||||||
Infinity,
|
|
||||||
)
|
|
||||||
: RANK_CARD_MIN_WIDTH) + iconWidth;
|
|
||||||
const defaultCardWidth =
|
|
||||||
(hide_rank ? CARD_DEFAULT_WIDTH : RANK_CARD_DEFAULT_WIDTH) + iconWidth;
|
|
||||||
let width = isNaN(card_width) ? defaultCardWidth : card_width;
|
let width = isNaN(card_width) ? defaultCardWidth : card_width;
|
||||||
if (width < minCardWidth) {
|
if (width < minCardWidth) {
|
||||||
width = minCardWidth;
|
width = minCardWidth;
|
||||||
@@ -269,21 +248,18 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the right rank circle translation values such that the rank circle
|
* Calculates the right rank circle translation values such that the rank circle
|
||||||
* keeps respecting the following padding:
|
* keeps respecting the padding.
|
||||||
*
|
*
|
||||||
* width > RANK_CARD_DEFAULT_WIDTH: The default right padding of 70 px will be used.
|
* width > 450: The default left padding of 50 px will be used.
|
||||||
* width < RANK_CARD_DEFAULT_WIDTH: The left and right padding will be enlarged
|
* width < 450: The left and right padding will shrink equally.
|
||||||
* equally from a certain minimum at RANK_CARD_MIN_WIDTH.
|
|
||||||
*
|
*
|
||||||
* @returns {number} - Rank circle translation value.
|
* @returns {number} - Rank circle translation value.
|
||||||
*/
|
*/
|
||||||
const calculateRankXTranslation = () => {
|
const calculateRankXTranslation = () => {
|
||||||
const minXTranslation = RANK_CARD_MIN_WIDTH + iconWidth - 70;
|
if (width < 450) {
|
||||||
if (width > RANK_CARD_DEFAULT_WIDTH) {
|
return width - 95 + (45 * (450 - 340)) / 110;
|
||||||
const xMaxExpansion = minXTranslation + (450 - minCardWidth) / 2;
|
|
||||||
return xMaxExpansion + width - RANK_CARD_DEFAULT_WIDTH;
|
|
||||||
} else {
|
} else {
|
||||||
return minXTranslation + (width - minCardWidth) / 2;
|
return width - 95;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -297,7 +273,15 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
<circle class="rank-circle-rim" cx="-10" cy="8" r="40" />
|
||||||
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
<circle class="rank-circle" cx="-10" cy="8" r="40" />
|
||||||
<g class="rank-text">
|
<g class="rank-text">
|
||||||
${rankIcon(rank_icon, rank?.level)}
|
<text
|
||||||
|
x="-5"
|
||||||
|
y="3"
|
||||||
|
alignment-baseline="central"
|
||||||
|
dominant-baseline="central"
|
||||||
|
text-anchor="middle"
|
||||||
|
>
|
||||||
|
${rank.level}
|
||||||
|
</text>
|
||||||
</g>
|
</g>
|
||||||
</g>`;
|
</g>`;
|
||||||
|
|
||||||
|
|||||||
+86
-461
@@ -13,7 +13,7 @@ import {
|
|||||||
import { langCardLocales } from "../translations.js";
|
import { langCardLocales } from "../translations.js";
|
||||||
|
|
||||||
const DEFAULT_CARD_WIDTH = 300;
|
const DEFAULT_CARD_WIDTH = 300;
|
||||||
const MIN_CARD_WIDTH = 280;
|
const MIN_CARD_WIDTH = 230;
|
||||||
const DEFAULT_LANGS_COUNT = 5;
|
const DEFAULT_LANGS_COUNT = 5;
|
||||||
const DEFAULT_LANG_COLOR = "#858585";
|
const DEFAULT_LANG_COLOR = "#858585";
|
||||||
const CARD_PADDING = 25;
|
const CARD_PADDING = 25;
|
||||||
@@ -36,206 +36,66 @@ const getLongestLang = (arr) =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert degrees to radians.
|
* Creates a node to display usage of a programming language in percentage
|
||||||
|
* using text and a horizontal progress bar.
|
||||||
*
|
*
|
||||||
* @param {number} angleInDegrees Angle in degrees.
|
* @param {object[]} props Function properties.
|
||||||
* @returns Angle in radians.
|
|
||||||
*/
|
|
||||||
const degreesToRadians = (angleInDegrees) => angleInDegrees * (Math.PI / 180.0);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert radians to degrees.
|
|
||||||
*
|
|
||||||
* @param {number} angleInRadians Angle in radians.
|
|
||||||
* @returns Angle in degrees.
|
|
||||||
*/
|
|
||||||
const radiansToDegrees = (angleInRadians) => angleInRadians / (Math.PI / 180.0);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert polar coordinates to cartesian coordinates.
|
|
||||||
*
|
|
||||||
* @param {number} centerX Center x coordinate.
|
|
||||||
* @param {number} centerY Center y coordinate.
|
|
||||||
* @param {number} radius Radius of the circle.
|
|
||||||
* @param {number} angleInDegrees Angle in degrees.
|
|
||||||
* @returns {{x: number, y: number}} Cartesian coordinates.
|
|
||||||
*/
|
|
||||||
const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => {
|
|
||||||
const rads = degreesToRadians(angleInDegrees);
|
|
||||||
return {
|
|
||||||
x: centerX + radius * Math.cos(rads),
|
|
||||||
y: centerY + radius * Math.sin(rads),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert cartesian coordinates to polar coordinates.
|
|
||||||
*
|
|
||||||
* @param {number} centerX Center x coordinate.
|
|
||||||
* @param {number} centerY Center y coordinate.
|
|
||||||
* @param {number} x Point x coordinate.
|
|
||||||
* @param {number} y Point y coordinate.
|
|
||||||
* @returns {{radius: number, angleInDegrees: number}} Polar coordinates.
|
|
||||||
*/
|
|
||||||
const cartesianToPolar = (centerX, centerY, x, y) => {
|
|
||||||
const radius = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2));
|
|
||||||
let angleInDegrees = radiansToDegrees(Math.atan2(y - centerY, x - centerX));
|
|
||||||
if (angleInDegrees < 0) angleInDegrees += 360;
|
|
||||||
return { radius, angleInDegrees };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates height for the compact layout.
|
|
||||||
*
|
|
||||||
* @param {number} totalLangs Total number of languages.
|
|
||||||
* @returns {number} Card height.
|
|
||||||
*/
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
const calculateNormalLayoutHeight = (totalLangs) => {
|
|
||||||
return 45 + (totalLangs + 1) * 40;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates height for the donut layout.
|
|
||||||
*
|
|
||||||
* @param {number} totalLangs Total number of languages.
|
|
||||||
* @returns {number} Card height.
|
|
||||||
*/
|
|
||||||
const calculateDonutLayoutHeight = (totalLangs) => {
|
|
||||||
return 215 + Math.max(totalLangs - 5, 0) * 32;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates height for the pie layout.
|
|
||||||
*
|
|
||||||
* @param {number} totalLangs Total number of languages.
|
|
||||||
* @returns {number} Card height.
|
|
||||||
*/
|
|
||||||
const calculatePieLayoutHeight = (totalLangs) => {
|
|
||||||
return 300 + Math.round(totalLangs / 2) * 25;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the center translation needed to keep the donut chart centred.
|
|
||||||
* @param {number} totalLangs Total number of languages.
|
|
||||||
* @returns {number} Donut center translation.
|
|
||||||
*/
|
|
||||||
const donutCenterTranslation = (totalLangs) => {
|
|
||||||
return -45 + Math.max(totalLangs - 5, 0) * 16;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trim top languages to lang_count while also hiding certain languages.
|
|
||||||
*
|
|
||||||
* @param {Record<string, Lang>} topLangs Top languages.
|
|
||||||
* @param {string[]} hide Languages to hide.
|
|
||||||
* @param {string} langs_count Number of languages to show.
|
|
||||||
* @returns {{topLangs: Record<string, Lang>, totalSize: number}} Trimmed top languages and total size.
|
|
||||||
*/
|
|
||||||
const trimTopLanguages = (topLangs, hide, langs_count) => {
|
|
||||||
let langs = Object.values(topLangs);
|
|
||||||
let langsToHide = {};
|
|
||||||
let langsCount = clampValue(parseInt(langs_count), 1, 10);
|
|
||||||
|
|
||||||
// populate langsToHide map for quick lookup
|
|
||||||
// while filtering out
|
|
||||||
if (hide) {
|
|
||||||
hide.forEach((langName) => {
|
|
||||||
langsToHide[lowercaseTrim(langName)] = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter out languages to be hidden
|
|
||||||
langs = langs
|
|
||||||
.sort((a, b) => b.size - a.size)
|
|
||||||
.filter((lang) => {
|
|
||||||
return !langsToHide[lowercaseTrim(lang.name)];
|
|
||||||
})
|
|
||||||
.slice(0, langsCount);
|
|
||||||
|
|
||||||
const totalLanguageSize = langs.reduce((acc, curr) => acc + curr.size, 0);
|
|
||||||
|
|
||||||
return { langs, totalLanguageSize };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create progress bar text item for a programming language.
|
|
||||||
*
|
|
||||||
* @param {object} props Function properties.
|
|
||||||
* @param {number} props.width The card width
|
* @param {number} props.width The card width
|
||||||
* @param {string} props.color Color of the programming language.
|
|
||||||
* @param {string} props.name Name of the programming language.
|
* @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.
|
* @param {string} props.progress Usage of the programming language in percentage.
|
||||||
* @param {number} props.index Index of the programming language.
|
|
||||||
* @returns {string} Programming language SVG node.
|
* @returns {string} Programming language SVG node.
|
||||||
*/
|
*/
|
||||||
const createProgressTextNode = ({ width, color, name, progress, index }) => {
|
const createProgressTextNode = ({ width, color, name, progress }) => {
|
||||||
const staggerDelay = (index + 3) * 150;
|
|
||||||
const paddingRight = 95;
|
const paddingRight = 95;
|
||||||
const progressTextX = width - paddingRight + 10;
|
const progressTextX = width - paddingRight + 10;
|
||||||
const progressWidth = width - paddingRight;
|
const progressWidth = width - paddingRight;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<g class="stagger" style="animation-delay: ${staggerDelay}ms">
|
<text data-testid="lang-name" x="2" y="15" class="lang-name">${name}</text>
|
||||||
<text data-testid="lang-name" x="2" y="15" class="lang-name">${name}</text>
|
<text x="${progressTextX}" y="34" class="lang-name">${progress}%</text>
|
||||||
<text x="${progressTextX}" y="34" class="lang-name">${progress}%</text>
|
${createProgressNode({
|
||||||
${createProgressNode({
|
x: 0,
|
||||||
x: 0,
|
y: 25,
|
||||||
y: 25,
|
color,
|
||||||
color,
|
width: progressWidth,
|
||||||
width: progressWidth,
|
progress,
|
||||||
progress,
|
progressBarBackgroundColor: "#ddd",
|
||||||
progressBarBackgroundColor: "#ddd",
|
})}
|
||||||
delay: staggerDelay + 300,
|
|
||||||
})}
|
|
||||||
</g>
|
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates compact text item for a programming language.
|
* Creates a text only node to display usage of a programming language in percentage.
|
||||||
*
|
*
|
||||||
* @param {object} props Function properties.
|
* @param {object[]} props Function properties.
|
||||||
* @param {Lang} props.lang Programming language object.
|
* @param {Lang} props.lang Programming language object.
|
||||||
* @param {number} props.totalSize Total size of all languages.
|
* @param {number} props.totalSize Total size of all languages.
|
||||||
* @param {boolean} props.hideProgress Whether to hide percentage.
|
|
||||||
* @param {number} props.index Index of the programming language.
|
|
||||||
* @returns {string} Compact layout programming language SVG node.
|
* @returns {string} Compact layout programming language SVG node.
|
||||||
*/
|
*/
|
||||||
const createCompactLangNode = ({ lang, totalSize, hideProgress, index }) => {
|
const createCompactLangNode = ({ lang, totalSize }) => {
|
||||||
const percentage = ((lang.size / totalSize) * 100).toFixed(2);
|
const percentage = ((lang.size / totalSize) * 100).toFixed(2);
|
||||||
const staggerDelay = (index + 3) * 150;
|
|
||||||
const color = lang.color || "#858585";
|
const color = lang.color || "#858585";
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<g class="stagger" style="animation-delay: ${staggerDelay}ms">
|
<g>
|
||||||
<circle cx="5" cy="6" r="5" fill="${color}" />
|
<circle cx="5" cy="6" r="5" fill="${color}" />
|
||||||
<text data-testid="lang-name" x="15" y="10" class='lang-name'>
|
<text data-testid="lang-name" x="15" y="10" class='lang-name'>
|
||||||
${lang.name} ${hideProgress ? "" : percentage + "%"}
|
${lang.name} ${percentage}%
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create compact languages text items for all programming languages.
|
* Creates compact layout of text only language nodes.
|
||||||
*
|
*
|
||||||
* @param {object} props Function properties.
|
* @param {object[]} props Function properties.
|
||||||
* @param {Lang[]} props.langs Array of programming languages.
|
* @param {Lang[]} props.langs Array of programming languages.
|
||||||
* @param {number} props.totalSize Total size of all languages.
|
* @param {number} props.totalSize Total size of all languages.
|
||||||
* @param {boolean} props.hideProgress Whether to hide percentage.
|
|
||||||
* @returns {string} Programming languages SVG node.
|
* @returns {string} Programming languages SVG node.
|
||||||
*/
|
*/
|
||||||
const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => {
|
const createLanguageTextNode = ({ langs, totalSize }) => {
|
||||||
const longestLang = getLongestLang(langs);
|
const longestLang = getLongestLang(langs);
|
||||||
const chunked = chunkArray(langs, langs.length / 2);
|
const chunked = chunkArray(langs, langs.length / 2);
|
||||||
const layouts = chunked.map((array) => {
|
const layouts = chunked.map((array) => {
|
||||||
@@ -244,7 +104,7 @@ const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => {
|
|||||||
createCompactLangNode({
|
createCompactLangNode({
|
||||||
lang,
|
lang,
|
||||||
totalSize,
|
totalSize,
|
||||||
hideProgress,
|
// @ts-ignore
|
||||||
index,
|
index,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -265,30 +125,7 @@ const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create donut languages text items for all programming languages.
|
* Renders layout to display user's most frequently used programming languages.
|
||||||
*
|
|
||||||
* @param {object} props Function properties.
|
|
||||||
* @param {Lang[]} props.langs Array of programming languages.
|
|
||||||
* @param {number} props.totalSize Total size of all languages.
|
|
||||||
* @returns {string} Donut layout programming language SVG node.
|
|
||||||
*/
|
|
||||||
const createDonutLanguagesNode = ({ langs, totalSize }) => {
|
|
||||||
return flexLayout({
|
|
||||||
items: langs.map((lang, index) => {
|
|
||||||
return createCompactLangNode({
|
|
||||||
lang,
|
|
||||||
totalSize,
|
|
||||||
hideProgress: false,
|
|
||||||
index,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
gap: 32,
|
|
||||||
direction: "column",
|
|
||||||
}).join("");
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the default language card layout.
|
|
||||||
*
|
*
|
||||||
* @param {Lang[]} langs Array of programming languages.
|
* @param {Lang[]} langs Array of programming languages.
|
||||||
* @param {number} width Card width.
|
* @param {number} width Card width.
|
||||||
@@ -297,13 +134,12 @@ const createDonutLanguagesNode = ({ langs, totalSize }) => {
|
|||||||
*/
|
*/
|
||||||
const renderNormalLayout = (langs, width, totalLanguageSize) => {
|
const renderNormalLayout = (langs, width, totalLanguageSize) => {
|
||||||
return flexLayout({
|
return flexLayout({
|
||||||
items: langs.map((lang, index) => {
|
items: langs.map((lang) => {
|
||||||
return createProgressTextNode({
|
return createProgressTextNode({
|
||||||
width,
|
width: width,
|
||||||
name: lang.name,
|
name: lang.name,
|
||||||
color: lang.color || DEFAULT_LANG_COLOR,
|
color: lang.color || DEFAULT_LANG_COLOR,
|
||||||
progress: ((lang.size / totalLanguageSize) * 100).toFixed(2),
|
progress: ((lang.size / totalLanguageSize) * 100).toFixed(2),
|
||||||
index,
|
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
gap: 40,
|
gap: 40,
|
||||||
@@ -312,15 +148,14 @@ const renderNormalLayout = (langs, width, totalLanguageSize) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the compact language card layout.
|
* Renders compact layout to display user's most frequently used programming languages.
|
||||||
*
|
*
|
||||||
* @param {Lang[]} langs Array of programming languages.
|
* @param {Lang[]} langs Array of programming languages.
|
||||||
* @param {number} width Card width.
|
* @param {number} width Card width.
|
||||||
* @param {number} totalLanguageSize Total size of all languages.
|
* @param {number} totalLanguageSize Total size of all languages.
|
||||||
* @param {boolean} hideProgress Whether to hide progress bar.
|
|
||||||
* @returns {string} Compact layout card SVG object.
|
* @returns {string} Compact layout card SVG object.
|
||||||
*/
|
*/
|
||||||
const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => {
|
const renderCompactLayout = (langs, width, totalLanguageSize) => {
|
||||||
const paddingRight = 50;
|
const paddingRight = 50;
|
||||||
const offsetWidth = width - paddingRight;
|
const offsetWidth = width - paddingRight;
|
||||||
// progressOffset holds the previous language's width and used to offset the next language
|
// progressOffset holds the previous language's width and used to offset the next language
|
||||||
@@ -351,221 +186,75 @@ const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => {
|
|||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
return `
|
return `
|
||||||
${
|
<mask id="rect-mask">
|
||||||
!hideProgress
|
<rect x="0" y="0" width="${offsetWidth}" height="8" fill="white" rx="5" />
|
||||||
? `
|
|
||||||
<mask id="rect-mask">
|
|
||||||
<rect x="0" y="0" width="${offsetWidth}" height="8" fill="white" rx="5"/>
|
|
||||||
</mask>
|
</mask>
|
||||||
${compactProgressBar}
|
${compactProgressBar}
|
||||||
`
|
|
||||||
: ""
|
<g transform="translate(0, 25)">
|
||||||
}
|
|
||||||
<g transform="translate(0, ${hideProgress ? "0" : "25"})">
|
|
||||||
${createLanguageTextNode({
|
${createLanguageTextNode({
|
||||||
langs,
|
langs,
|
||||||
totalSize: totalLanguageSize,
|
totalSize: totalLanguageSize,
|
||||||
hideProgress: hideProgress,
|
|
||||||
})}
|
})}
|
||||||
</g>
|
</g>
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders pie layout to display user's most frequently used programming languages.
|
* Calculates height for the compact layout.
|
||||||
*
|
*
|
||||||
* @param {Lang[]} langs Array of programming languages.
|
* @param {number} totalLangs Total number of languages.
|
||||||
* @param {number} totalLanguageSize Total size of all languages.
|
* @returns {number} Card height.
|
||||||
* @returns {string} Compact layout card SVG object.
|
|
||||||
*/
|
*/
|
||||||
const renderPieLayout = (langs, totalLanguageSize) => {
|
const calculateCompactLayoutHeight = (totalLangs) => {
|
||||||
// Pie chart radius and center coordinates
|
return 90 + Math.round(totalLangs / 2) * 25;
|
||||||
const radius = 90;
|
};
|
||||||
const centerX = 150;
|
|
||||||
const centerY = 100;
|
|
||||||
|
|
||||||
// Start angle for the pie chart parts
|
/**
|
||||||
let startAngle = 0;
|
* Calculates height for the normal layout.
|
||||||
|
*
|
||||||
|
* @param {number} totalLangs Total number of languages.
|
||||||
|
* @returns {number} Card height.
|
||||||
|
*/
|
||||||
|
const calculateNormalLayoutHeight = (totalLangs) => {
|
||||||
|
return 45 + (totalLangs + 1) * 40;
|
||||||
|
};
|
||||||
|
|
||||||
// Start delay coefficient for the pie chart parts
|
/**
|
||||||
let startDelayCoefficient = 1;
|
* 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.
|
||||||
|
*/
|
||||||
|
const useLanguages = (topLangs, hide, langs_count) => {
|
||||||
|
let langs = Object.values(topLangs);
|
||||||
|
let langsToHide = {};
|
||||||
|
let langsCount = clampValue(parseInt(langs_count), 1, 10);
|
||||||
|
|
||||||
// SVG paths
|
// populate langsToHide map for quick lookup
|
||||||
const paths = [];
|
// while filtering out
|
||||||
|
if (hide) {
|
||||||
// Generate each pie chart part
|
hide.forEach((langName) => {
|
||||||
for (const lang of langs) {
|
langsToHide[lowercaseTrim(langName)] = true;
|
||||||
if (langs.length === 1) {
|
});
|
||||||
paths.push(`
|
|
||||||
<circle
|
|
||||||
cx="${centerX}"
|
|
||||||
cy="${centerY}"
|
|
||||||
r="${radius}"
|
|
||||||
stroke="none"
|
|
||||||
fill="${lang.color}"
|
|
||||||
data-testid="lang-pie"
|
|
||||||
size="100"
|
|
||||||
/>
|
|
||||||
`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const langSizePart = lang.size / totalLanguageSize;
|
|
||||||
const percentage = langSizePart * 100;
|
|
||||||
// Calculate the angle for the current part
|
|
||||||
const angle = langSizePart * 360;
|
|
||||||
|
|
||||||
// Calculate the end angle
|
|
||||||
const endAngle = startAngle + angle;
|
|
||||||
|
|
||||||
// Calculate the coordinates of the start and end points of the arc
|
|
||||||
const startPoint = polarToCartesian(centerX, centerY, radius, startAngle);
|
|
||||||
const endPoint = polarToCartesian(centerX, centerY, radius, endAngle);
|
|
||||||
|
|
||||||
// Determine the large arc flag based on the angle
|
|
||||||
const largeArcFlag = angle > 180 ? 1 : 0;
|
|
||||||
|
|
||||||
// Calculate delay
|
|
||||||
const delay = startDelayCoefficient * 100;
|
|
||||||
|
|
||||||
// SVG arc markup
|
|
||||||
paths.push(`
|
|
||||||
<g class="stagger" style="animation-delay: ${delay}ms">
|
|
||||||
<path
|
|
||||||
data-testid="lang-pie"
|
|
||||||
size="${percentage}"
|
|
||||||
d="M ${centerX} ${centerY} L ${startPoint.x} ${startPoint.y} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${endPoint.x} ${endPoint.y} Z"
|
|
||||||
fill="${lang.color}"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Update the start angle for the next part
|
|
||||||
startAngle = endAngle;
|
|
||||||
// Update the start delay coefficient for the next part
|
|
||||||
startDelayCoefficient += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return `
|
// filter out languages to be hidden
|
||||||
<svg data-testid="lang-items">
|
langs = langs
|
||||||
<g transform="translate(0, 0)">
|
.sort((a, b) => b.size - a.size)
|
||||||
<svg data-testid="pie">
|
.filter((lang) => {
|
||||||
${paths.join("")}
|
return !langsToHide[lowercaseTrim(lang.name)];
|
||||||
</svg>
|
})
|
||||||
</g>
|
.slice(0, langsCount);
|
||||||
<g transform="translate(0, 220)">
|
|
||||||
<svg data-testid="lang-names" x="${CARD_PADDING}">
|
const totalLanguageSize = langs.reduce((acc, curr) => acc + curr.size, 0);
|
||||||
${createLanguageTextNode({
|
|
||||||
langs,
|
return { langs, totalLanguageSize };
|
||||||
totalSize: totalLanguageSize,
|
|
||||||
hideProgress: false,
|
|
||||||
})}
|
|
||||||
</svg>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the SVG paths for the language donut chart.
|
* Renders card to display user's most frequently used programming languages.
|
||||||
*
|
|
||||||
* @param {number} cx Donut center x-position.
|
|
||||||
* @param {number} cy Donut center y-position.
|
|
||||||
* @param {number} radius Donut arc Radius.
|
|
||||||
* @param {number[]} percentages Array with donut section percentages.
|
|
||||||
* @returns {{d: string, percent: number}[]} Array of svg path elements
|
|
||||||
*/
|
|
||||||
const createDonutPaths = (cx, cy, radius, percentages) => {
|
|
||||||
const paths = [];
|
|
||||||
let startAngle = 0;
|
|
||||||
let endAngle = 0;
|
|
||||||
|
|
||||||
const totalPercent = percentages.reduce((acc, curr) => acc + curr, 0);
|
|
||||||
for (let i = 0; i < percentages.length; i++) {
|
|
||||||
const tmpPath = {};
|
|
||||||
|
|
||||||
let percent = parseFloat(
|
|
||||||
((percentages[i] / totalPercent) * 100).toFixed(2),
|
|
||||||
);
|
|
||||||
|
|
||||||
endAngle = 3.6 * percent + startAngle;
|
|
||||||
const startPoint = polarToCartesian(cx, cy, radius, endAngle - 90); // rotate donut 90 degrees counter-clockwise.
|
|
||||||
const endPoint = polarToCartesian(cx, cy, radius, startAngle - 90); // rotate donut 90 degrees counter-clockwise.
|
|
||||||
const largeArc = endAngle - startAngle <= 180 ? 0 : 1;
|
|
||||||
|
|
||||||
tmpPath.percent = percent;
|
|
||||||
tmpPath.d = `M ${startPoint.x} ${startPoint.y} A ${radius} ${radius} 0 ${largeArc} 0 ${endPoint.x} ${endPoint.y}`;
|
|
||||||
|
|
||||||
paths.push(tmpPath);
|
|
||||||
startAngle = endAngle;
|
|
||||||
}
|
|
||||||
|
|
||||||
return paths;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the donut language card layout.
|
|
||||||
*
|
|
||||||
* @param {Lang[]} langs Array of programming languages.
|
|
||||||
* @param {number} width Card width.
|
|
||||||
* @param {number} totalLanguageSize Total size of all languages.
|
|
||||||
* @returns {string} Donut layout card SVG object.
|
|
||||||
*/
|
|
||||||
const renderDonutLayout = (langs, width, totalLanguageSize) => {
|
|
||||||
const centerX = width / 3;
|
|
||||||
const centerY = width / 3;
|
|
||||||
const radius = centerX - 60;
|
|
||||||
const strokeWidth = 12;
|
|
||||||
|
|
||||||
const colors = langs.map((lang) => lang.color);
|
|
||||||
const langsPercents = langs.map((lang) =>
|
|
||||||
parseFloat(((lang.size / totalLanguageSize) * 100).toFixed(2)),
|
|
||||||
);
|
|
||||||
|
|
||||||
const langPaths = createDonutPaths(centerX, centerY, radius, langsPercents);
|
|
||||||
|
|
||||||
const donutPaths =
|
|
||||||
langs.length === 1
|
|
||||||
? `<circle cx="${centerX}" cy="${centerY}" r="${radius}" stroke="${colors[0]}" fill="none" stroke-width="${strokeWidth}" data-testid="lang-donut" size="100"/>`
|
|
||||||
: langPaths
|
|
||||||
.map((section, index) => {
|
|
||||||
const staggerDelay = (index + 3) * 100;
|
|
||||||
const delay = staggerDelay + 300;
|
|
||||||
|
|
||||||
const output = `
|
|
||||||
<g class="stagger" style="animation-delay: ${delay}ms">
|
|
||||||
<path
|
|
||||||
data-testid="lang-donut"
|
|
||||||
size="${section.percent}"
|
|
||||||
d="${section.d}"
|
|
||||||
stroke="${colors[index]}"
|
|
||||||
fill="none"
|
|
||||||
stroke-width="${strokeWidth}">
|
|
||||||
</path>
|
|
||||||
</g>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return output;
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
const donut = `<svg width="${width}" height="${width}">${donutPaths}</svg>`;
|
|
||||||
|
|
||||||
return `
|
|
||||||
<g transform="translate(0, 0)">
|
|
||||||
<g transform="translate(0, 0)">
|
|
||||||
${createDonutLanguagesNode({ langs, totalSize: totalLanguageSize })}
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<g transform="translate(125, ${donutCenterTranslation(langs.length)})">
|
|
||||||
${donut}
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders card that display user's most frequently used programming languages.
|
|
||||||
*
|
*
|
||||||
* @param {import('../fetchers/types').TopLangData} topLangs 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.
|
* @param {Partial<import("./types").TopLangOptions>} options Card options.
|
||||||
@@ -580,7 +269,6 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
|||||||
text_color,
|
text_color,
|
||||||
bg_color,
|
bg_color,
|
||||||
hide,
|
hide,
|
||||||
hide_progress,
|
|
||||||
theme,
|
theme,
|
||||||
layout,
|
layout,
|
||||||
custom_title,
|
custom_title,
|
||||||
@@ -588,7 +276,6 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
|||||||
langs_count = DEFAULT_LANGS_COUNT,
|
langs_count = DEFAULT_LANGS_COUNT,
|
||||||
border_radius,
|
border_radius,
|
||||||
border_color,
|
border_color,
|
||||||
disable_animations,
|
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const i18n = new I18n({
|
const i18n = new I18n({
|
||||||
@@ -596,7 +283,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
|||||||
translations: langCardLocales,
|
translations: langCardLocales,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { langs, totalLanguageSize } = trimTopLanguages(
|
const { langs, totalLanguageSize } = useLanguages(
|
||||||
topLangs,
|
topLangs,
|
||||||
hide,
|
hide,
|
||||||
String(langs_count),
|
String(langs_count),
|
||||||
@@ -610,23 +297,11 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
|||||||
let height = calculateNormalLayoutHeight(langs.length);
|
let height = calculateNormalLayoutHeight(langs.length);
|
||||||
|
|
||||||
let finalLayout = "";
|
let finalLayout = "";
|
||||||
if (layout === "pie") {
|
if (layout === "compact") {
|
||||||
height = calculatePieLayoutHeight(langs.length);
|
|
||||||
finalLayout = renderPieLayout(langs, totalLanguageSize);
|
|
||||||
} else if (layout === "compact" || hide_progress == true) {
|
|
||||||
height =
|
|
||||||
calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0);
|
|
||||||
|
|
||||||
finalLayout = renderCompactLayout(
|
|
||||||
langs,
|
|
||||||
width,
|
|
||||||
totalLanguageSize,
|
|
||||||
hide_progress,
|
|
||||||
);
|
|
||||||
} else if (layout?.toLowerCase() === "donut") {
|
|
||||||
height = calculateDonutLayoutHeight(langs.length);
|
|
||||||
width = width + 50; // padding
|
width = width + 50; // padding
|
||||||
finalLayout = renderDonutLayout(langs, width, totalLanguageSize);
|
height = calculateCompactLayoutHeight(langs.length);
|
||||||
|
|
||||||
|
finalLayout = renderCompactLayout(langs, width, totalLanguageSize);
|
||||||
} else {
|
} else {
|
||||||
finalLayout = renderNormalLayout(langs, width, totalLanguageSize);
|
finalLayout = renderNormalLayout(langs, width, totalLanguageSize);
|
||||||
}
|
}
|
||||||
@@ -649,49 +324,13 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
|||||||
colors,
|
colors,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (disable_animations) card.disableAnimations();
|
card.disableAnimations();
|
||||||
|
|
||||||
card.setHideBorder(hide_border);
|
card.setHideBorder(hide_border);
|
||||||
card.setHideTitle(hide_title);
|
card.setHideTitle(hide_title);
|
||||||
card.setCSS(
|
card.setCSS(
|
||||||
`
|
`.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} }`,
|
||||||
@keyframes slideInAnimation {
|
|
||||||
from {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
width: calc(100%-100px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes growWidthAnimation {
|
|
||||||
from {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.lang-name {
|
|
||||||
font: 400 11px "Segoe UI", Ubuntu, Sans-Serif;
|
|
||||||
fill: ${colors.textColor};
|
|
||||||
}
|
|
||||||
.stagger {
|
|
||||||
opacity: 0;
|
|
||||||
animation: fadeInAnimation 0.3s ease-in-out forwards;
|
|
||||||
}
|
|
||||||
#rect-mask rect{
|
|
||||||
animation: slideInAnimation 1s ease-in-out forwards;
|
|
||||||
}
|
|
||||||
.lang-progress{
|
|
||||||
animation: growWidthAnimation 0.6s ease-in-out forwards;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (layout === "pie") {
|
|
||||||
return card.render(finalLayout);
|
|
||||||
}
|
|
||||||
|
|
||||||
return card.render(`
|
return card.render(`
|
||||||
<svg data-testid="lang-items" x="${CARD_PADDING}">
|
<svg data-testid="lang-items" x="${CARD_PADDING}">
|
||||||
${finalLayout}
|
${finalLayout}
|
||||||
@@ -699,18 +338,4 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
|||||||
`);
|
`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export { renderTopLanguages, MIN_CARD_WIDTH };
|
||||||
getLongestLang,
|
|
||||||
degreesToRadians,
|
|
||||||
radiansToDegrees,
|
|
||||||
polarToCartesian,
|
|
||||||
cartesianToPolar,
|
|
||||||
calculateCompactLayoutHeight,
|
|
||||||
calculateNormalLayoutHeight,
|
|
||||||
calculateDonutLayoutHeight,
|
|
||||||
calculatePieLayoutHeight,
|
|
||||||
donutCenterTranslation,
|
|
||||||
trimTopLanguages,
|
|
||||||
renderTopLanguages,
|
|
||||||
MIN_CARD_WIDTH,
|
|
||||||
};
|
|
||||||
|
|||||||
Vendored
+1
-8
@@ -1,5 +1,4 @@
|
|||||||
type ThemeNames = keyof typeof import("../../themes/index.js");
|
type ThemeNames = keyof typeof import("../../themes/index.js");
|
||||||
type RankIcon = "default" | "github";
|
|
||||||
|
|
||||||
export type CommonOptions = {
|
export type CommonOptions = {
|
||||||
title_color: string;
|
title_color: string;
|
||||||
@@ -23,10 +22,6 @@ export type StatCardOptions = CommonOptions & {
|
|||||||
line_height: number | string;
|
line_height: number | string;
|
||||||
custom_title: string;
|
custom_title: string;
|
||||||
disable_animations: boolean;
|
disable_animations: boolean;
|
||||||
number_format: string;
|
|
||||||
ring_color: string;
|
|
||||||
text_bold: boolean;
|
|
||||||
rank_icon: RankIcon;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RepoCardOptions = CommonOptions & {
|
export type RepoCardOptions = CommonOptions & {
|
||||||
@@ -39,11 +34,9 @@ export type TopLangOptions = CommonOptions & {
|
|||||||
hide_border: boolean;
|
hide_border: boolean;
|
||||||
card_width: number;
|
card_width: number;
|
||||||
hide: string[];
|
hide: string[];
|
||||||
layout: "compact" | "normal" | "donut" | "pie";
|
layout: "compact" | "normal";
|
||||||
custom_title: string;
|
custom_title: string;
|
||||||
langs_count: number;
|
langs_count: number;
|
||||||
disable_animations: boolean;
|
|
||||||
hide_progress: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type WakaTimeOptions = CommonOptions & {
|
type WakaTimeOptions = CommonOptions & {
|
||||||
|
|||||||
+34
-69
@@ -36,11 +36,11 @@ const noCodingActivityNode = ({ color, text }) => {
|
|||||||
/**
|
/**
|
||||||
* Create compact WakaTime layout.
|
* Create compact WakaTime layout.
|
||||||
*
|
*
|
||||||
* @param {Object} args The function arguments.
|
* @param {Object[]} args The function arguments.
|
||||||
* @param {import("../fetchers/types").WakaTimeLang} args.lang The languages array.
|
* @param {import("../fetchers/types").WakaTimeLang[]} languages The languages array.
|
||||||
* @param {number} args.totalSize The total size of the languages.
|
* @param {number} totalSize The total size of the languages.
|
||||||
* @param {number} args.x The x position of the language node.
|
* @param {number} x The x position of the language node.
|
||||||
* @param {number} args.y The y position of the language node.
|
* @param {number} y The y position of the language node.
|
||||||
*/
|
*/
|
||||||
const createCompactLangNode = ({ lang, totalSize, x, y }) => {
|
const createCompactLangNode = ({ lang, totalSize, x, y }) => {
|
||||||
const color = languageColors[lang.name] || "#858585";
|
const color = languageColors[lang.name] || "#858585";
|
||||||
@@ -58,11 +58,11 @@ const createCompactLangNode = ({ lang, totalSize, x, y }) => {
|
|||||||
/**
|
/**
|
||||||
* Create WakaTime language text node item.
|
* Create WakaTime language text node item.
|
||||||
*
|
*
|
||||||
* @param {Object} args The function arguments.
|
* @param {Object[]} args The function arguments.
|
||||||
* @param {import("../fetchers/types").WakaTimeLang[]} args.langs The language objects.
|
* @param {import("../fetchers/types").WakaTimeLang} lang The language object.
|
||||||
* @param {number} args.totalSize The total size of the languages.
|
* @param {number} totalSize The total size of the languages.
|
||||||
* @param {number} args.x The x position of the language node.
|
* @param {number} x The x position of the language node.
|
||||||
* @param {number} args.y The y position of the language node.
|
* @param {number} y The y position of the language node.
|
||||||
*/
|
*/
|
||||||
const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
|
const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
|
||||||
return langs.map((lang, index) => {
|
return langs.map((lang, index) => {
|
||||||
@@ -86,15 +86,14 @@ const createLanguageTextNode = ({ langs, totalSize, x, y }) => {
|
|||||||
/**
|
/**
|
||||||
* Create WakaTime text item.
|
* Create WakaTime text item.
|
||||||
*
|
*
|
||||||
* @param {Object} args The function arguments.
|
* @param {Object[]} args The function arguments.
|
||||||
* @param {string} args.id The id of the text node item.
|
* @param {string} id The id of the text node item.
|
||||||
* @param {string} args.label The label of the text node item.
|
* @param {string} label The label of the text node item.
|
||||||
* @param {string} args.value The value of the text node item.
|
* @param {string} value The value of the text node item.
|
||||||
* @param {number} args.index The index of the text node item.
|
* @param {number} index The index of the text node item.
|
||||||
* @param {string} args.percent Percentage of the text node item.
|
* @param {percent} percent Percentage of the text node item.
|
||||||
* @param {boolean} args.hideProgress Whether to hide the progress bar.
|
* @param {boolean} hideProgress Whether to hide the progress bar.
|
||||||
* @param {string} args.progressBarColor The color of the progress bar.
|
* @param {string} progressBarBackgroundColor The color of the progress bar background.
|
||||||
* @param {string} args.progressBarBackgroundColor The color of the progress bar background.
|
|
||||||
*/
|
*/
|
||||||
const createTextNode = ({
|
const createTextNode = ({
|
||||||
id,
|
id,
|
||||||
@@ -119,7 +118,6 @@ const createTextNode = ({
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
name: label,
|
name: label,
|
||||||
progressBarBackgroundColor,
|
progressBarBackgroundColor,
|
||||||
delay: staggerDelay + 300,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return `
|
return `
|
||||||
@@ -140,7 +138,7 @@ const createTextNode = ({
|
|||||||
* hiding languages.
|
* hiding languages.
|
||||||
*
|
*
|
||||||
* @param {import("../fetchers/types").WakaTimeLang[]} languages The languages array.
|
* @param {import("../fetchers/types").WakaTimeLang[]} languages The languages array.
|
||||||
* @return {void} The recalculated languages array.
|
* @return {import("../fetchers/types").WakaTimeLang[]} The recalculated languages array.
|
||||||
*/
|
*/
|
||||||
const recalculatePercentages = (languages) => {
|
const recalculatePercentages = (languages) => {
|
||||||
const totalSum = languages.reduce(
|
const totalSum = languages.reduce(
|
||||||
@@ -161,7 +159,7 @@ const recalculatePercentages = (languages) => {
|
|||||||
* @returns {string} WakaTime card SVG.
|
* @returns {string} WakaTime card SVG.
|
||||||
*/
|
*/
|
||||||
const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
||||||
let { languages = [] } = stats;
|
let { languages } = stats;
|
||||||
const {
|
const {
|
||||||
hide_title = false,
|
hide_title = false,
|
||||||
hide_border = false,
|
hide_border = false,
|
||||||
@@ -176,24 +174,20 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
custom_title,
|
custom_title,
|
||||||
locale,
|
locale,
|
||||||
layout,
|
layout,
|
||||||
langs_count = languages.length,
|
langs_count = languages ? languages.length : 0,
|
||||||
border_radius,
|
border_radius,
|
||||||
border_color,
|
border_color,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const shouldHideLangs = Array.isArray(hide) && hide.length > 0;
|
const shouldHideLangs = Array.isArray(hide) && hide.length > 0;
|
||||||
if (shouldHideLangs) {
|
if (shouldHideLangs && languages !== undefined) {
|
||||||
const languagesToHide = new Set(hide.map((lang) => lowercaseTrim(lang)));
|
const languagesToHide = new Set(hide.map((lang) => lowercaseTrim(lang)));
|
||||||
languages = languages.filter(
|
languages = languages.filter(
|
||||||
(lang) => !languagesToHide.has(lowercaseTrim(lang.name)),
|
(lang) => !languagesToHide.has(lowercaseTrim(lang.name)),
|
||||||
);
|
);
|
||||||
|
recalculatePercentages(languages);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since the percentages are sorted in descending order, we can just
|
|
||||||
// slice from the beginning without sorting.
|
|
||||||
languages = languages.slice(0, langs_count);
|
|
||||||
recalculatePercentages(languages);
|
|
||||||
|
|
||||||
const i18n = new I18n({
|
const i18n = new I18n({
|
||||||
locale,
|
locale,
|
||||||
translations: wakatimeCardLocales,
|
translations: wakatimeCardLocales,
|
||||||
@@ -215,8 +209,10 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const filteredLanguages = languages
|
const filteredLanguages = languages
|
||||||
.filter((language) => language.hours || language.minutes)
|
? languages
|
||||||
.slice(0, langsCount);
|
.filter((language) => language.hours || language.minutes)
|
||||||
|
.slice(0, langsCount)
|
||||||
|
: [];
|
||||||
|
|
||||||
// Calculate the card height depending on how many items there are
|
// Calculate the card height depending on how many items there are
|
||||||
// but if rank circle is visible clamp the minimum height to `150`
|
// but if rank circle is visible clamp the minimum height to `150`
|
||||||
@@ -268,30 +264,21 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
<rect x="25" y="0" width="${width - 50}" height="8" fill="white" rx="5" />
|
<rect x="25" y="0" width="${width - 50}" height="8" fill="white" rx="5" />
|
||||||
</mask>
|
</mask>
|
||||||
${compactProgressBar}
|
${compactProgressBar}
|
||||||
${
|
${createLanguageTextNode({
|
||||||
filteredLanguages.length
|
x: 0,
|
||||||
? createLanguageTextNode({
|
y: 25,
|
||||||
x: 0,
|
langs: filteredLanguages,
|
||||||
y: 25,
|
totalSize: 100,
|
||||||
langs: filteredLanguages,
|
}).join("")}
|
||||||
totalSize: 100,
|
|
||||||
}).join("")
|
|
||||||
: noCodingActivityNode({
|
|
||||||
// @ts-ignore
|
|
||||||
color: textColor,
|
|
||||||
text: i18n.t("wakatimecard.nocodingactivity"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
finalLayout = flexLayout({
|
finalLayout = flexLayout({
|
||||||
items: filteredLanguages.length
|
items: filteredLanguages.length
|
||||||
? filteredLanguages.map((language, index) => {
|
? filteredLanguages.map((language) => {
|
||||||
return createTextNode({
|
return createTextNode({
|
||||||
id: language.name,
|
id: language.name,
|
||||||
label: language.name,
|
label: language.name,
|
||||||
value: language.text,
|
value: language.text,
|
||||||
index: index,
|
|
||||||
percent: language.percent,
|
percent: language.percent,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
progressBarColor: titleColor,
|
progressBarColor: titleColor,
|
||||||
@@ -332,29 +319,7 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
|||||||
card.setCSS(
|
card.setCSS(
|
||||||
`
|
`
|
||||||
${cssStyles}
|
${cssStyles}
|
||||||
@keyframes slideInAnimation {
|
|
||||||
from {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
width: calc(100%-100px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes growWidthAnimation {
|
|
||||||
from {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
|
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
|
||||||
#rect-mask rect{
|
|
||||||
animation: slideInAnimation 1s ease-in-out forwards;
|
|
||||||
}
|
|
||||||
.lang-progress{
|
|
||||||
animation: growWidthAnimation 0.6s ease-in-out forwards;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,6 @@ class Card {
|
|||||||
* @param {string?=} args.customTitle Card custom title.
|
* @param {string?=} args.customTitle Card custom title.
|
||||||
* @param {string?=} args.defaultTitle Card default title.
|
* @param {string?=} args.defaultTitle Card default title.
|
||||||
* @param {string?=} args.titlePrefixIcon Card title prefix icon.
|
* @param {string?=} args.titlePrefixIcon Card title prefix icon.
|
||||||
* @param {object?=} args.colors Card colors arguments.
|
|
||||||
* @param {string} args.colors.titleColor Card title color.
|
|
||||||
* @param {string} args.colors.textColor Card text color.
|
|
||||||
* @param {string} args.colors.iconColor Card icon color.
|
|
||||||
* @param {string|Array} args.colors.bgColor Card background color.
|
|
||||||
* @param {string} args.colors.borderColor Card border color.
|
|
||||||
* @returns {Card} Card instance.
|
* @returns {Card} Card instance.
|
||||||
*/
|
*/
|
||||||
constructor({
|
constructor({
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { clampValue } from "./utils.js";
|
|||||||
* @param {string} createProgressNodeParams.color Progress color.
|
* @param {string} createProgressNodeParams.color Progress color.
|
||||||
* @param {string} createProgressNodeParams.progress Progress value.
|
* @param {string} createProgressNodeParams.progress Progress value.
|
||||||
* @param {string} createProgressNodeParams.progressBarBackgroundColor Progress bar bg color.
|
* @param {string} createProgressNodeParams.progressBarBackgroundColor Progress bar bg color.
|
||||||
* @param {number} createProgressNodeParams.delay Delay before animation starts.
|
|
||||||
* @returns {string} Progress node.
|
* @returns {string} Progress node.
|
||||||
*/
|
*/
|
||||||
const createProgressNode = ({
|
const createProgressNode = ({
|
||||||
@@ -20,22 +19,20 @@ const createProgressNode = ({
|
|||||||
color,
|
color,
|
||||||
progress,
|
progress,
|
||||||
progressBarBackgroundColor,
|
progressBarBackgroundColor,
|
||||||
delay,
|
|
||||||
}) => {
|
}) => {
|
||||||
const progressPercentage = clampValue(progress, 2, 100);
|
const progressPercentage = clampValue(progress, 2, 100);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<svg width="${width}" x="${x}" y="${y}">
|
<svg width="${width}" x="${x}" y="${y}">
|
||||||
<rect rx="5" ry="5" x="0" y="0" width="${width}" height="8" fill="${progressBarBackgroundColor}"></rect>
|
<rect rx="5" ry="5" x="0" y="0" width="${width}" height="8" fill="${progressBarBackgroundColor}"></rect>
|
||||||
<svg data-testid="lang-progress" width="${progressPercentage}%">
|
<rect
|
||||||
<rect
|
height="8"
|
||||||
height="8"
|
fill="${color}"
|
||||||
fill="${color}"
|
rx="5" ry="5" x="0" y="0"
|
||||||
rx="5" ry="5" x="0" y="0"
|
data-testid="lang-progress"
|
||||||
class="lang-progress"
|
width="${progressPercentage}%"
|
||||||
style="animation-delay: ${delay}ms;"
|
>
|
||||||
/>
|
</rect>
|
||||||
</svg>
|
|
||||||
</svg>
|
</svg>
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-24
@@ -8,28 +8,5 @@ const icons = {
|
|||||||
fork: `<path fill-rule="evenodd" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25v-.878a2.25 2.25 0 10-1.5 0v.878a.75.75 0 01-.75.75h-4.5A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"></path>`,
|
fork: `<path fill-rule="evenodd" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25v-.878a2.25 2.25 0 10-1.5 0v.878a.75.75 0 01-.75.75h-4.5A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"></path>`,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export { icons };
|
||||||
* Get rank icon
|
|
||||||
*
|
|
||||||
* @returns {string} - The SVG code of the rank icon
|
|
||||||
*/
|
|
||||||
const rankIcon = (rankIcon, rankLevel) => {
|
|
||||||
switch (rankIcon) {
|
|
||||||
case "github":
|
|
||||||
return `
|
|
||||||
<svg x="-38" y="-30" height="66" width="66" aria-hidden="true" viewBox="0 0 16 16" version="1.1" data-view-component="true" data-testid="github-rank-icon">
|
|
||||||
<path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"></path>
|
|
||||||
</svg>
|
|
||||||
`;
|
|
||||||
case "default":
|
|
||||||
default:
|
|
||||||
return `
|
|
||||||
<text x="-5" y="3" alignment-baseline="central" dominant-baseline="central" text-anchor="middle" data-testid="level-rank-icon">
|
|
||||||
${rankLevel}
|
|
||||||
</text>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export { icons, rankIcon };
|
|
||||||
export default icons;
|
export default icons;
|
||||||
|
|||||||
@@ -79,7 +79,6 @@
|
|||||||
"Ceylon": "#dfa535",
|
"Ceylon": "#dfa535",
|
||||||
"Chapel": "#8dc63f",
|
"Chapel": "#8dc63f",
|
||||||
"ChucK": "#3f8000",
|
"ChucK": "#3f8000",
|
||||||
"Circom": "#707575",
|
|
||||||
"Cirru": "#ccccff",
|
"Cirru": "#ccccff",
|
||||||
"Clarion": "#db901e",
|
"Clarion": "#db901e",
|
||||||
"Clarity": "#5546ff",
|
"Clarity": "#5546ff",
|
||||||
@@ -103,7 +102,6 @@
|
|||||||
"Csound Score": "#1a1a1a",
|
"Csound Score": "#1a1a1a",
|
||||||
"Cuda": "#3A4E3A",
|
"Cuda": "#3A4E3A",
|
||||||
"Curry": "#531242",
|
"Curry": "#531242",
|
||||||
"Cypher": "#34c0eb",
|
|
||||||
"Cython": "#fedf5b",
|
"Cython": "#fedf5b",
|
||||||
"D": "#ba595e",
|
"D": "#ba595e",
|
||||||
"DM": "#447265",
|
"DM": "#447265",
|
||||||
@@ -117,7 +115,6 @@
|
|||||||
"DirectX 3D File": "#aace60",
|
"DirectX 3D File": "#aace60",
|
||||||
"Dockerfile": "#384d54",
|
"Dockerfile": "#384d54",
|
||||||
"Dogescript": "#cca760",
|
"Dogescript": "#cca760",
|
||||||
"Dotenv": "#e5d559",
|
|
||||||
"Dylan": "#6c616e",
|
"Dylan": "#6c616e",
|
||||||
"E": "#ccce35",
|
"E": "#ccce35",
|
||||||
"ECL": "#8a1267",
|
"ECL": "#8a1267",
|
||||||
@@ -127,13 +124,11 @@
|
|||||||
"Earthly": "#2af0ff",
|
"Earthly": "#2af0ff",
|
||||||
"Easybuild": "#069406",
|
"Easybuild": "#069406",
|
||||||
"Ecere Projects": "#913960",
|
"Ecere Projects": "#913960",
|
||||||
"Ecmarkup": "#eb8131",
|
|
||||||
"EditorConfig": "#fff1f2",
|
"EditorConfig": "#fff1f2",
|
||||||
"Eiffel": "#4d6977",
|
"Eiffel": "#4d6977",
|
||||||
"Elixir": "#6e4a7e",
|
"Elixir": "#6e4a7e",
|
||||||
"Elm": "#60B5CC",
|
"Elm": "#60B5CC",
|
||||||
"Elvish": "#55BB55",
|
"Elvish": "#55BB55",
|
||||||
"Elvish Transcript": "#55BB55",
|
|
||||||
"Emacs Lisp": "#c065db",
|
"Emacs Lisp": "#c065db",
|
||||||
"EmberScript": "#FFF4F3",
|
"EmberScript": "#FFF4F3",
|
||||||
"Erlang": "#B83998",
|
"Erlang": "#B83998",
|
||||||
@@ -152,7 +147,7 @@
|
|||||||
"Forth": "#341708",
|
"Forth": "#341708",
|
||||||
"Fortran": "#4d41b1",
|
"Fortran": "#4d41b1",
|
||||||
"Fortran Free Form": "#4d41b1",
|
"Fortran Free Form": "#4d41b1",
|
||||||
"FreeBasic": "#141AC9",
|
"FreeBasic": "#867db1",
|
||||||
"FreeMarker": "#0050b2",
|
"FreeMarker": "#0050b2",
|
||||||
"Frege": "#00cafe",
|
"Frege": "#00cafe",
|
||||||
"Futhark": "#5f021f",
|
"Futhark": "#5f021f",
|
||||||
@@ -185,7 +180,6 @@
|
|||||||
"Go": "#00ADD8",
|
"Go": "#00ADD8",
|
||||||
"Go Checksums": "#00ADD8",
|
"Go Checksums": "#00ADD8",
|
||||||
"Go Module": "#00ADD8",
|
"Go Module": "#00ADD8",
|
||||||
"Godot Resource": "#355570",
|
|
||||||
"Golo": "#88562A",
|
"Golo": "#88562A",
|
||||||
"Gosu": "#82937f",
|
"Gosu": "#82937f",
|
||||||
"Grace": "#615f8b",
|
"Grace": "#615f8b",
|
||||||
@@ -196,7 +190,6 @@
|
|||||||
"Groovy": "#4298b8",
|
"Groovy": "#4298b8",
|
||||||
"Groovy Server Pages": "#4298b8",
|
"Groovy Server Pages": "#4298b8",
|
||||||
"HAProxy": "#106da9",
|
"HAProxy": "#106da9",
|
||||||
"HCL": "#844FBA",
|
|
||||||
"HLSL": "#aace60",
|
"HLSL": "#aace60",
|
||||||
"HOCON": "#9ff8ee",
|
"HOCON": "#9ff8ee",
|
||||||
"HTML": "#e34c26",
|
"HTML": "#e34c26",
|
||||||
@@ -222,7 +215,6 @@
|
|||||||
"Idris": "#b30000",
|
"Idris": "#b30000",
|
||||||
"Ignore List": "#000000",
|
"Ignore List": "#000000",
|
||||||
"ImageJ Macro": "#99AAFF",
|
"ImageJ Macro": "#99AAFF",
|
||||||
"Imba": "#16cec6",
|
|
||||||
"Inno Setup": "#264b99",
|
"Inno Setup": "#264b99",
|
||||||
"Io": "#a9188d",
|
"Io": "#a9188d",
|
||||||
"Ioke": "#078193",
|
"Ioke": "#078193",
|
||||||
@@ -230,7 +222,6 @@
|
|||||||
"Isabelle ROOT": "#FEFE00",
|
"Isabelle ROOT": "#FEFE00",
|
||||||
"J": "#9EEDFF",
|
"J": "#9EEDFF",
|
||||||
"JAR Manifest": "#b07219",
|
"JAR Manifest": "#b07219",
|
||||||
"JCL": "#d90e09",
|
|
||||||
"JFlex": "#DBCA00",
|
"JFlex": "#DBCA00",
|
||||||
"JSON": "#292929",
|
"JSON": "#292929",
|
||||||
"JSON with Comments": "#292929",
|
"JSON with Comments": "#292929",
|
||||||
@@ -253,11 +244,9 @@
|
|||||||
"Jsonnet": "#0064bd",
|
"Jsonnet": "#0064bd",
|
||||||
"Julia": "#a270ba",
|
"Julia": "#a270ba",
|
||||||
"Jupyter Notebook": "#DA5B0B",
|
"Jupyter Notebook": "#DA5B0B",
|
||||||
"Just": "#384d54",
|
|
||||||
"KRL": "#28430A",
|
"KRL": "#28430A",
|
||||||
"Kaitai Struct": "#773b37",
|
"Kaitai Struct": "#773b37",
|
||||||
"KakouneScript": "#6f8042",
|
"KakouneScript": "#6f8042",
|
||||||
"KerboScript": "#41adf0",
|
|
||||||
"KiCad Layout": "#2f4aab",
|
"KiCad Layout": "#2f4aab",
|
||||||
"KiCad Legacy Layout": "#2f4aab",
|
"KiCad Legacy Layout": "#2f4aab",
|
||||||
"KiCad Schematic": "#2f4aab",
|
"KiCad Schematic": "#2f4aab",
|
||||||
@@ -297,7 +286,6 @@
|
|||||||
"Mathematica": "#dd1100",
|
"Mathematica": "#dd1100",
|
||||||
"Max": "#c4a79c",
|
"Max": "#c4a79c",
|
||||||
"Mercury": "#ff2b2b",
|
"Mercury": "#ff2b2b",
|
||||||
"Mermaid": "#ff3670",
|
|
||||||
"Meson": "#007800",
|
"Meson": "#007800",
|
||||||
"Metal": "#8f14e9",
|
"Metal": "#8f14e9",
|
||||||
"MiniYAML": "#ff1111",
|
"MiniYAML": "#ff1111",
|
||||||
@@ -330,10 +318,6 @@
|
|||||||
"Nu": "#c9df40",
|
"Nu": "#c9df40",
|
||||||
"NumPy": "#9C8AF9",
|
"NumPy": "#9C8AF9",
|
||||||
"Nunjucks": "#3d8137",
|
"Nunjucks": "#3d8137",
|
||||||
"OASv2-json": "#85ea2d",
|
|
||||||
"OASv2-yaml": "#85ea2d",
|
|
||||||
"OASv3-json": "#85ea2d",
|
|
||||||
"OASv3-yaml": "#85ea2d",
|
|
||||||
"OCaml": "#3be133",
|
"OCaml": "#3be133",
|
||||||
"ObjectScript": "#424893",
|
"ObjectScript": "#424893",
|
||||||
"Objective-C": "#438eff",
|
"Objective-C": "#438eff",
|
||||||
@@ -343,18 +327,14 @@
|
|||||||
"Omgrofl": "#cabbff",
|
"Omgrofl": "#cabbff",
|
||||||
"Opal": "#f7ede0",
|
"Opal": "#f7ede0",
|
||||||
"Open Policy Agent": "#7d9199",
|
"Open Policy Agent": "#7d9199",
|
||||||
"OpenAPI Specification v2": "#85ea2d",
|
|
||||||
"OpenAPI Specification v3": "#85ea2d",
|
|
||||||
"OpenCL": "#ed2e2d",
|
"OpenCL": "#ed2e2d",
|
||||||
"OpenEdge ABL": "#5ce600",
|
"OpenEdge ABL": "#5ce600",
|
||||||
"OpenQASM": "#AA70FF",
|
"OpenQASM": "#AA70FF",
|
||||||
"OpenSCAD": "#e5cd45",
|
"OpenSCAD": "#e5cd45",
|
||||||
"Option List": "#476732",
|
|
||||||
"Org": "#77aa99",
|
"Org": "#77aa99",
|
||||||
"Oxygene": "#cdd0e3",
|
"Oxygene": "#cdd0e3",
|
||||||
"Oz": "#fab738",
|
"Oz": "#fab738",
|
||||||
"P4": "#7055b5",
|
"P4": "#7055b5",
|
||||||
"PDDL": "#0d00ff",
|
|
||||||
"PEG.js": "#234d6b",
|
"PEG.js": "#234d6b",
|
||||||
"PHP": "#4F5D95",
|
"PHP": "#4F5D95",
|
||||||
"PLSQL": "#dad8d8",
|
"PLSQL": "#dad8d8",
|
||||||
@@ -370,9 +350,7 @@
|
|||||||
"PicoLisp": "#6067af",
|
"PicoLisp": "#6067af",
|
||||||
"PigLatin": "#fcd7de",
|
"PigLatin": "#fcd7de",
|
||||||
"Pike": "#005390",
|
"Pike": "#005390",
|
||||||
"PlantUML": "#fbbd16",
|
|
||||||
"PogoScript": "#d80074",
|
"PogoScript": "#d80074",
|
||||||
"Polar": "#ae81ff",
|
|
||||||
"Portugol": "#f8bd00",
|
"Portugol": "#f8bd00",
|
||||||
"PostCSS": "#dc3a0c",
|
"PostCSS": "#dc3a0c",
|
||||||
"PostScript": "#da291c",
|
"PostScript": "#da291c",
|
||||||
@@ -388,7 +366,6 @@
|
|||||||
"Puppet": "#302B6D",
|
"Puppet": "#302B6D",
|
||||||
"PureBasic": "#5a6986",
|
"PureBasic": "#5a6986",
|
||||||
"PureScript": "#1D222D",
|
"PureScript": "#1D222D",
|
||||||
"Pyret": "#ee1e10",
|
|
||||||
"Python": "#3572A5",
|
"Python": "#3572A5",
|
||||||
"Python console": "#3572A5",
|
"Python console": "#3572A5",
|
||||||
"Python traceback": "#3572A5",
|
"Python traceback": "#3572A5",
|
||||||
@@ -437,7 +414,6 @@
|
|||||||
"Sass": "#a53b70",
|
"Sass": "#a53b70",
|
||||||
"Scala": "#c22d40",
|
"Scala": "#c22d40",
|
||||||
"Scaml": "#bd181a",
|
"Scaml": "#bd181a",
|
||||||
"Scenic": "#fdc700",
|
|
||||||
"Scheme": "#1e4aec",
|
"Scheme": "#1e4aec",
|
||||||
"Scilab": "#ca0f21",
|
"Scilab": "#ca0f21",
|
||||||
"Self": "#0579aa",
|
"Self": "#0579aa",
|
||||||
@@ -445,7 +421,6 @@
|
|||||||
"Shell": "#89e051",
|
"Shell": "#89e051",
|
||||||
"ShellCheck Config": "#cecfcb",
|
"ShellCheck Config": "#cecfcb",
|
||||||
"Shen": "#120F14",
|
"Shen": "#120F14",
|
||||||
"Simple File Verification": "#C9BFED",
|
|
||||||
"Singularity": "#64E6AD",
|
"Singularity": "#64E6AD",
|
||||||
"Slash": "#007eff",
|
"Slash": "#007eff",
|
||||||
"Slice": "#003fa2",
|
"Slice": "#003fa2",
|
||||||
@@ -453,8 +428,6 @@
|
|||||||
"SmPL": "#c94949",
|
"SmPL": "#c94949",
|
||||||
"Smalltalk": "#596706",
|
"Smalltalk": "#596706",
|
||||||
"Smarty": "#f0c040",
|
"Smarty": "#f0c040",
|
||||||
"Smithy": "#c44536",
|
|
||||||
"Snakemake": "#419179",
|
|
||||||
"Solidity": "#AA6746",
|
"Solidity": "#AA6746",
|
||||||
"SourcePawn": "#f69e1d",
|
"SourcePawn": "#f69e1d",
|
||||||
"Squirrel": "#800000",
|
"Squirrel": "#800000",
|
||||||
@@ -468,7 +441,6 @@
|
|||||||
"SugarSS": "#2fcc9f",
|
"SugarSS": "#2fcc9f",
|
||||||
"SuperCollider": "#46390b",
|
"SuperCollider": "#46390b",
|
||||||
"Svelte": "#ff3e00",
|
"Svelte": "#ff3e00",
|
||||||
"Sway": "#dea584",
|
|
||||||
"Swift": "#F05138",
|
"Swift": "#F05138",
|
||||||
"SystemVerilog": "#DAE1C2",
|
"SystemVerilog": "#DAE1C2",
|
||||||
"TI Program": "#A0AA87",
|
"TI Program": "#A0AA87",
|
||||||
@@ -506,7 +478,6 @@
|
|||||||
"Vim Script": "#199f4b",
|
"Vim Script": "#199f4b",
|
||||||
"Vim Snippet": "#199f4b",
|
"Vim Snippet": "#199f4b",
|
||||||
"Visual Basic .NET": "#945db7",
|
"Visual Basic .NET": "#945db7",
|
||||||
"Visual Basic 6.0": "#2c6353",
|
|
||||||
"Volt": "#1F1F1F",
|
"Volt": "#1F1F1F",
|
||||||
"Vue": "#41b883",
|
"Vue": "#41b883",
|
||||||
"Vyper": "#2980b9",
|
"Vyper": "#2980b9",
|
||||||
|
|||||||
+2
-11
@@ -1,11 +1,5 @@
|
|||||||
import { CustomError, logger } from "./utils.js";
|
import { CustomError, logger } from "./utils.js";
|
||||||
|
|
||||||
// Script variables.
|
|
||||||
const PATs = Object.keys(process.env).filter((key) =>
|
|
||||||
/PAT_\d*$/.exec(key),
|
|
||||||
).length;
|
|
||||||
const RETRIES = PATs ? PATs : 7;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to execute the fetcher function until it succeeds or the max number of retries is reached.
|
* Try to execute the fetcher function until it succeeds or the max number of retries is reached.
|
||||||
*
|
*
|
||||||
@@ -16,7 +10,7 @@ const RETRIES = PATs ? PATs : 7;
|
|||||||
* @returns Promise<retryer>
|
* @returns Promise<retryer>
|
||||||
*/
|
*/
|
||||||
const retryer = async (fetcher, variables, retries = 0) => {
|
const retryer = async (fetcher, variables, retries = 0) => {
|
||||||
if (retries > RETRIES) {
|
if (retries > 7) {
|
||||||
throw new CustomError("Maximum retries exceeded", CustomError.MAX_RETRY);
|
throw new CustomError("Maximum retries exceeded", CustomError.MAX_RETRY);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -45,11 +39,8 @@ const retryer = async (fetcher, variables, retries = 0) => {
|
|||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
// also checking for bad credentials if any tokens gets invalidated
|
// also checking for bad credentials if any tokens gets invalidated
|
||||||
const isBadCredential = err.response.data && err.response.data.message === "Bad credentials";
|
const isBadCredential = err.response.data && err.response.data.message === "Bad credentials";
|
||||||
const isAccountSuspended =
|
|
||||||
err.response.data &&
|
|
||||||
err.response.data.message === "Sorry. Your account was suspended.";
|
|
||||||
|
|
||||||
if (isBadCredential || isAccountSuspended) {
|
if (isBadCredential) {
|
||||||
logger.log(`PAT_${retries + 1} Failed`);
|
logger.log(`PAT_${retries + 1} Failed`);
|
||||||
retries++;
|
retries++;
|
||||||
// directly return from the function
|
// directly return from the function
|
||||||
|
|||||||
+52
-74
@@ -42,13 +42,13 @@ const renderError = (message, secondaryMessage = "") => {
|
|||||||
* @param {string} str String to encode.
|
* @param {string} str String to encode.
|
||||||
* @returns {string} Encoded string.
|
* @returns {string} Encoded string.
|
||||||
*/
|
*/
|
||||||
const encodeHTML = (str) => {
|
function encodeHTML(str) {
|
||||||
return str
|
return str
|
||||||
.replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => {
|
.replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => {
|
||||||
return "&#" + i.charCodeAt(0) + ";";
|
return "&#" + i.charCodeAt(0) + ";";
|
||||||
})
|
})
|
||||||
.replace(/\u0008/gim, "");
|
.replace(/\u0008/gim, "");
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves num with suffix k(thousands) precise to 1 decimal if greater than 999.
|
* Retrieves num with suffix k(thousands) precise to 1 decimal if greater than 999.
|
||||||
@@ -56,42 +56,41 @@ const encodeHTML = (str) => {
|
|||||||
* @param {number} num The number to format.
|
* @param {number} num The number to format.
|
||||||
* @returns {string|number} The formatted number.
|
* @returns {string|number} The formatted number.
|
||||||
*/
|
*/
|
||||||
const kFormatter = (num) => {
|
function kFormatter(num) {
|
||||||
return Math.abs(num) > 999
|
return Math.abs(num) > 999
|
||||||
? Math.sign(num) * parseFloat((Math.abs(num) / 1000).toFixed(1)) + "k"
|
? Math.sign(num) * parseFloat((Math.abs(num) / 1000).toFixed(1)) + "k"
|
||||||
: Math.sign(num) * Math.abs(num);
|
: Math.sign(num) * Math.abs(num);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a string is a valid hex color.
|
* Checks if a string is a valid hex color.
|
||||||
*
|
*
|
||||||
* @param {string} hexColor String to check.
|
* @param {string} hexColor String to check.
|
||||||
* @returns {boolean} True if the given string is a valid hex color.
|
* @returns {boolean} True if the given string is a valid hex color.
|
||||||
|
*
|
||||||
|
* @description: Tests color codes without the hash (#) prefix.
|
||||||
*/
|
*/
|
||||||
const isValidHexColor = (hexColor) => {
|
function isValidHexColor(hexColor) {
|
||||||
return new RegExp(
|
return new RegExp(
|
||||||
/^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{4})$/,
|
/^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{4})$/,
|
||||||
).test(hexColor);
|
).test(hexColor);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns boolean if value is either "true" or "false" else the value as it is.
|
* Returns boolean if value is either "true" or "false" else the value as it is.
|
||||||
*
|
*
|
||||||
* @param {string | boolean} value The value to parse.
|
* @param {string} value The value to parse.
|
||||||
* @returns {boolean | undefined } The parsed value.
|
* @returns {boolean | string} The parsed value.
|
||||||
*/
|
*/
|
||||||
const parseBoolean = (value) => {
|
function parseBoolean(value) {
|
||||||
if (typeof value === "boolean") return value;
|
if (value === "true") {
|
||||||
|
return true;
|
||||||
if (typeof value === "string") {
|
} else if (value === "false") {
|
||||||
if (value.toLowerCase() === "true") {
|
return false;
|
||||||
return true;
|
} else {
|
||||||
} else if (value.toLowerCase() === "false") {
|
return value;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return undefined;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse string to array of strings.
|
* Parse string to array of strings.
|
||||||
@@ -99,10 +98,10 @@ const parseBoolean = (value) => {
|
|||||||
* @param {string} str The string to parse.
|
* @param {string} str The string to parse.
|
||||||
* @returns {string[]} The array of strings.
|
* @returns {string[]} The array of strings.
|
||||||
*/
|
*/
|
||||||
const parseArray = (str) => {
|
function parseArray(str) {
|
||||||
if (!str) return [];
|
if (!str) return [];
|
||||||
return str.split(",");
|
return str.split(",");
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clamp the given number between the given range.
|
* Clamp the given number between the given range.
|
||||||
@@ -112,11 +111,11 @@ const parseArray = (str) => {
|
|||||||
* @param {number} max The maximum value.
|
* @param {number} max The maximum value.
|
||||||
* returns {number} The clamped number.
|
* returns {number} The clamped number.
|
||||||
*/
|
*/
|
||||||
const clampValue = (number, min, max) => {
|
function clampValue(number, min, max) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (Number.isNaN(parseInt(number))) return min;
|
if (Number.isNaN(parseInt(number))) return min;
|
||||||
return Math.max(min, Math.min(number, max));
|
return Math.max(min, Math.min(number, max));
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the given string is a valid gradient.
|
* Check if the given string is a valid gradient.
|
||||||
@@ -124,9 +123,10 @@ const clampValue = (number, min, max) => {
|
|||||||
* @param {string[]} colors Array of colors.
|
* @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.
|
||||||
*/
|
*/
|
||||||
const isValidGradient = (colors) => {
|
function isValidGradient(colors) {
|
||||||
return isValidHexColor(colors[1]) && isValidHexColor(colors[2]);
|
if (isNaN(colors[0])) return false;
|
||||||
};
|
return colors.slice(1).every((color) => isValidHexColor(color));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a gradient if color has more than one valid hex codes else a single color.
|
* Retrieves a gradient if color has more than one valid hex codes else a single color.
|
||||||
@@ -135,10 +135,10 @@ const isValidGradient = (colors) => {
|
|||||||
* @param {string} fallbackColor The fallback color.
|
* @param {string} fallbackColor The fallback color.
|
||||||
* @returns {string | string[]} The gradient or color.
|
* @returns {string | string[]} The gradient or color.
|
||||||
*/
|
*/
|
||||||
const fallbackColor = (color, fallbackColor) => {
|
function fallbackColor(color, fallbackColor) {
|
||||||
|
let colors = color.split(",");
|
||||||
let gradient = null;
|
let gradient = null;
|
||||||
|
|
||||||
let colors = color ? color.split(",") : [];
|
|
||||||
if (colors.length > 1 && isValidGradient(colors)) {
|
if (colors.length > 1 && isValidGradient(colors)) {
|
||||||
gradient = colors;
|
gradient = colors;
|
||||||
}
|
}
|
||||||
@@ -147,7 +147,7 @@ const fallbackColor = (color, fallbackColor) => {
|
|||||||
(gradient ? gradient : isValidHexColor(color) && `#${color}`) ||
|
(gradient ? gradient : isValidHexColor(color) && `#${color}`) ||
|
||||||
fallbackColor
|
fallbackColor
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send GraphQL request to GitHub API.
|
* Send GraphQL request to GitHub API.
|
||||||
@@ -156,7 +156,7 @@ const fallbackColor = (color, fallbackColor) => {
|
|||||||
* @param {import('axios').AxiosRequestConfig['headers']} headers Request headers.
|
* @param {import('axios').AxiosRequestConfig['headers']} headers Request headers.
|
||||||
* @returns {Promise<any>} Request response.
|
* @returns {Promise<any>} Request response.
|
||||||
*/
|
*/
|
||||||
const request = (data, headers) => {
|
function request(data, headers) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return axios({
|
return axios({
|
||||||
url: "https://api.github.com/graphql",
|
url: "https://api.github.com/graphql",
|
||||||
@@ -164,7 +164,7 @@ const request = (data, headers) => {
|
|||||||
headers,
|
headers,
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto layout utility, allows us to layout things vertically or horizontally with
|
* Auto layout utility, allows us to layout things vertically or horizontally with
|
||||||
@@ -173,11 +173,11 @@ const request = (data, headers) => {
|
|||||||
* @param {object} props Function properties.
|
* @param {object} props Function properties.
|
||||||
* @param {string[]} props.items Array of items to layout.
|
* @param {string[]} props.items Array of items to layout.
|
||||||
* @param {number} props.gap Gap between items.
|
* @param {number} props.gap Gap between items.
|
||||||
* @param {number[]?=} props.sizes Array of sizes for each item.
|
|
||||||
* @param {"column" | "row"?=} props.direction Direction to layout 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[]} Array of items with proper layout.
|
||||||
*/
|
*/
|
||||||
const flexLayout = ({ items, gap, direction, sizes = [] }) => {
|
function flexLayout({ items, gap, direction, sizes = [] }) {
|
||||||
let lastSize = 0;
|
let lastSize = 0;
|
||||||
// filter() for filtering out empty strings
|
// filter() for filtering out empty strings
|
||||||
return items.filter(Boolean).map((item, i) => {
|
return items.filter(Boolean).map((item, i) => {
|
||||||
@@ -189,31 +189,30 @@ const flexLayout = ({ items, gap, direction, sizes = [] }) => {
|
|||||||
lastSize += size + gap;
|
lastSize += size + gap;
|
||||||
return `<g transform="${transform}">${item}</g>`;
|
return `<g transform="${transform}">${item}</g>`;
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns theme based colors with proper overrides and defaults.
|
* Returns theme based colors with proper overrides and defaults.
|
||||||
*
|
*
|
||||||
* @param {Object} args Function arguments.
|
* @param {Object[]} args Function arguments.
|
||||||
* @param {string} args.title_color Card title color.
|
* @param {string} args.title_color Card title color.
|
||||||
* @param {string} args.text_color Card text color.
|
* @param {string} args.text_color Card text color.
|
||||||
* @param {string} args.icon_color Card icon color.
|
* @param {string} args.icon_color Card icon color.
|
||||||
* @param {string} args.bg_color Card background color.
|
* @param {string} args.bg_color Card background color.
|
||||||
* @param {string} args.border_color Card border color.
|
* @param {string} args.border_color Card border color.
|
||||||
* @param {string} args.ring_color Card ring color.
|
|
||||||
* @param {string} args.theme Card theme.
|
* @param {string} args.theme Card theme.
|
||||||
* @param {string} args.fallbackTheme Fallback theme.
|
* @param {string} args.fallbackTheme Fallback theme.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
const getCardColors = ({
|
function getCardColors({
|
||||||
title_color,
|
title_color,
|
||||||
text_color,
|
text_color,
|
||||||
icon_color,
|
icon_color,
|
||||||
bg_color,
|
bg_color,
|
||||||
border_color,
|
border_color,
|
||||||
ring_color,
|
|
||||||
theme,
|
theme,
|
||||||
fallbackTheme = "default",
|
fallbackTheme = "default",
|
||||||
}) => {
|
}) {
|
||||||
const defaultTheme = themes[fallbackTheme];
|
const defaultTheme = themes[fallbackTheme];
|
||||||
const selectedTheme = themes[theme] || defaultTheme;
|
const selectedTheme = themes[theme] || defaultTheme;
|
||||||
const defaultBorderColor =
|
const defaultBorderColor =
|
||||||
@@ -225,13 +224,6 @@ const getCardColors = ({
|
|||||||
title_color || selectedTheme.title_color,
|
title_color || selectedTheme.title_color,
|
||||||
"#" + defaultTheme.title_color,
|
"#" + defaultTheme.title_color,
|
||||||
);
|
);
|
||||||
|
|
||||||
// get the color provided by the user else the theme color
|
|
||||||
// finally if both colors are invalid we use the titleColor
|
|
||||||
const ringColor = fallbackColor(
|
|
||||||
ring_color || selectedTheme.ring_color,
|
|
||||||
titleColor,
|
|
||||||
);
|
|
||||||
const iconColor = fallbackColor(
|
const iconColor = fallbackColor(
|
||||||
icon_color || selectedTheme.icon_color,
|
icon_color || selectedTheme.icon_color,
|
||||||
"#" + defaultTheme.icon_color,
|
"#" + defaultTheme.icon_color,
|
||||||
@@ -250,8 +242,8 @@ const getCardColors = ({
|
|||||||
"#" + defaultBorderColor,
|
"#" + defaultBorderColor,
|
||||||
);
|
);
|
||||||
|
|
||||||
return { titleColor, iconColor, textColor, bgColor, borderColor, ringColor };
|
return { titleColor, iconColor, textColor, bgColor, borderColor };
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Split text over multiple lines based on the card width.
|
* Split text over multiple lines based on the card width.
|
||||||
@@ -261,7 +253,7 @@ const getCardColors = ({
|
|||||||
* @param {number} maxLines Maximum number of lines.
|
* @param {number} maxLines Maximum number of lines.
|
||||||
* @returns {string[]} Array of lines.
|
* @returns {string[]} Array of lines.
|
||||||
*/
|
*/
|
||||||
const wrapTextMultiline = (text, width = 59, maxLines = 3) => {
|
function wrapTextMultiline(text, width = 59, maxLines = 3) {
|
||||||
const fullWidthComma = ",";
|
const fullWidthComma = ",";
|
||||||
const encoded = encodeHTML(text);
|
const encoded = encodeHTML(text);
|
||||||
const isChinese = encoded.includes(fullWidthComma);
|
const isChinese = encoded.includes(fullWidthComma);
|
||||||
@@ -286,7 +278,7 @@ const wrapTextMultiline = (text, width = 59, maxLines = 3) => {
|
|||||||
// Remove empty lines if text fits in less than maxLines lines
|
// Remove empty lines if text fits in less than maxLines lines
|
||||||
const multiLineText = lines.filter(Boolean);
|
const multiLineText = lines.filter(Boolean);
|
||||||
return multiLineText;
|
return multiLineText;
|
||||||
};
|
}
|
||||||
|
|
||||||
const noop = () => {};
|
const noop = () => {};
|
||||||
// return console instance based on the environment
|
// return console instance based on the environment
|
||||||
@@ -352,7 +344,7 @@ class MissingParamError extends Error {
|
|||||||
* @param {number} fontSize Font size.
|
* @param {number} fontSize Font size.
|
||||||
* @returns {number} Text length.
|
* @returns {number} Text length.
|
||||||
*/
|
*/
|
||||||
const measureText = (str, fontSize = 10) => {
|
function measureText(str, fontSize = 10) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const widths = [
|
const widths = [
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
@@ -384,20 +376,20 @@ const measureText = (str, fontSize = 10) => {
|
|||||||
)
|
)
|
||||||
.reduce((cur, acc) => acc + cur) * fontSize
|
.reduce((cur, acc) => acc + cur) * fontSize
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
/** @param {string} name */
|
/** @param {string} name */
|
||||||
const lowercaseTrim = (name) => name.toLowerCase().trim();
|
const lowercaseTrim = (name) => name.toLowerCase().trim();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Split array of languages in two columns.
|
* Split array of languages in columns.
|
||||||
*
|
*
|
||||||
* @template T Language object.
|
* @template T Langauge object.
|
||||||
* @param {Array<T>} arr Array of languages.
|
* @param {Array<T>} arr Array of languages.
|
||||||
* @param {number} perChunk Number of languages per column.
|
* @param {number} perChunk Number of languages per column.
|
||||||
* @returns {Array<T>} Array of languages split in two columns.
|
* @returns {Array<T>} Array of languages split in columns.
|
||||||
*/
|
*/
|
||||||
const chunkArray = (arr, perChunk) => {
|
function chunkArray(arr, perChunk) {
|
||||||
return arr.reduce((resultArray, item, index) => {
|
return arr.reduce((resultArray, item, index) => {
|
||||||
const chunkIndex = Math.floor(index / perChunk);
|
const chunkIndex = Math.floor(index / perChunk);
|
||||||
|
|
||||||
@@ -409,7 +401,7 @@ const chunkArray = (arr, perChunk) => {
|
|||||||
|
|
||||||
return resultArray;
|
return resultArray;
|
||||||
}, []);
|
}, []);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse emoji from string.
|
* Parse emoji from string.
|
||||||
@@ -417,25 +409,12 @@ const chunkArray = (arr, perChunk) => {
|
|||||||
* @param {string} str String to parse emoji from.
|
* @param {string} str String to parse emoji from.
|
||||||
* @returns {string} String with emoji parsed.
|
* @returns {string} String with emoji parsed.
|
||||||
*/
|
*/
|
||||||
const parseEmojis = (str) => {
|
function parseEmojis(str) {
|
||||||
if (!str) throw new Error("[parseEmoji]: str argument not provided");
|
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) || "";
|
return toEmoji.get(emoji) || "";
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get diff in minutes
|
|
||||||
* @param {Date} d1
|
|
||||||
* @param {Date} d2
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
const dateDiff = (d1, d2) => {
|
|
||||||
const date1 = new Date(d1);
|
|
||||||
const date2 = new Date(d2);
|
|
||||||
const diff = date1.getTime() - date2.getTime();
|
|
||||||
return Math.round(diff / (1000 * 60));
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ERROR_CARD_LENGTH,
|
ERROR_CARD_LENGTH,
|
||||||
@@ -460,5 +439,4 @@ export {
|
|||||||
lowercaseTrim,
|
lowercaseTrim,
|
||||||
chunkArray,
|
chunkArray,
|
||||||
parseEmojis,
|
parseEmojis,
|
||||||
dateDiff,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { MissingParamError, request } from "../common/utils.js";
|
|||||||
/**
|
/**
|
||||||
* Repo data fetcher.
|
* Repo data fetcher.
|
||||||
*
|
*
|
||||||
* @param {import('axios').AxiosRequestHeaders} variables Fetcher variables.
|
* @param {import('Axios').AxiosRequestHeaders} variables Fetcher variables.
|
||||||
* @param {string} token GitHub token.
|
* @param {string} token Github token.
|
||||||
* @returns {Promise<import('axios').AxiosResponse>} The response.
|
* @returns {Promise<import('Axios').AxiosResponse>} The response.
|
||||||
*/
|
*/
|
||||||
const fetcher = (variables, token) => {
|
const fetcher = (variables, token) => {
|
||||||
return request(
|
return request(
|
||||||
@@ -56,11 +56,11 @@ const urlExample = "/api/pin?username=USERNAME&repo=REPO_NAME";
|
|||||||
/**
|
/**
|
||||||
* Fetch repository data.
|
* Fetch repository data.
|
||||||
*
|
*
|
||||||
* @param {string} username GitHub username.
|
* @param {string} username Github username.
|
||||||
* @param {string} reponame GitHub repository name.
|
* @param {string} reponame Github repository name.
|
||||||
* @returns {Promise<import("./types").RepositoryData>} Repository data.
|
* @returns {Promise<import("./types").RepositoryData>} Repository data.
|
||||||
*/
|
*/
|
||||||
const fetchRepo = async (username, reponame) => {
|
async function fetchRepo(username, reponame) {
|
||||||
if (!username && !reponame) {
|
if (!username && !reponame) {
|
||||||
throw new MissingParamError(["username", "repo"], urlExample);
|
throw new MissingParamError(["username", "repo"], urlExample);
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ const fetchRepo = async (username, reponame) => {
|
|||||||
starCount: data.organization.repository.stargazers.totalCount,
|
starCount: data.organization.repository.stargazers.totalCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export { fetchRepo };
|
export { fetchRepo };
|
||||||
export default fetchRepo;
|
export default fetchRepo;
|
||||||
|
|||||||
+139
-125
@@ -14,72 +14,46 @@ import {
|
|||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
// GraphQL queries.
|
|
||||||
const GRAPHQL_REPOS_FIELD = `
|
|
||||||
repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}, after: $after) {
|
|
||||||
totalCount
|
|
||||||
nodes {
|
|
||||||
name
|
|
||||||
stargazers {
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pageInfo {
|
|
||||||
hasNextPage
|
|
||||||
endCursor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const GRAPHQL_REPOS_QUERY = `
|
|
||||||
query userInfo($login: String!, $after: String) {
|
|
||||||
user(login: $login) {
|
|
||||||
${GRAPHQL_REPOS_FIELD}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const GRAPHQL_STATS_QUERY = `
|
|
||||||
query userInfo($login: String!, $after: String) {
|
|
||||||
user(login: $login) {
|
|
||||||
name
|
|
||||||
login
|
|
||||||
contributionsCollection {
|
|
||||||
totalCommitContributions
|
|
||||||
restrictedContributionsCount
|
|
||||||
}
|
|
||||||
repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
pullRequests(first: 1) {
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
openIssues: issues(states: OPEN) {
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
closedIssues: issues(states: CLOSED) {
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
followers {
|
|
||||||
totalCount
|
|
||||||
}
|
|
||||||
${GRAPHQL_REPOS_FIELD}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stats fetcher object.
|
* Stats fetcher object.
|
||||||
*
|
*
|
||||||
* @param {import('axios').AxiosRequestHeaders} variables Fetcher variables.
|
* @param {import('axios').AxiosRequestHeaders} variables Fetcher variables.
|
||||||
* @param {string} token GitHub token.
|
* @param {string} token Github token.
|
||||||
* @returns {Promise<import('../common/types').Fetcher>} Stats fetcher response.
|
* @returns {Promise<import('../common/types').StatsFetcherResponse>} Stats fetcher response.
|
||||||
*/
|
*/
|
||||||
const fetcher = (variables, token) => {
|
const fetcher = (variables, token) => {
|
||||||
const query = !variables.after ? GRAPHQL_STATS_QUERY : GRAPHQL_REPOS_QUERY;
|
|
||||||
return request(
|
return request(
|
||||||
{
|
{
|
||||||
query,
|
query: `
|
||||||
|
query userInfo($login: String!) {
|
||||||
|
user(login: $login) {
|
||||||
|
name
|
||||||
|
login
|
||||||
|
contributionsCollection {
|
||||||
|
totalCommitContributions
|
||||||
|
restrictedContributionsCount
|
||||||
|
}
|
||||||
|
repositoriesContributedTo(contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
pullRequests {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
openIssues: issues(states: OPEN) {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
closedIssues: issues(states: CLOSED) {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
followers {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
repositories(ownerAffiliations: OWNER) {
|
||||||
|
totalCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
variables,
|
variables,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -89,51 +63,48 @@ const fetcher = (variables, token) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch stats information for a given username.
|
* Fetch first 100 repositories for a given username.
|
||||||
*
|
*
|
||||||
* @param {string} username Github username.
|
* @param {import('axios').AxiosRequestHeaders} variables Fetcher variables.
|
||||||
* @returns {Promise<import('../common/types').StatsFetcher>} GraphQL Stats object.
|
* @param {string} token Github token.
|
||||||
*
|
* @returns {Promise<import('../common/types').StatsFetcherResponse>} Repositories fetcher response.
|
||||||
* @description This function supports multi-page fetching if the 'FETCH_MULTI_PAGE_STARS' environment variable is set to true.
|
|
||||||
*/
|
*/
|
||||||
const statsFetcher = async (username) => {
|
const repositoriesFetcher = (variables, token) => {
|
||||||
let stats;
|
return request(
|
||||||
let hasNextPage = true;
|
{
|
||||||
let endCursor = null;
|
query: `
|
||||||
while (hasNextPage) {
|
query userInfo($login: String!, $after: String) {
|
||||||
const variables = { login: username, first: 100, after: endCursor };
|
user(login: $login) {
|
||||||
let res = await retryer(fetcher, variables);
|
repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}, after: $after) {
|
||||||
if (res.data.errors) return res;
|
nodes {
|
||||||
|
name
|
||||||
// Store stats data.
|
stargazers {
|
||||||
const repoNodes = res.data.data.user.repositories.nodes;
|
totalCount
|
||||||
if (!stats) {
|
}
|
||||||
stats = res;
|
}
|
||||||
} else {
|
pageInfo {
|
||||||
stats.data.data.user.repositories.nodes.push(...repoNodes);
|
hasNextPage
|
||||||
}
|
endCursor
|
||||||
|
}
|
||||||
// Disable multi page fetching on public Vercel instance due to rate limits.
|
}
|
||||||
const repoNodesWithStars = repoNodes.filter(
|
}
|
||||||
(node) => node.stargazers.totalCount !== 0,
|
}
|
||||||
);
|
`,
|
||||||
hasNextPage =
|
variables,
|
||||||
process.env.FETCH_MULTI_PAGE_STARS === "true" &&
|
},
|
||||||
repoNodes.length === repoNodesWithStars.length &&
|
{
|
||||||
res.data.data.user.repositories.pageInfo.hasNextPage;
|
Authorization: `bearer ${token}`,
|
||||||
endCursor = res.data.data.user.repositories.pageInfo.endCursor;
|
},
|
||||||
}
|
);
|
||||||
|
|
||||||
return stats;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all the commits for all the repositories of a given username.
|
* Fetch all the commits for all the repositories of a given username.
|
||||||
*
|
*
|
||||||
* @param {*} username GitHub username.
|
* @param {*} username Github username.
|
||||||
* @returns {Promise<number>} Total commits.
|
* @returns {Promise<number>} Total commits.
|
||||||
*
|
*
|
||||||
* @description Done like this because the GitHub API does not provide a way to fetch all the commits. See
|
* @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.
|
* #92#issuecomment-661026467 and #211 for more information.
|
||||||
*/
|
*/
|
||||||
const totalCommitsFetcher = async (username) => {
|
const totalCommitsFetcher = async (username) => {
|
||||||
@@ -169,20 +140,60 @@ const totalCommitsFetcher = async (username) => {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
const totalStarsFetcher = async (username, repoToHide) => {
|
||||||
|
let nodes = [];
|
||||||
|
let hasNextPage = true;
|
||||||
|
let endCursor = null;
|
||||||
|
while (hasNextPage) {
|
||||||
|
const variables = { login: username, first: 100, after: endCursor };
|
||||||
|
let res = await retryer(repositoriesFetcher, variables);
|
||||||
|
|
||||||
|
if (res.data.errors) {
|
||||||
|
logger.error(res.data.errors);
|
||||||
|
throw new CustomError(
|
||||||
|
res.data.errors[0].message || "Could not fetch user",
|
||||||
|
CustomError.USER_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const allNodes = res.data.data.user.repositories.nodes;
|
||||||
|
const nodesWithStars = allNodes.filter(
|
||||||
|
(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.
|
||||||
|
endCursor = res.data.data.user.repositories.pageInfo.endCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
.filter((data) => !repoToHide[data.name])
|
||||||
|
.reduce((prev, curr) => prev + curr.stargazers.totalCount, 0);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch stats for a given username.
|
* Fetch stats for a given username.
|
||||||
*
|
*
|
||||||
* @param {string} username GitHub username.
|
* @param {string} username Github username.
|
||||||
* @param {boolean} count_private Include private contributions.
|
* @param {boolean} count_private Include private contributions.
|
||||||
* @param {boolean} include_all_commits Include all commits.
|
* @param {boolean} include_all_commits Include all commits.
|
||||||
* @returns {Promise<import("./types").StatsData>} Stats data.
|
* @returns {Promise<import("./types").StatsData>} Stats data.
|
||||||
*/
|
*/
|
||||||
const fetchStats = async (
|
async function fetchStats(
|
||||||
username,
|
username,
|
||||||
count_private = false,
|
count_private = false,
|
||||||
include_all_commits = false,
|
include_all_commits = false,
|
||||||
exclude_repo = [],
|
exclude_repo = [],
|
||||||
) => {
|
) {
|
||||||
if (!username) throw new MissingParamError(["username"]);
|
if (!username) throw new MissingParamError(["username"]);
|
||||||
|
|
||||||
const stats = {
|
const stats = {
|
||||||
@@ -192,10 +203,10 @@ const fetchStats = async (
|
|||||||
totalIssues: 0,
|
totalIssues: 0,
|
||||||
totalStars: 0,
|
totalStars: 0,
|
||||||
contributedTo: 0,
|
contributedTo: 0,
|
||||||
rank: { level: "B", score: 0 },
|
rank: { level: "C", score: 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = await statsFetcher(username);
|
let res = await retryer(fetcher, { login: username });
|
||||||
|
|
||||||
// Catch GraphQL errors.
|
// Catch GraphQL errors.
|
||||||
if (res.data.errors) {
|
if (res.data.errors) {
|
||||||
@@ -213,55 +224,58 @@ const fetchStats = async (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw new CustomError(
|
throw new CustomError(
|
||||||
"Something went wrong while trying to retrieve the stats data using the GraphQL API.",
|
"Something went while trying to retrieve the stats data using the GraphQL API.",
|
||||||
CustomError.GRAPHQL_ERROR,
|
CustomError.GRAPHQL_ERROR,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = res.data.data.user;
|
const user = res.data.data.user;
|
||||||
|
|
||||||
stats.name = user.name || user.login;
|
// populate repoToHide map for quick lookup
|
||||||
|
// while filtering out
|
||||||
// if include_all_commits, fetch all commits using the REST API.
|
let repoToHide = {};
|
||||||
if (include_all_commits) {
|
if (exclude_repo) {
|
||||||
stats.totalCommits = await totalCommitsFetcher(username);
|
exclude_repo.forEach((repoName) => {
|
||||||
} else {
|
repoToHide[repoName] = true;
|
||||||
stats.totalCommits = user.contributionsCollection.totalCommitContributions;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// if count_private, add private contributions to totalCommits.
|
stats.name = user.name || user.login;
|
||||||
|
stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount;
|
||||||
|
|
||||||
|
// normal commits
|
||||||
|
stats.totalCommits = user.contributionsCollection.totalCommitContributions;
|
||||||
|
|
||||||
|
// if include_all_commits then just get that,
|
||||||
|
// since totalCommitsFetcher already sends totalCommits no need to +=
|
||||||
|
if (include_all_commits) {
|
||||||
|
stats.totalCommits = await totalCommitsFetcher(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if count_private then add private commits to totalCommits so far.
|
||||||
if (count_private) {
|
if (count_private) {
|
||||||
stats.totalCommits +=
|
stats.totalCommits +=
|
||||||
user.contributionsCollection.restrictedContributionsCount;
|
user.contributionsCollection.restrictedContributionsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.totalPRs = user.pullRequests.totalCount;
|
stats.totalPRs = user.pullRequests.totalCount;
|
||||||
stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount;
|
|
||||||
stats.contributedTo = user.repositoriesContributedTo.totalCount;
|
stats.contributedTo = user.repositoriesContributedTo.totalCount;
|
||||||
|
|
||||||
// Retrieve stars while filtering out repositories to be hidden.
|
// Retrieve stars while filtering out repositories to be hidden
|
||||||
let repoToHide = new Set(exclude_repo);
|
stats.totalStars = await totalStarsFetcher(username, repoToHide);
|
||||||
|
|
||||||
stats.totalStars = user.repositories.nodes
|
|
||||||
.filter((data) => {
|
|
||||||
return !repoToHide.has(data.name);
|
|
||||||
})
|
|
||||||
.reduce((prev, curr) => {
|
|
||||||
return prev + curr.stargazers.totalCount;
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
stats.rank = calculateRank({
|
stats.rank = calculateRank({
|
||||||
all_commits: include_all_commits,
|
totalCommits: stats.totalCommits,
|
||||||
commits: stats.totalCommits,
|
totalRepos: user.repositories.totalCount,
|
||||||
|
followers: user.followers.totalCount,
|
||||||
|
contributions: stats.contributedTo,
|
||||||
|
stargazers: stats.totalStars,
|
||||||
prs: stats.totalPRs,
|
prs: stats.totalPRs,
|
||||||
issues: stats.totalIssues,
|
issues: stats.totalIssues,
|
||||||
repos: user.repositories.totalCount,
|
|
||||||
stars: stats.totalStars,
|
|
||||||
followers: user.followers.totalCount,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return stats;
|
return stats;
|
||||||
};
|
}
|
||||||
|
|
||||||
export { fetchStats };
|
export { fetchStats };
|
||||||
export default fetchStats;
|
export default fetchStats;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
import * as dotenv from "dotenv";
|
||||||
import { retryer } from "../common/retryer.js";
|
import { retryer } from "../common/retryer.js";
|
||||||
import {
|
import {
|
||||||
CustomError,
|
CustomError,
|
||||||
@@ -8,11 +9,13 @@ import {
|
|||||||
wrapTextMultiline,
|
wrapTextMultiline,
|
||||||
} from "../common/utils.js";
|
} from "../common/utils.js";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top languages fetcher object.
|
* Top languages fetcher object.
|
||||||
*
|
*
|
||||||
* @param {import('axios').AxiosRequestHeaders} variables Fetcher variables.
|
* @param {import('Axios').AxiosRequestHeaders} variables Fetcher variables.
|
||||||
* @param {string} token GitHub token.
|
* @param {string} token Github token.
|
||||||
* @returns {Promise<import('../common/types').StatsFetcherResponse>} Languages fetcher response.
|
* @returns {Promise<import('../common/types').StatsFetcherResponse>} Languages fetcher response.
|
||||||
*/
|
*/
|
||||||
const fetcher = (variables, token) => {
|
const fetcher = (variables, token) => {
|
||||||
@@ -50,16 +53,11 @@ const fetcher = (variables, token) => {
|
|||||||
/**
|
/**
|
||||||
* Fetch top languages for a given username.
|
* Fetch top languages for a given username.
|
||||||
*
|
*
|
||||||
* @param {string} username GitHub username.
|
* @param {string} username Github username.
|
||||||
* @param {string[]} exclude_repo List of repositories to exclude.
|
* @param {string[]} exclude_repo List of repositories to exclude.
|
||||||
* @returns {Promise<import("./types").TopLangData>} Top languages data.
|
* @returns {Promise<import("./types").TopLangData>} Top languages data.
|
||||||
*/
|
*/
|
||||||
const fetchTopLanguages = async (
|
async function fetchTopLanguages(username, exclude_repo = []) {
|
||||||
username,
|
|
||||||
exclude_repo = [],
|
|
||||||
size_weight = 1,
|
|
||||||
count_weight = 0,
|
|
||||||
) => {
|
|
||||||
if (!username) throw new MissingParamError(["username"]);
|
if (!username) throw new MissingParamError(["username"]);
|
||||||
|
|
||||||
const res = await retryer(fetcher, { login: username });
|
const res = await retryer(fetcher, { login: username });
|
||||||
@@ -106,8 +104,6 @@ const fetchTopLanguages = async (
|
|||||||
.sort((a, b) => b.size - a.size)
|
.sort((a, b) => b.size - a.size)
|
||||||
.filter((name) => !repoToHide[name.name]);
|
.filter((name) => !repoToHide[name.name]);
|
||||||
|
|
||||||
let repoCount = 0;
|
|
||||||
|
|
||||||
repoNodes = repoNodes
|
repoNodes = repoNodes
|
||||||
.filter((node) => node.languages.edges.length > 0)
|
.filter((node) => node.languages.edges.length > 0)
|
||||||
// flatten the list of language nodes
|
// flatten the list of language nodes
|
||||||
@@ -118,14 +114,9 @@ const fetchTopLanguages = async (
|
|||||||
|
|
||||||
// if we already have the language in the accumulator
|
// if we already have the language in the accumulator
|
||||||
// & the current language name is same as previous name
|
// & the current language name is same as previous name
|
||||||
// add the size to the language size and increase repoCount.
|
// add the size to the language size.
|
||||||
if (acc[prev.node.name] && prev.node.name === acc[prev.node.name].name) {
|
if (acc[prev.node.name] && prev.node.name === acc[prev.node.name].name) {
|
||||||
langSize = prev.size + acc[prev.node.name].size;
|
langSize = prev.size + acc[prev.node.name].size;
|
||||||
repoCount += 1;
|
|
||||||
} else {
|
|
||||||
// reset repoCount to 1
|
|
||||||
// language must exist in at least one repo to be detected
|
|
||||||
repoCount = 1;
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...acc,
|
...acc,
|
||||||
@@ -133,18 +124,10 @@ const fetchTopLanguages = async (
|
|||||||
name: prev.node.name,
|
name: prev.node.name,
|
||||||
color: prev.node.color,
|
color: prev.node.color,
|
||||||
size: langSize,
|
size: langSize,
|
||||||
count: repoCount,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
Object.keys(repoNodes).forEach((name) => {
|
|
||||||
// comparison index calculation
|
|
||||||
repoNodes[name].size =
|
|
||||||
Math.pow(repoNodes[name].size, size_weight) *
|
|
||||||
Math.pow(repoNodes[name].count, count_weight);
|
|
||||||
});
|
|
||||||
|
|
||||||
const topLangs = Object.keys(repoNodes)
|
const topLangs = Object.keys(repoNodes)
|
||||||
.sort((a, b) => repoNodes[b].size - repoNodes[a].size)
|
.sort((a, b) => repoNodes[b].size - repoNodes[a].size)
|
||||||
.reduce((result, key) => {
|
.reduce((result, key) => {
|
||||||
@@ -153,7 +136,7 @@ const fetchTopLanguages = async (
|
|||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
return topLangs;
|
return topLangs;
|
||||||
};
|
}
|
||||||
|
|
||||||
export { fetchTopLanguages };
|
export { fetchTopLanguages };
|
||||||
export default fetchTopLanguages;
|
export default fetchTopLanguages;
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ const fetchWakatimeStats = async ({ username, api_domain, range }) => {
|
|||||||
const { data } = await axios.get(
|
const { data } = await axios.get(
|
||||||
`https://${
|
`https://${
|
||||||
api_domain ? api_domain.replace(/\/$/gi, "") : "wakatime.com"
|
api_domain ? api_domain.replace(/\/$/gi, "") : "wakatime.com"
|
||||||
}/api/v1/users/${username}/stats/${
|
}/api/v1/users/${username}/stats/${range || ""}?is_including_today=true`,
|
||||||
range || "all_time"
|
|
||||||
}?is_including_today=true`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return data.data;
|
return data.data;
|
||||||
|
|||||||
+3
-5
@@ -65,11 +65,10 @@ const getAnimations = () => {
|
|||||||
/**
|
/**
|
||||||
* Retrieves CSS styles for a card.
|
* Retrieves CSS styles for a card.
|
||||||
*
|
*
|
||||||
* @param {Object} colors The colors to use for the card.
|
* @param {Object[]} colors The colors to use for the card.
|
||||||
* @param {string} colors.titleColor The title color.
|
* @param {string} colors.titleColor The title color.
|
||||||
* @param {string} colors.textColor The text color.
|
* @param {string} colors.textColor The text color.
|
||||||
* @param {string} colors.iconColor The icon color.
|
* @param {string} colors.iconColor The icon color.
|
||||||
* @param {string} colors.ringColor The ring color.
|
|
||||||
* @param {boolean} colors.show_icons Whether to show icons.
|
* @param {boolean} colors.show_icons Whether to show icons.
|
||||||
* @param {number} colors.progress The progress value to animate to.
|
* @param {number} colors.progress The progress value to animate to.
|
||||||
* @returns {string} Card CSS styles.
|
* @returns {string} Card CSS styles.
|
||||||
@@ -78,7 +77,6 @@ const getStyles = ({
|
|||||||
titleColor,
|
titleColor,
|
||||||
textColor,
|
textColor,
|
||||||
iconColor,
|
iconColor,
|
||||||
ringColor,
|
|
||||||
show_icons,
|
show_icons,
|
||||||
progress,
|
progress,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -107,13 +105,13 @@ const getStyles = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rank-circle-rim {
|
.rank-circle-rim {
|
||||||
stroke: ${ringColor};
|
stroke: ${titleColor};
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke-width: 6;
|
stroke-width: 6;
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
.rank-circle {
|
.rank-circle {
|
||||||
stroke: ${ringColor};
|
stroke: ${titleColor};
|
||||||
stroke-dasharray: 250;
|
stroke-dasharray: 250;
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke-width: 6;
|
stroke-width: 6;
|
||||||
|
|||||||
+6
-6
@@ -37,7 +37,7 @@ const statCardLocales = ({ name, apostrophe }) => {
|
|||||||
sk: `GitHub štatistiky používateľa ${encodedName}`,
|
sk: `GitHub štatistiky používateľa ${encodedName}`,
|
||||||
tr: `${encodedName} Hesabının GitHub Yıldızları`,
|
tr: `${encodedName} Hesabının GitHub Yıldızları`,
|
||||||
pl: `Statystyki GitHub użytkownika ${encodedName}`,
|
pl: `Statystyki GitHub użytkownika ${encodedName}`,
|
||||||
uz: `${encodedName}ning GitHub'dagi statistikasi`,
|
uz: `${encodedName}ning Github'dagi statistikasi`,
|
||||||
vi: `Thống Kê GitHub ${encodedName}`,
|
vi: `Thống Kê GitHub ${encodedName}`,
|
||||||
se: `GitHubstatistik för ${encodedName}`,
|
se: `GitHubstatistik för ${encodedName}`,
|
||||||
},
|
},
|
||||||
@@ -67,7 +67,7 @@ const statCardLocales = ({ name, apostrophe }) => {
|
|||||||
my: "Jumlah Bintang",
|
my: "Jumlah Bintang",
|
||||||
sk: "Hviezdy",
|
sk: "Hviezdy",
|
||||||
tr: "Toplam Yıldız",
|
tr: "Toplam Yıldız",
|
||||||
pl: "Liczba otrzymanych gwiazdek",
|
pl: "Liczba Gwiazdek dostanych",
|
||||||
uz: "Yulduzchalar",
|
uz: "Yulduzchalar",
|
||||||
vi: "Tổng Số Sao",
|
vi: "Tổng Số Sao",
|
||||||
se: "Antal intjänade stjärnor",
|
se: "Antal intjänade stjärnor",
|
||||||
@@ -160,7 +160,7 @@ const statCardLocales = ({ name, apostrophe }) => {
|
|||||||
my: "Jumlah Isu Dilaporkan",
|
my: "Jumlah Isu Dilaporkan",
|
||||||
sk: "Všetky problémy",
|
sk: "Všetky problémy",
|
||||||
tr: "Toplam Hata",
|
tr: "Toplam Hata",
|
||||||
pl: "Wszystkie problemy",
|
pl: "Wszystkie Issues",
|
||||||
uz: "'Issue'lar",
|
uz: "'Issue'lar",
|
||||||
vi: "Tổng Số Vấn Đề",
|
vi: "Tổng Số Vấn Đề",
|
||||||
se: "Total antal issues",
|
se: "Total antal issues",
|
||||||
@@ -322,7 +322,7 @@ const wakatimeCardLocales = {
|
|||||||
my: "Statistik Wakatime",
|
my: "Statistik Wakatime",
|
||||||
sk: "Wakatime štatistika",
|
sk: "Wakatime štatistika",
|
||||||
tr: "Waketime İstatistikler",
|
tr: "Waketime İstatistikler",
|
||||||
pl: "Statystyki Wakatime",
|
pl: "statystyki Wakatime",
|
||||||
vi: "Thống Kê Wakatime",
|
vi: "Thống Kê Wakatime",
|
||||||
se: "Wakatime statistik",
|
se: "Wakatime statistik",
|
||||||
},
|
},
|
||||||
@@ -367,9 +367,9 @@ const availableLocales = Object.keys(repoCardLocales["repocard.archived"]);
|
|||||||
* @param {string} locale The locale to check.
|
* @param {string} locale The locale to check.
|
||||||
* @returns {boolean} Boolean specifying whether the locale is available or not.
|
* @returns {boolean} Boolean specifying whether the locale is available or not.
|
||||||
*/
|
*/
|
||||||
const isLocaleAvailable = (locale) => {
|
function isLocaleAvailable(locale) {
|
||||||
return availableLocales.includes(locale.toLowerCase());
|
return availableLocales.includes(locale.toLowerCase());
|
||||||
};
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
isLocaleAvailable,
|
isLocaleAvailable,
|
||||||
|
|||||||
@@ -51,13 +51,13 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rank-circle-rim {
|
.rank-circle-rim {
|
||||||
stroke: undefined;
|
stroke: #2f80ed;
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke-width: 6;
|
stroke-width: 6;
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
.rank-circle {
|
.rank-circle {
|
||||||
stroke: undefined;
|
stroke: #2f80ed;
|
||||||
stroke-dasharray: 250;
|
stroke-dasharray: 250;
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke-width: 6;
|
stroke-width: 6;
|
||||||
@@ -69,29 +69,7 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@keyframes slideInAnimation {
|
|
||||||
from {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
width: calc(100%-100px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes growWidthAnimation {
|
|
||||||
from {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58 }
|
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58 }
|
||||||
#rect-mask rect{
|
|
||||||
animation: slideInAnimation 1s ease-in-out forwards;
|
|
||||||
}
|
|
||||||
.lang-progress{
|
|
||||||
animation: growWidthAnimation 0.6s ease-in-out forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -144,7 +122,7 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1
|
|||||||
data-testid="lang-progress"
|
data-testid="lang-progress"
|
||||||
x="0"
|
x="0"
|
||||||
y="0"
|
y="0"
|
||||||
width="415.61699999999996"
|
width="6.6495"
|
||||||
height="8"
|
height="8"
|
||||||
fill="#858585"
|
fill="#858585"
|
||||||
/>
|
/>
|
||||||
@@ -152,189 +130,9 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1
|
|||||||
<rect
|
<rect
|
||||||
mask="url(#rect-mask)"
|
mask="url(#rect-mask)"
|
||||||
data-testid="lang-progress"
|
data-testid="lang-progress"
|
||||||
x="415.61699999999996"
|
x="6.6495"
|
||||||
y="0"
|
y="0"
|
||||||
width="29.0625"
|
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>
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`Test Render Wakatime Card should render correctly with compact layout when langs_count is set 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: undefined;
|
|
||||||
fill: none;
|
|
||||||
stroke-width: 6;
|
|
||||||
opacity: 0.2;
|
|
||||||
}
|
|
||||||
.rank-circle {
|
|
||||||
stroke: undefined;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@keyframes slideInAnimation {
|
|
||||||
from {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
width: calc(100%-100px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@keyframes growWidthAnimation {
|
|
||||||
from {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58 }
|
|
||||||
#rect-mask rect{
|
|
||||||
animation: slideInAnimation 1s ease-in-out forwards;
|
|
||||||
}
|
|
||||||
.lang-progress{
|
|
||||||
animation: growWidthAnimation 0.6s ease-in-out forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</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="436.4025"
|
|
||||||
height="8"
|
|
||||||
fill="#858585"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<rect
|
|
||||||
mask="url(#rect-mask)"
|
|
||||||
data-testid="lang-progress"
|
|
||||||
x="436.4025"
|
|
||||||
y="0"
|
|
||||||
width="30.503999999999998"
|
|
||||||
height="8"
|
height="8"
|
||||||
fill="#3178c6"
|
fill="#3178c6"
|
||||||
/>
|
/>
|
||||||
|
|||||||
+54
-70
@@ -12,21 +12,20 @@ const stats = {
|
|||||||
totalCommits: 200,
|
totalCommits: 200,
|
||||||
totalIssues: 300,
|
totalIssues: 300,
|
||||||
totalPRs: 400,
|
totalPRs: 400,
|
||||||
contributedTo: 50,
|
contributedTo: 500,
|
||||||
rank: null,
|
rank: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
stats.rank = calculateRank({
|
stats.rank = calculateRank({
|
||||||
all_commits: false,
|
totalCommits: stats.totalCommits,
|
||||||
commits: stats.totalCommits,
|
totalRepos: 1,
|
||||||
|
followers: 0,
|
||||||
|
contributions: stats.contributedTo,
|
||||||
|
stargazers: stats.totalStars,
|
||||||
prs: stats.totalPRs,
|
prs: stats.totalPRs,
|
||||||
issues: stats.totalIssues,
|
issues: stats.totalIssues,
|
||||||
repos: 1,
|
|
||||||
stars: stats.totalStars,
|
|
||||||
followers: 0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const data_stats = {
|
const data = {
|
||||||
data: {
|
data: {
|
||||||
user: {
|
user: {
|
||||||
name: stats.name,
|
name: stats.name,
|
||||||
@@ -41,10 +40,19 @@ const data_stats = {
|
|||||||
followers: { totalCount: 0 },
|
followers: { totalCount: 0 },
|
||||||
repositories: {
|
repositories: {
|
||||||
totalCount: 1,
|
totalCount: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const repositoriesData = {
|
||||||
|
data: {
|
||||||
|
user: {
|
||||||
|
repositories: {
|
||||||
nodes: [{ stargazers: { totalCount: 100 } }],
|
nodes: [{ stargazers: { totalCount: 100 } }],
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
hasNextPage: false,
|
hasNextPage: false,
|
||||||
endCursor: "cursor",
|
cursor: "cursor",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -75,7 +83,11 @@ const faker = (query, data) => {
|
|||||||
setHeader: jest.fn(),
|
setHeader: jest.fn(),
|
||||||
send: jest.fn(),
|
send: jest.fn(),
|
||||||
};
|
};
|
||||||
mock.onPost("https://api.github.com/graphql").replyOnce(200, data);
|
mock
|
||||||
|
.onPost("https://api.github.com/graphql")
|
||||||
|
.replyOnce(200, data)
|
||||||
|
.onPost("https://api.github.com/graphql")
|
||||||
|
.replyOnce(200, repositoriesData);
|
||||||
|
|
||||||
return { req, res };
|
return { req, res };
|
||||||
};
|
};
|
||||||
@@ -86,7 +98,7 @@ afterEach(() => {
|
|||||||
|
|
||||||
describe("Test /api/", () => {
|
describe("Test /api/", () => {
|
||||||
it("should test the request", async () => {
|
it("should test the request", async () => {
|
||||||
const { req, res } = faker({}, data_stats);
|
const { req, res } = faker({}, data);
|
||||||
|
|
||||||
await api(req, res);
|
await api(req, res);
|
||||||
|
|
||||||
@@ -121,7 +133,7 @@ describe("Test /api/", () => {
|
|||||||
text_color: "fff",
|
text_color: "fff",
|
||||||
bg_color: "fff",
|
bg_color: "fff",
|
||||||
},
|
},
|
||||||
data_stats,
|
data,
|
||||||
);
|
);
|
||||||
|
|
||||||
await api(req, res);
|
await api(req, res);
|
||||||
@@ -142,33 +154,23 @@ describe("Test /api/", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should have proper cache", async () => {
|
it("should have proper cache", async () => {
|
||||||
const { req, res } = faker({}, data_stats);
|
const { req, res } = faker({}, data);
|
||||||
|
|
||||||
await api(req, res);
|
await api(req, res);
|
||||||
|
|
||||||
expect(res.setHeader.mock.calls).toEqual([
|
expect(res.setHeader.mock.calls).toEqual([
|
||||||
["Content-Type", "image/svg+xml"],
|
["Content-Type", "image/svg+xml"],
|
||||||
[
|
["Cache-Control", `public, max-age=${CONSTANTS.FOUR_HOURS}`],
|
||||||
"Cache-Control",
|
|
||||||
`max-age=${CONSTANTS.FOUR_HOURS / 2}, s-maxage=${
|
|
||||||
CONSTANTS.FOUR_HOURS
|
|
||||||
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
|
||||||
],
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set proper cache", async () => {
|
it("should set proper cache", async () => {
|
||||||
const { req, res } = faker({ cache_seconds: 15000 }, data_stats);
|
const { req, res } = faker({ cache_seconds: 15000 }, data);
|
||||||
await api(req, res);
|
await api(req, res);
|
||||||
|
|
||||||
expect(res.setHeader.mock.calls).toEqual([
|
expect(res.setHeader.mock.calls).toEqual([
|
||||||
["Content-Type", "image/svg+xml"],
|
["Content-Type", "image/svg+xml"],
|
||||||
[
|
["Cache-Control", `public, max-age=${15000}`],
|
||||||
"Cache-Control",
|
|
||||||
`max-age=7500, s-maxage=${15000}, stale-while-revalidate=${
|
|
||||||
CONSTANTS.ONE_DAY
|
|
||||||
}`,
|
|
||||||
],
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -178,90 +180,72 @@ describe("Test /api/", () => {
|
|||||||
|
|
||||||
expect(res.setHeader.mock.calls).toEqual([
|
expect(res.setHeader.mock.calls).toEqual([
|
||||||
["Content-Type", "image/svg+xml"],
|
["Content-Type", "image/svg+xml"],
|
||||||
["Cache-Control", `no-cache, no-store, must-revalidate`],
|
["Cache-Control", `no-store`],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should set proper cache with clamped values", async () => {
|
it("should set proper cache with clamped values", async () => {
|
||||||
{
|
{
|
||||||
let { req, res } = faker({ cache_seconds: 200000 }, data_stats);
|
let { req, res } = faker({ cache_seconds: 200000 }, data);
|
||||||
await api(req, res);
|
await api(req, res);
|
||||||
|
|
||||||
expect(res.setHeader.mock.calls).toEqual([
|
expect(res.setHeader.mock.calls).toEqual([
|
||||||
["Content-Type", "image/svg+xml"],
|
["Content-Type", "image/svg+xml"],
|
||||||
[
|
["Cache-Control", `public, max-age=${CONSTANTS.ONE_DAY}`],
|
||||||
"Cache-Control",
|
|
||||||
`max-age=${CONSTANTS.ONE_DAY / 2}, s-maxage=${
|
|
||||||
CONSTANTS.ONE_DAY
|
|
||||||
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
|
||||||
],
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// note i'm using block scoped vars
|
// note i'm using block scoped vars
|
||||||
{
|
{
|
||||||
let { req, res } = faker({ cache_seconds: 0 }, data_stats);
|
let { req, res } = faker({ cache_seconds: 0 }, data);
|
||||||
await api(req, res);
|
await api(req, res);
|
||||||
|
|
||||||
expect(res.setHeader.mock.calls).toEqual([
|
expect(res.setHeader.mock.calls).toEqual([
|
||||||
["Content-Type", "image/svg+xml"],
|
["Content-Type", "image/svg+xml"],
|
||||||
[
|
["Cache-Control", `public, max-age=${CONSTANTS.FOUR_HOURS}`],
|
||||||
"Cache-Control",
|
|
||||||
`max-age=${CONSTANTS.FOUR_HOURS / 2}, s-maxage=${
|
|
||||||
CONSTANTS.FOUR_HOURS
|
|
||||||
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
|
||||||
],
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let { req, res } = faker({ cache_seconds: -10000 }, data_stats);
|
let { req, res } = faker({ cache_seconds: -10000 }, data);
|
||||||
await api(req, res);
|
await api(req, res);
|
||||||
|
|
||||||
expect(res.setHeader.mock.calls).toEqual([
|
expect(res.setHeader.mock.calls).toEqual([
|
||||||
["Content-Type", "image/svg+xml"],
|
["Content-Type", "image/svg+xml"],
|
||||||
[
|
["Cache-Control", `public, max-age=${CONSTANTS.FOUR_HOURS}`],
|
||||||
"Cache-Control",
|
|
||||||
`max-age=${CONSTANTS.FOUR_HOURS / 2}, s-maxage=${
|
|
||||||
CONSTANTS.FOUR_HOURS
|
|
||||||
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
|
||||||
],
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow changing ring_color", async () => {
|
it("should add private contributions", async () => {
|
||||||
const { req, res } = faker(
|
const { req, res } = faker(
|
||||||
{
|
{
|
||||||
username: "anuraghazra",
|
username: "anuraghazra",
|
||||||
hide: "issues,prs,contribs",
|
count_private: true,
|
||||||
show_icons: true,
|
|
||||||
hide_border: true,
|
|
||||||
line_height: 100,
|
|
||||||
title_color: "fff",
|
|
||||||
ring_color: "0000ff",
|
|
||||||
icon_color: "fff",
|
|
||||||
text_color: "fff",
|
|
||||||
bg_color: "fff",
|
|
||||||
},
|
},
|
||||||
data_stats,
|
data,
|
||||||
);
|
);
|
||||||
|
|
||||||
await api(req, res);
|
await api(req, res);
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
||||||
expect(res.send).toBeCalledWith(
|
expect(res.send).toBeCalledWith(
|
||||||
renderStatsCard(stats, {
|
renderStatsCard(
|
||||||
hide: ["issues", "prs", "contribs"],
|
{
|
||||||
show_icons: true,
|
...stats,
|
||||||
hide_border: true,
|
totalCommits: stats.totalCommits + 100,
|
||||||
line_height: 100,
|
rank: calculateRank({
|
||||||
title_color: "fff",
|
totalCommits: stats.totalCommits + 100,
|
||||||
ring_color: "0000ff",
|
totalRepos: 1,
|
||||||
icon_color: "fff",
|
followers: 0,
|
||||||
text_color: "fff",
|
contributions: stats.contributedTo,
|
||||||
bg_color: "fff",
|
stargazers: stats.totalStars,
|
||||||
}),
|
prs: stats.totalPRs,
|
||||||
|
issues: stats.totalIssues,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,87 +2,17 @@ import "@testing-library/jest-dom";
|
|||||||
import { calculateRank } from "../src/calculateRank.js";
|
import { calculateRank } from "../src/calculateRank.js";
|
||||||
|
|
||||||
describe("Test calculateRank", () => {
|
describe("Test calculateRank", () => {
|
||||||
it("new user gets B rank", () => {
|
it("should calculate rank correctly", () => {
|
||||||
expect(
|
expect(
|
||||||
calculateRank({
|
calculateRank({
|
||||||
all_commits: false,
|
totalCommits: 100,
|
||||||
commits: 0,
|
totalRepos: 5,
|
||||||
prs: 0,
|
|
||||||
issues: 0,
|
|
||||||
repos: 0,
|
|
||||||
stars: 0,
|
|
||||||
followers: 0,
|
|
||||||
}),
|
|
||||||
).toStrictEqual({ level: "B", score: 100 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("average user gets A rank", () => {
|
|
||||||
expect(
|
|
||||||
calculateRank({
|
|
||||||
all_commits: false,
|
|
||||||
commits: 250,
|
|
||||||
prs: 50,
|
|
||||||
issues: 25,
|
|
||||||
repos: 0,
|
|
||||||
stars: 250,
|
|
||||||
followers: 25,
|
|
||||||
}),
|
|
||||||
).toStrictEqual({ level: "A", score: 50 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("average user gets A rank (include_all_commits)", () => {
|
|
||||||
expect(
|
|
||||||
calculateRank({
|
|
||||||
all_commits: true,
|
|
||||||
commits: 1000,
|
|
||||||
prs: 50,
|
|
||||||
issues: 25,
|
|
||||||
repos: 0,
|
|
||||||
stars: 250,
|
|
||||||
followers: 25,
|
|
||||||
}),
|
|
||||||
).toStrictEqual({ level: "A", score: 50 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("more than average user gets A+ rank", () => {
|
|
||||||
expect(
|
|
||||||
calculateRank({
|
|
||||||
all_commits: false,
|
|
||||||
commits: 500,
|
|
||||||
prs: 100,
|
|
||||||
issues: 50,
|
|
||||||
repos: 0,
|
|
||||||
stars: 500,
|
|
||||||
followers: 50,
|
|
||||||
}),
|
|
||||||
).toStrictEqual({ level: "A+", score: 25 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("expert user gets S rank", () => {
|
|
||||||
expect(
|
|
||||||
calculateRank({
|
|
||||||
all_commits: false,
|
|
||||||
commits: 1000,
|
|
||||||
prs: 200,
|
|
||||||
issues: 100,
|
|
||||||
repos: 0,
|
|
||||||
stars: 1000,
|
|
||||||
followers: 100,
|
followers: 100,
|
||||||
|
contributions: 61,
|
||||||
|
stargazers: 400,
|
||||||
|
prs: 300,
|
||||||
|
issues: 200,
|
||||||
}),
|
}),
|
||||||
).toStrictEqual({ level: "S", score: 6.25 });
|
).toStrictEqual({ level: "A+", score: 49.16605417270399 });
|
||||||
});
|
|
||||||
|
|
||||||
it("ezyang gets S+ rank", () => {
|
|
||||||
expect(
|
|
||||||
calculateRank({
|
|
||||||
all_commits: false,
|
|
||||||
commits: 1000,
|
|
||||||
prs: 4000,
|
|
||||||
issues: 2000,
|
|
||||||
repos: 0,
|
|
||||||
stars: 5000,
|
|
||||||
followers: 2000,
|
|
||||||
}),
|
|
||||||
).toStrictEqual({ level: "S+", score: 1.1363983154296875 });
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ describe("Card", () => {
|
|||||||
"#fff",
|
"#fff",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render gradient backgrounds", () => {
|
it("should render gradient backgrounds", () => {
|
||||||
const { titleColor, textColor, iconColor, bgColor } = getCardColors({
|
const { titleColor, textColor, iconColor, bgColor } = getCardColors({
|
||||||
title_color: "f00",
|
title_color: "f00",
|
||||||
|
|||||||
+24
-19
@@ -11,22 +11,27 @@ import { renderStatsCard } from "../../src/cards/stats-card.js";
|
|||||||
import { renderTopLanguages } from "../../src/cards/top-languages-card.js";
|
import { renderTopLanguages } from "../../src/cards/top-languages-card.js";
|
||||||
import { renderWakatimeCard } from "../../src/cards/wakatime-card.js";
|
import { renderWakatimeCard } from "../../src/cards/wakatime-card.js";
|
||||||
|
|
||||||
const REPO = "curly-fiesta";
|
const REPO = "dummy-cra";
|
||||||
const USER = "catelinemnemosyne";
|
const USER = "grsdummy";
|
||||||
const STATS_DATA = {
|
const STATS_DATA = {
|
||||||
name: "Cateline Mnemosyne",
|
name: "grsdummy",
|
||||||
totalPRs: 2,
|
totalPRs: 2,
|
||||||
totalCommits: 8,
|
totalCommits: 2,
|
||||||
totalIssues: 1,
|
totalIssues: 1,
|
||||||
totalStars: 1,
|
totalStars: 1,
|
||||||
contributedTo: 1,
|
contributedTo: 2,
|
||||||
rank: {
|
rank: {
|
||||||
level: "A+",
|
level: "A+",
|
||||||
score: 50.88831151384285,
|
score: 51.01013099671447,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const LANGS_DATA = {
|
const LANGS_DATA = {
|
||||||
|
TypeScript: {
|
||||||
|
color: "#3178c6",
|
||||||
|
name: "TypeScript",
|
||||||
|
size: 2049,
|
||||||
|
},
|
||||||
HTML: {
|
HTML: {
|
||||||
color: "#e34c26",
|
color: "#e34c26",
|
||||||
name: "HTML",
|
name: "HTML",
|
||||||
@@ -37,19 +42,19 @@ const LANGS_DATA = {
|
|||||||
name: "CSS",
|
name: "CSS",
|
||||||
size: 930,
|
size: 930,
|
||||||
},
|
},
|
||||||
JavaScript: {
|
Python: {
|
||||||
color: "#f1e05a",
|
color: "#3572A5",
|
||||||
name: "JavaScript",
|
name: "Python",
|
||||||
size: 1912,
|
size: 671,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const WAKATIME_DATA = {
|
const WAKATIME_DATA = {
|
||||||
human_readable_range: "last week",
|
human_readable_range: "last week",
|
||||||
is_already_updating: false,
|
is_already_updating: false,
|
||||||
is_coding_activity_visible: true,
|
is_coding_activity_visible: false,
|
||||||
is_including_today: false,
|
is_including_today: false,
|
||||||
is_other_usage_visible: true,
|
is_other_usage_visible: false,
|
||||||
is_stuck: false,
|
is_stuck: false,
|
||||||
is_up_to_date: false,
|
is_up_to_date: false,
|
||||||
is_up_to_date_pending_future: false,
|
is_up_to_date_pending_future: false,
|
||||||
@@ -57,24 +62,24 @@ const WAKATIME_DATA = {
|
|||||||
range: "last_7_days",
|
range: "last_7_days",
|
||||||
status: "pending_update",
|
status: "pending_update",
|
||||||
timeout: 15,
|
timeout: 15,
|
||||||
username: USER,
|
username: "grsdummy",
|
||||||
writes_only: false,
|
writes_only: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const REPOSITORY_DATA = {
|
const REPOSITORY_DATA = {
|
||||||
name: REPO,
|
name: "dummy-cra",
|
||||||
nameWithOwner: `${USER}/cra-test`,
|
nameWithOwner: "grsdummy/dummy-cra",
|
||||||
isPrivate: false,
|
isPrivate: false,
|
||||||
isArchived: false,
|
isArchived: false,
|
||||||
isTemplate: false,
|
isTemplate: false,
|
||||||
stargazers: {
|
stargazers: {
|
||||||
totalCount: 1,
|
totalCount: 1,
|
||||||
},
|
},
|
||||||
description: "Simple cra test repo.",
|
description: "Dummy create react app.",
|
||||||
primaryLanguage: {
|
primaryLanguage: {
|
||||||
color: "#f1e05a",
|
color: "#3178c6",
|
||||||
id: "MDg6TGFuZ3VhZ2UxNDA=",
|
id: "MDg6TGFuZ3VhZ2UyODc=",
|
||||||
name: "JavaScript",
|
name: "TypeScript",
|
||||||
},
|
},
|
||||||
forkCount: 0,
|
forkCount: 0,
|
||||||
starCount: 1,
|
starCount: 1,
|
||||||
|
|||||||
+87
-123
@@ -4,8 +4,7 @@ import MockAdapter from "axios-mock-adapter";
|
|||||||
import { calculateRank } from "../src/calculateRank.js";
|
import { calculateRank } from "../src/calculateRank.js";
|
||||||
import { fetchStats } from "../src/fetchers/stats-fetcher.js";
|
import { fetchStats } from "../src/fetchers/stats-fetcher.js";
|
||||||
|
|
||||||
// Test parameters.
|
const data = {
|
||||||
const data_stats = {
|
|
||||||
data: {
|
data: {
|
||||||
user: {
|
user: {
|
||||||
name: "Anurag Hazra",
|
name: "Anurag Hazra",
|
||||||
@@ -20,6 +19,15 @@ const data_stats = {
|
|||||||
followers: { totalCount: 100 },
|
followers: { totalCount: 100 },
|
||||||
repositories: {
|
repositories: {
|
||||||
totalCount: 5,
|
totalCount: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const firstRepositoriesData = {
|
||||||
|
data: {
|
||||||
|
user: {
|
||||||
|
repositories: {
|
||||||
nodes: [
|
nodes: [
|
||||||
{ name: "test-repo-1", stargazers: { totalCount: 100 } },
|
{ name: "test-repo-1", stargazers: { totalCount: 100 } },
|
||||||
{ name: "test-repo-2", stargazers: { totalCount: 100 } },
|
{ name: "test-repo-2", stargazers: { totalCount: 100 } },
|
||||||
@@ -27,14 +35,14 @@ const data_stats = {
|
|||||||
],
|
],
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
hasNextPage: true,
|
hasNextPage: true,
|
||||||
endCursor: "cursor",
|
cursor: "cursor",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const data_repo = {
|
const secondRepositoriesData = {
|
||||||
data: {
|
data: {
|
||||||
user: {
|
user: {
|
||||||
repositories: {
|
repositories: {
|
||||||
@@ -44,14 +52,14 @@ const data_repo = {
|
|||||||
],
|
],
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
hasNextPage: false,
|
hasNextPage: false,
|
||||||
endCursor: "cursor",
|
cursor: "cursor",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const data_repo_zero_stars = {
|
const repositoriesWithZeroStarsData = {
|
||||||
data: {
|
data: {
|
||||||
user: {
|
user: {
|
||||||
repositories: {
|
repositories: {
|
||||||
@@ -64,7 +72,7 @@ const data_repo_zero_stars = {
|
|||||||
],
|
],
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
hasNextPage: true,
|
hasNextPage: true,
|
||||||
endCursor: "cursor",
|
cursor: "cursor",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -85,13 +93,13 @@ const error = {
|
|||||||
const mock = new MockAdapter(axios);
|
const mock = new MockAdapter(axios);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
process.env.FETCH_MULTI_PAGE_STARS = "false"; // Set to `false` to fetch only one page of stars.
|
mock
|
||||||
mock.onPost("https://api.github.com/graphql").reply((cfg) => {
|
.onPost("https://api.github.com/graphql")
|
||||||
return [
|
.replyOnce(200, data)
|
||||||
200,
|
.onPost("https://api.github.com/graphql")
|
||||||
cfg.data.includes("contributionsCollection") ? data_stats : data_repo,
|
.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.
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -102,13 +110,14 @@ describe("Test fetchStats", () => {
|
|||||||
it("should fetch correct stats", async () => {
|
it("should fetch correct stats", async () => {
|
||||||
let stats = await fetchStats("anuraghazra");
|
let stats = await fetchStats("anuraghazra");
|
||||||
const rank = calculateRank({
|
const rank = calculateRank({
|
||||||
all_commits: false,
|
totalCommits: 100,
|
||||||
commits: 100,
|
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.
|
||||||
prs: 300,
|
prs: 300,
|
||||||
issues: 200,
|
issues: 200,
|
||||||
repos: 5,
|
|
||||||
stars: 300,
|
|
||||||
followers: 100,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
expect(stats).toStrictEqual({
|
||||||
@@ -117,7 +126,8 @@ describe("Test fetchStats", () => {
|
|||||||
totalCommits: 100,
|
totalCommits: 100,
|
||||||
totalIssues: 200,
|
totalIssues: 200,
|
||||||
totalPRs: 300,
|
totalPRs: 300,
|
||||||
totalStars: 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.
|
||||||
rank,
|
rank,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -126,19 +136,19 @@ describe("Test fetchStats", () => {
|
|||||||
mock.reset();
|
mock.reset();
|
||||||
mock
|
mock
|
||||||
.onPost("https://api.github.com/graphql")
|
.onPost("https://api.github.com/graphql")
|
||||||
.replyOnce(200, data_stats)
|
.replyOnce(200, data)
|
||||||
.onPost("https://api.github.com/graphql")
|
.onPost("https://api.github.com/graphql")
|
||||||
.replyOnce(200, data_repo_zero_stars);
|
.replyOnce(200, repositoriesWithZeroStarsData);
|
||||||
|
|
||||||
let stats = await fetchStats("anuraghazra");
|
let stats = await fetchStats("anuraghazra");
|
||||||
const rank = calculateRank({
|
const rank = calculateRank({
|
||||||
all_commits: false,
|
totalCommits: 100,
|
||||||
commits: 100,
|
totalRepos: 5,
|
||||||
|
followers: 100,
|
||||||
|
contributions: 61,
|
||||||
|
stargazers: 300,
|
||||||
prs: 300,
|
prs: 300,
|
||||||
issues: 200,
|
issues: 200,
|
||||||
repos: 5,
|
|
||||||
stars: 300,
|
|
||||||
followers: 100,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
expect(stats).toStrictEqual({
|
||||||
@@ -161,29 +171,56 @@ describe("Test fetchStats", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fetch total commits", async () => {
|
it("should fetch and add private contributions", async () => {
|
||||||
mock
|
let stats = await fetchStats("anuraghazra", true);
|
||||||
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
|
|
||||||
.reply(200, { total_count: 1000 });
|
|
||||||
|
|
||||||
let stats = await fetchStats("anuraghazra", false, true);
|
|
||||||
const rank = calculateRank({
|
const rank = calculateRank({
|
||||||
all_commits: true,
|
totalCommits: 150,
|
||||||
commits: 1000,
|
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.
|
||||||
prs: 300,
|
prs: 300,
|
||||||
issues: 200,
|
issues: 200,
|
||||||
repos: 5,
|
|
||||||
stars: 300,
|
|
||||||
followers: 100,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
expect(stats).toStrictEqual({
|
||||||
contributedTo: 61,
|
contributedTo: 61,
|
||||||
name: "Anurag Hazra",
|
name: "Anurag Hazra",
|
||||||
totalCommits: 1000,
|
totalCommits: 150,
|
||||||
totalIssues: 200,
|
totalIssues: 200,
|
||||||
totalPRs: 300,
|
totalPRs: 300,
|
||||||
totalStars: 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.
|
||||||
|
rank,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fetch total commits", async () => {
|
||||||
|
mock
|
||||||
|
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
|
||||||
|
.reply(200, { total_count: 1000 });
|
||||||
|
|
||||||
|
let stats = await fetchStats("anuraghazra", true, true);
|
||||||
|
const rank = calculateRank({
|
||||||
|
totalCommits: 1050,
|
||||||
|
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.
|
||||||
|
prs: 300,
|
||||||
|
issues: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(stats).toStrictEqual({
|
||||||
|
contributedTo: 61,
|
||||||
|
name: "Anurag Hazra",
|
||||||
|
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.
|
||||||
rank,
|
rank,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -193,99 +230,26 @@ describe("Test fetchStats", () => {
|
|||||||
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
|
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
|
||||||
.reply(200, { total_count: 1000 });
|
.reply(200, { total_count: 1000 });
|
||||||
|
|
||||||
let stats = await fetchStats("anuraghazra", false, true, ["test-repo-1"]);
|
let stats = await fetchStats("anuraghazra", true, true, ["test-repo-1"]);
|
||||||
const rank = calculateRank({
|
const rank = calculateRank({
|
||||||
all_commits: true,
|
totalCommits: 1050,
|
||||||
commits: 1000,
|
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.
|
||||||
prs: 300,
|
prs: 300,
|
||||||
issues: 200,
|
issues: 200,
|
||||||
repos: 5,
|
|
||||||
stars: 200,
|
|
||||||
followers: 100,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
expect(stats).toStrictEqual({
|
||||||
contributedTo: 61,
|
contributedTo: 61,
|
||||||
name: "Anurag Hazra",
|
name: "Anurag Hazra",
|
||||||
totalCommits: 1000,
|
totalCommits: 1050,
|
||||||
totalIssues: 200,
|
totalIssues: 200,
|
||||||
totalPRs: 300,
|
totalPRs: 300,
|
||||||
totalStars: 200,
|
// totalStars: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||||
rank,
|
totalStars: 200, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130.
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fetch two pages of stars if 'FETCH_MULTI_PAGE_STARS' env variable is set to `true`", async () => {
|
|
||||||
process.env.FETCH_MULTI_PAGE_STARS = true;
|
|
||||||
|
|
||||||
let stats = await fetchStats("anuraghazra");
|
|
||||||
const rank = calculateRank({
|
|
||||||
all_commits: false,
|
|
||||||
commits: 100,
|
|
||||||
prs: 300,
|
|
||||||
issues: 200,
|
|
||||||
repos: 5,
|
|
||||||
stars: 400,
|
|
||||||
followers: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
|
||||||
contributedTo: 61,
|
|
||||||
name: "Anurag Hazra",
|
|
||||||
totalCommits: 100,
|
|
||||||
totalIssues: 200,
|
|
||||||
totalPRs: 300,
|
|
||||||
totalStars: 400,
|
|
||||||
rank,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fetch one page of stars if 'FETCH_MULTI_PAGE_STARS' env variable is set to `false`", async () => {
|
|
||||||
process.env.FETCH_MULTI_PAGE_STARS = "false";
|
|
||||||
|
|
||||||
let stats = await fetchStats("anuraghazra");
|
|
||||||
const rank = calculateRank({
|
|
||||||
all_commits: false,
|
|
||||||
commits: 100,
|
|
||||||
prs: 300,
|
|
||||||
issues: 200,
|
|
||||||
repos: 5,
|
|
||||||
stars: 300,
|
|
||||||
followers: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
|
||||||
contributedTo: 61,
|
|
||||||
name: "Anurag Hazra",
|
|
||||||
totalCommits: 100,
|
|
||||||
totalIssues: 200,
|
|
||||||
totalPRs: 300,
|
|
||||||
totalStars: 300,
|
|
||||||
rank,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should fetch one page of stars if 'FETCH_MULTI_PAGE_STARS' env variable is not set", async () => {
|
|
||||||
process.env.FETCH_MULTI_PAGE_STARS = undefined;
|
|
||||||
|
|
||||||
let stats = await fetchStats("anuraghazra");
|
|
||||||
const rank = calculateRank({
|
|
||||||
all_commits: false,
|
|
||||||
commits: 100,
|
|
||||||
prs: 300,
|
|
||||||
issues: 200,
|
|
||||||
repos: 5,
|
|
||||||
stars: 300,
|
|
||||||
followers: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(stats).toStrictEqual({
|
|
||||||
contributedTo: 61,
|
|
||||||
name: "Anurag Hazra",
|
|
||||||
totalCommits: 100,
|
|
||||||
totalIssues: 200,
|
|
||||||
totalPRs: 300,
|
|
||||||
totalStars: 300,
|
|
||||||
rank,
|
rank,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -60,22 +60,20 @@ const error = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe("FetchTopLanguages", () => {
|
describe("FetchTopLanguages", () => {
|
||||||
it("should fetch correct language data while using the new calculation", async () => {
|
it("should fetch correct language data", async () => {
|
||||||
mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
|
mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
|
||||||
|
|
||||||
let repo = await fetchTopLanguages("anuraghazra", [], 0.5, 0.5);
|
let repo = await fetchTopLanguages("anuraghazra");
|
||||||
expect(repo).toStrictEqual({
|
expect(repo).toStrictEqual({
|
||||||
HTML: {
|
HTML: {
|
||||||
color: "#0f0",
|
color: "#0f0",
|
||||||
count: 2,
|
|
||||||
name: "HTML",
|
name: "HTML",
|
||||||
size: 20.000000000000004,
|
size: 200,
|
||||||
},
|
},
|
||||||
javascript: {
|
javascript: {
|
||||||
color: "#0ff",
|
color: "#0ff",
|
||||||
count: 2,
|
|
||||||
name: "javascript",
|
name: "javascript",
|
||||||
size: 20.000000000000004,
|
size: 200,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -87,59 +85,17 @@ describe("FetchTopLanguages", () => {
|
|||||||
expect(repo).toStrictEqual({
|
expect(repo).toStrictEqual({
|
||||||
HTML: {
|
HTML: {
|
||||||
color: "#0f0",
|
color: "#0f0",
|
||||||
count: 1,
|
|
||||||
name: "HTML",
|
name: "HTML",
|
||||||
size: 100,
|
size: 100,
|
||||||
},
|
},
|
||||||
javascript: {
|
javascript: {
|
||||||
color: "#0ff",
|
color: "#0ff",
|
||||||
count: 2,
|
|
||||||
name: "javascript",
|
name: "javascript",
|
||||||
size: 200,
|
size: 200,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fetch correct language data while using the old calculation", async () => {
|
|
||||||
mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
|
|
||||||
|
|
||||||
let repo = await fetchTopLanguages("anuraghazra", [], 1, 0);
|
|
||||||
expect(repo).toStrictEqual({
|
|
||||||
HTML: {
|
|
||||||
color: "#0f0",
|
|
||||||
count: 2,
|
|
||||||
name: "HTML",
|
|
||||||
size: 200,
|
|
||||||
},
|
|
||||||
javascript: {
|
|
||||||
color: "#0ff",
|
|
||||||
count: 2,
|
|
||||||
name: "javascript",
|
|
||||||
size: 200,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should rank languages by the number of repositories they appear in", async () => {
|
|
||||||
mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
|
|
||||||
|
|
||||||
let repo = await fetchTopLanguages("anuraghazra", [], 0, 1);
|
|
||||||
expect(repo).toStrictEqual({
|
|
||||||
HTML: {
|
|
||||||
color: "#0f0",
|
|
||||||
count: 2,
|
|
||||||
name: "HTML",
|
|
||||||
size: 2,
|
|
||||||
},
|
|
||||||
javascript: {
|
|
||||||
color: "#0ff",
|
|
||||||
count: 2,
|
|
||||||
name: "javascript",
|
|
||||||
size: 2,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should throw error", async () => {
|
it("should throw error", async () => {
|
||||||
mock.onPost("https://api.github.com/graphql").reply(200, error);
|
mock.onPost("https://api.github.com/graphql").reply(200, error);
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ describe("Wakatime fetcher", () => {
|
|||||||
const username = "anuraghazra";
|
const username = "anuraghazra";
|
||||||
mock
|
mock
|
||||||
.onGet(
|
.onGet(
|
||||||
`https://wakatime.com/api/v1/users/${username}/stats/all_time?is_including_today=true`,
|
`https://wakatime.com/api/v1/users/${username}/stats/?is_including_today=true`,
|
||||||
)
|
)
|
||||||
.reply(200, wakaTimeData);
|
.reply(200, wakaTimeData);
|
||||||
|
|
||||||
|
|||||||
@@ -1,244 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Tests for the status/pat-info cloud function.
|
|
||||||
*/
|
|
||||||
import dotenv from "dotenv";
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
import { jest } from "@jest/globals";
|
|
||||||
import axios from "axios";
|
|
||||||
import MockAdapter from "axios-mock-adapter";
|
|
||||||
import patInfo, { RATE_LIMIT_SECONDS } from "../api/status/pat-info.js";
|
|
||||||
|
|
||||||
const mock = new MockAdapter(axios);
|
|
||||||
|
|
||||||
const successData = {
|
|
||||||
data: {
|
|
||||||
rateLimit: {
|
|
||||||
remaining: 4986,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const faker = (query) => {
|
|
||||||
const req = {
|
|
||||||
query: { ...query },
|
|
||||||
};
|
|
||||||
const res = {
|
|
||||||
setHeader: jest.fn(),
|
|
||||||
send: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return { req, res };
|
|
||||||
};
|
|
||||||
|
|
||||||
const rate_limit_error = {
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: "RATE_LIMITED",
|
|
||||||
message: "API rate limit exceeded for user ID.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
data: {
|
|
||||||
rateLimit: {
|
|
||||||
resetAt: Date.now(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const other_error = {
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: "SOME_ERROR",
|
|
||||||
message: "This is a error",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const bad_credentials_error = {
|
|
||||||
message: "Bad credentials",
|
|
||||||
};
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mock.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Test /api/status/pat-info", () => {
|
|
||||||
beforeAll(() => {
|
|
||||||
// reset patenv first so that dotenv doesn't populate them with local envs
|
|
||||||
process.env = {};
|
|
||||||
process.env.PAT_1 = "testPAT1";
|
|
||||||
process.env.PAT_2 = "testPAT2";
|
|
||||||
process.env.PAT_3 = "testPAT3";
|
|
||||||
process.env.PAT_4 = "testPAT4";
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return only 'validPATs' if all PATs are valid", async () => {
|
|
||||||
mock
|
|
||||||
.onPost("https://api.github.com/graphql")
|
|
||||||
.replyOnce(200, rate_limit_error)
|
|
||||||
.onPost("https://api.github.com/graphql")
|
|
||||||
.reply(200, successData);
|
|
||||||
|
|
||||||
const { req, res } = faker({}, {});
|
|
||||||
await patInfo(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
|
||||||
expect(res.send).toBeCalledWith(
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
validPATs: ["PAT_2", "PAT_3", "PAT_4"],
|
|
||||||
expiredPATs: [],
|
|
||||||
exhaustedPATs: ["PAT_1"],
|
|
||||||
suspendedPATs: [],
|
|
||||||
errorPATs: [],
|
|
||||||
details: {
|
|
||||||
PAT_1: {
|
|
||||||
status: "exhausted",
|
|
||||||
remaining: 0,
|
|
||||||
resetIn: "0 minutes",
|
|
||||||
},
|
|
||||||
PAT_2: {
|
|
||||||
status: "valid",
|
|
||||||
remaining: 4986,
|
|
||||||
},
|
|
||||||
PAT_3: {
|
|
||||||
status: "valid",
|
|
||||||
remaining: 4986,
|
|
||||||
},
|
|
||||||
PAT_4: {
|
|
||||||
status: "valid",
|
|
||||||
remaining: 4986,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return `errorPATs` if a PAT causes an error to be thrown", async () => {
|
|
||||||
mock
|
|
||||||
.onPost("https://api.github.com/graphql")
|
|
||||||
.replyOnce(200, other_error)
|
|
||||||
.onPost("https://api.github.com/graphql")
|
|
||||||
.reply(200, successData);
|
|
||||||
|
|
||||||
const { req, res } = faker({}, {});
|
|
||||||
await patInfo(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
|
||||||
expect(res.send).toBeCalledWith(
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
validPATs: ["PAT_2", "PAT_3", "PAT_4"],
|
|
||||||
expiredPATs: [],
|
|
||||||
exhaustedPATs: [],
|
|
||||||
suspendedPATs: [],
|
|
||||||
errorPATs: ["PAT_1"],
|
|
||||||
details: {
|
|
||||||
PAT_1: {
|
|
||||||
status: "error",
|
|
||||||
error: {
|
|
||||||
type: "SOME_ERROR",
|
|
||||||
message: "This is a error",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PAT_2: {
|
|
||||||
status: "valid",
|
|
||||||
remaining: 4986,
|
|
||||||
},
|
|
||||||
PAT_3: {
|
|
||||||
status: "valid",
|
|
||||||
remaining: 4986,
|
|
||||||
},
|
|
||||||
PAT_4: {
|
|
||||||
status: "valid",
|
|
||||||
remaining: 4986,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return `expiredPaths` if a PAT returns a 'Bad credentials' error", async () => {
|
|
||||||
mock
|
|
||||||
.onPost("https://api.github.com/graphql")
|
|
||||||
.replyOnce(404, bad_credentials_error)
|
|
||||||
.onPost("https://api.github.com/graphql")
|
|
||||||
.reply(200, successData);
|
|
||||||
|
|
||||||
const { req, res } = faker({}, {});
|
|
||||||
await patInfo(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
|
||||||
expect(res.send).toBeCalledWith(
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
validPATs: ["PAT_2", "PAT_3", "PAT_4"],
|
|
||||||
expiredPATs: ["PAT_1"],
|
|
||||||
exhaustedPATs: [],
|
|
||||||
suspendedPATs: [],
|
|
||||||
errorPATs: [],
|
|
||||||
details: {
|
|
||||||
PAT_1: {
|
|
||||||
status: "expired",
|
|
||||||
},
|
|
||||||
PAT_2: {
|
|
||||||
status: "valid",
|
|
||||||
remaining: 4986,
|
|
||||||
},
|
|
||||||
PAT_3: {
|
|
||||||
status: "valid",
|
|
||||||
remaining: 4986,
|
|
||||||
},
|
|
||||||
PAT_4: {
|
|
||||||
status: "valid",
|
|
||||||
remaining: 4986,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should throw an error if something goes wrong", async () => {
|
|
||||||
mock.onPost("https://api.github.com/graphql").networkError();
|
|
||||||
|
|
||||||
const { req, res } = faker({}, {});
|
|
||||||
await patInfo(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
|
||||||
expect(res.send).toBeCalledWith("Something went wrong: Network Error");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should have proper cache when no error is thrown", async () => {
|
|
||||||
mock.onPost("https://api.github.com/graphql").reply(200, successData);
|
|
||||||
|
|
||||||
const { req, res } = faker({}, {});
|
|
||||||
await patInfo(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader.mock.calls).toEqual([
|
|
||||||
["Content-Type", "application/json"],
|
|
||||||
["Cache-Control", `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should have proper cache when error is thrown", async () => {
|
|
||||||
mock.reset();
|
|
||||||
mock.onPost("https://api.github.com/graphql").networkError();
|
|
||||||
|
|
||||||
const { req, res } = faker({}, {});
|
|
||||||
await patInfo(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader.mock.calls).toEqual([
|
|
||||||
["Content-Type", "application/json"],
|
|
||||||
["Cache-Control", "no-store"],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -78,17 +78,16 @@ describe("Test renderStatsCard", () => {
|
|||||||
|
|
||||||
it("should render with custom width set", () => {
|
it("should render with custom width set", () => {
|
||||||
document.body.innerHTML = renderStatsCard(stats);
|
document.body.innerHTML = renderStatsCard(stats);
|
||||||
expect(document.querySelector("svg")).toHaveAttribute("width", "450");
|
expect(document.querySelector("svg")).toHaveAttribute("width", "495");
|
||||||
|
|
||||||
document.body.innerHTML = renderStatsCard(stats, { card_width: 500 });
|
document.body.innerHTML = renderStatsCard(stats, { card_width: 400 });
|
||||||
expect(document.querySelector("svg")).toHaveAttribute("width", "500");
|
expect(document.querySelector("svg")).toHaveAttribute("width", "400");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render with custom width set and limit minimum width", () => {
|
it("should render with custom width set and limit minimum width", () => {
|
||||||
document.body.innerHTML = renderStatsCard(stats, { card_width: 1 });
|
document.body.innerHTML = renderStatsCard(stats, { card_width: 1 });
|
||||||
expect(document.querySelector("svg")).toHaveAttribute("width", "420");
|
expect(document.querySelector("svg")).toHaveAttribute("width", "340");
|
||||||
|
|
||||||
// Test default minimum card width without rank circle.
|
|
||||||
document.body.innerHTML = renderStatsCard(stats, {
|
document.body.innerHTML = renderStatsCard(stats, {
|
||||||
card_width: 1,
|
card_width: 1,
|
||||||
hide_rank: true,
|
hide_rank: true,
|
||||||
@@ -98,7 +97,6 @@ describe("Test renderStatsCard", () => {
|
|||||||
"305.81250000000006",
|
"305.81250000000006",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test minimum card width with rank and icons.
|
|
||||||
document.body.innerHTML = renderStatsCard(stats, {
|
document.body.innerHTML = renderStatsCard(stats, {
|
||||||
card_width: 1,
|
card_width: 1,
|
||||||
hide_rank: true,
|
hide_rank: true,
|
||||||
@@ -106,24 +104,22 @@ describe("Test renderStatsCard", () => {
|
|||||||
});
|
});
|
||||||
expect(document.querySelector("svg")).toHaveAttribute(
|
expect(document.querySelector("svg")).toHaveAttribute(
|
||||||
"width",
|
"width",
|
||||||
"322.81250000000006",
|
"305.81250000000006",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test minimum card width with icons but without rank.
|
|
||||||
document.body.innerHTML = renderStatsCard(stats, {
|
document.body.innerHTML = renderStatsCard(stats, {
|
||||||
card_width: 1,
|
card_width: 1,
|
||||||
hide_rank: false,
|
hide_rank: false,
|
||||||
show_icons: true,
|
show_icons: true,
|
||||||
});
|
});
|
||||||
expect(document.querySelector("svg")).toHaveAttribute("width", "437");
|
expect(document.querySelector("svg")).toHaveAttribute("width", "356");
|
||||||
|
|
||||||
// Test minimum card width without icons or rank.
|
|
||||||
document.body.innerHTML = renderStatsCard(stats, {
|
document.body.innerHTML = renderStatsCard(stats, {
|
||||||
card_width: 1,
|
card_width: 1,
|
||||||
hide_rank: false,
|
hide_rank: false,
|
||||||
show_icons: false,
|
show_icons: false,
|
||||||
});
|
});
|
||||||
expect(document.querySelector("svg")).toHaveAttribute("width", "420");
|
expect(document.querySelector("svg")).toHaveAttribute("width", "340");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render default colors properly", () => {
|
it("should render default colors properly", () => {
|
||||||
@@ -243,39 +239,6 @@ describe("Test renderStatsCard", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render custom ring_color properly", () => {
|
|
||||||
const customColors = {
|
|
||||||
title_color: "5a0",
|
|
||||||
ring_color: "0000ff",
|
|
||||||
icon_color: "1b998b",
|
|
||||||
text_color: "9991",
|
|
||||||
bg_color: "252525",
|
|
||||||
};
|
|
||||||
|
|
||||||
document.body.innerHTML = renderStatsCard(stats, { ...customColors });
|
|
||||||
|
|
||||||
const styleTag = document.querySelector("style");
|
|
||||||
const stylesObject = cssToObject(styleTag.innerHTML);
|
|
||||||
|
|
||||||
const headerClassStyles = stylesObject[":host"][".header "];
|
|
||||||
const statClassStyles = stylesObject[":host"][".stat "];
|
|
||||||
const iconClassStyles = stylesObject[":host"][".icon "];
|
|
||||||
const rankCircleStyles = stylesObject[":host"][".rank-circle "];
|
|
||||||
const rankCircleRimStyles = stylesObject[":host"][".rank-circle-rim "];
|
|
||||||
|
|
||||||
expect(headerClassStyles.fill.trim()).toBe(`#${customColors.title_color}`);
|
|
||||||
expect(statClassStyles.fill.trim()).toBe(`#${customColors.text_color}`);
|
|
||||||
expect(iconClassStyles.fill.trim()).toBe(`#${customColors.icon_color}`);
|
|
||||||
expect(rankCircleStyles.stroke.trim()).toBe(`#${customColors.ring_color}`);
|
|
||||||
expect(rankCircleRimStyles.stroke.trim()).toBe(
|
|
||||||
`#${customColors.ring_color}`,
|
|
||||||
);
|
|
||||||
expect(queryByTestId(document.body, "card-bg")).toHaveAttribute(
|
|
||||||
"fill",
|
|
||||||
"#252525",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render icons correctly", () => {
|
it("should render icons correctly", () => {
|
||||||
document.body.innerHTML = renderStatsCard(stats, {
|
document.body.innerHTML = renderStatsCard(stats, {
|
||||||
show_icons: true,
|
show_icons: true,
|
||||||
@@ -316,7 +279,7 @@ describe("Test renderStatsCard", () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
document.body.getElementsByTagName("svg")[0].getAttribute("width"),
|
document.body.getElementsByTagName("svg")[0].getAttribute("width"),
|
||||||
).toBe("287");
|
).toBe("270");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render translations", () => {
|
it("should render translations", () => {
|
||||||
@@ -333,7 +296,7 @@ describe("Test renderStatsCard", () => {
|
|||||||
document.querySelector(
|
document.querySelector(
|
||||||
'g[transform="translate(0, 25)"]>.stagger>.stat.bold',
|
'g[transform="translate(0, 25)"]>.stagger>.stat.bold',
|
||||||
).textContent,
|
).textContent,
|
||||||
).toMatchInlineSnapshot(`"累计提交数(commit) (2023):"`);
|
).toMatchInlineSnapshot(`"累计提交数(commit) (2022):"`);
|
||||||
expect(
|
expect(
|
||||||
document.querySelector(
|
document.querySelector(
|
||||||
'g[transform="translate(0, 50)"]>.stagger>.stat.bold',
|
'g[transform="translate(0, 50)"]>.stagger>.stat.bold',
|
||||||
@@ -348,7 +311,7 @@ describe("Test renderStatsCard", () => {
|
|||||||
document.querySelector(
|
document.querySelector(
|
||||||
'g[transform="translate(0, 100)"]>.stagger>.stat.bold',
|
'g[transform="translate(0, 100)"]>.stagger>.stat.bold',
|
||||||
).textContent,
|
).textContent,
|
||||||
).toMatchInlineSnapshot(`"参与项目数 (last year):"`);
|
).toMatchInlineSnapshot(`"参与项目数:"`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render without rounding", () => {
|
it("should render without rounding", () => {
|
||||||
@@ -357,30 +320,4 @@ describe("Test renderStatsCard", () => {
|
|||||||
document.body.innerHTML = renderStatsCard(stats, {});
|
document.body.innerHTML = renderStatsCard(stats, {});
|
||||||
expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5");
|
expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should shorten values", () => {
|
|
||||||
stats["totalCommits"] = 1999;
|
|
||||||
|
|
||||||
document.body.innerHTML = renderStatsCard(stats);
|
|
||||||
expect(getByTestId(document.body, "commits").textContent).toBe("2k");
|
|
||||||
document.body.innerHTML = renderStatsCard(stats, { number_format: "long" });
|
|
||||||
expect(getByTestId(document.body, "commits").textContent).toBe("1999");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render default rank icon with level A+", () => {
|
|
||||||
document.body.innerHTML = renderStatsCard(stats, {
|
|
||||||
rank_icon: "default",
|
|
||||||
});
|
|
||||||
expect(queryByTestId(document.body, "level-rank-icon")).toBeDefined();
|
|
||||||
expect(
|
|
||||||
queryByTestId(document.body, "level-rank-icon").textContent.trim(),
|
|
||||||
).toBe("A+");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render github rank icon", () => {
|
|
||||||
document.body.innerHTML = renderStatsCard(stats, {
|
|
||||||
rank_icon: "github",
|
|
||||||
});
|
|
||||||
expect(queryByTestId(document.body, "github-rank-icon")).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +1,9 @@
|
|||||||
import { queryAllByTestId, queryByTestId } from "@testing-library/dom";
|
import { queryAllByTestId, queryByTestId } from "@testing-library/dom";
|
||||||
import { cssToObject } from "@uppercod/css-to-object";
|
import { cssToObject } from "@uppercod/css-to-object";
|
||||||
import {
|
import {
|
||||||
getLongestLang,
|
|
||||||
degreesToRadians,
|
|
||||||
radiansToDegrees,
|
|
||||||
polarToCartesian,
|
|
||||||
cartesianToPolar,
|
|
||||||
calculateCompactLayoutHeight,
|
|
||||||
calculateNormalLayoutHeight,
|
|
||||||
calculateDonutLayoutHeight,
|
|
||||||
calculatePieLayoutHeight,
|
|
||||||
donutCenterTranslation,
|
|
||||||
trimTopLanguages,
|
|
||||||
renderTopLanguages,
|
|
||||||
MIN_CARD_WIDTH,
|
MIN_CARD_WIDTH,
|
||||||
|
renderTopLanguages,
|
||||||
} from "../src/cards/top-languages-card.js";
|
} from "../src/cards/top-languages-card.js";
|
||||||
|
|
||||||
// adds special assertions like toHaveTextContent
|
// adds special assertions like toHaveTextContent
|
||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
|
|
||||||
@@ -39,255 +27,6 @@ const langs = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve number array from SVG path definition string.
|
|
||||||
*
|
|
||||||
* @param {string} d SVG path definition string.
|
|
||||||
* @return {number[]} Resulting numbers array.
|
|
||||||
*/
|
|
||||||
const getNumbersFromSvgPathDefinitionAttribute = (d) => {
|
|
||||||
return d
|
|
||||||
.split(" ")
|
|
||||||
.filter((x) => !isNaN(x))
|
|
||||||
.map((x) => parseFloat(x));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the language percentage from the donut chart SVG.
|
|
||||||
*
|
|
||||||
* @param {string} d The SVG path element.
|
|
||||||
* @param {number} centerX The center X coordinate of the donut chart.
|
|
||||||
* @param {number} centerY The center Y coordinate of the donut chart.
|
|
||||||
* @returns {number} The percentage of the language.
|
|
||||||
*/
|
|
||||||
const langPercentFromDonutLayoutSvg = (d, centerX, centerY) => {
|
|
||||||
const dTmp = getNumbersFromSvgPathDefinitionAttribute(d);
|
|
||||||
const endAngle =
|
|
||||||
cartesianToPolar(centerX, centerY, dTmp[0], dTmp[1]).angleInDegrees + 90;
|
|
||||||
let startAngle =
|
|
||||||
cartesianToPolar(centerX, centerY, dTmp[7], dTmp[8]).angleInDegrees + 90;
|
|
||||||
if (startAngle > endAngle) startAngle -= 360;
|
|
||||||
return (endAngle - startAngle) / 3.6;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the language percentage from the pie chart SVG.
|
|
||||||
*
|
|
||||||
* @param {string} d The SVG path element.
|
|
||||||
* @param {number} centerX The center X coordinate of the pie chart.
|
|
||||||
* @param {number} centerY The center Y coordinate of the pie chart.
|
|
||||||
* @returns {number} The percentage of the language.
|
|
||||||
*/
|
|
||||||
const langPercentFromPieLayoutSvg = (d, centerX, centerY) => {
|
|
||||||
const dTmp = getNumbersFromSvgPathDefinitionAttribute(d);
|
|
||||||
const startAngle = cartesianToPolar(
|
|
||||||
centerX,
|
|
||||||
centerY,
|
|
||||||
dTmp[2],
|
|
||||||
dTmp[3],
|
|
||||||
).angleInDegrees;
|
|
||||||
let endAngle = cartesianToPolar(
|
|
||||||
centerX,
|
|
||||||
centerY,
|
|
||||||
dTmp[9],
|
|
||||||
dTmp[10],
|
|
||||||
).angleInDegrees;
|
|
||||||
return ((endAngle - startAngle) / 360) * 100;
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("Test renderTopLanguages helper functions", () => {
|
|
||||||
it("getLongestLang", () => {
|
|
||||||
const langArray = Object.values(langs);
|
|
||||||
expect(getLongestLang(langArray)).toBe(langs.javascript);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("degreesToRadians", () => {
|
|
||||||
expect(degreesToRadians(0)).toBe(0);
|
|
||||||
expect(degreesToRadians(90)).toBe(Math.PI / 2);
|
|
||||||
expect(degreesToRadians(180)).toBe(Math.PI);
|
|
||||||
expect(degreesToRadians(270)).toBe((3 * Math.PI) / 2);
|
|
||||||
expect(degreesToRadians(360)).toBe(2 * Math.PI);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("radiansToDegrees", () => {
|
|
||||||
expect(radiansToDegrees(0)).toBe(0);
|
|
||||||
expect(radiansToDegrees(Math.PI / 2)).toBe(90);
|
|
||||||
expect(radiansToDegrees(Math.PI)).toBe(180);
|
|
||||||
expect(radiansToDegrees((3 * Math.PI) / 2)).toBe(270);
|
|
||||||
expect(radiansToDegrees(2 * Math.PI)).toBe(360);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("polarToCartesian", () => {
|
|
||||||
expect(polarToCartesian(100, 100, 60, 0)).toStrictEqual({ x: 160, y: 100 });
|
|
||||||
expect(polarToCartesian(100, 100, 60, 45)).toStrictEqual({
|
|
||||||
x: 142.42640687119285,
|
|
||||||
y: 142.42640687119285,
|
|
||||||
});
|
|
||||||
expect(polarToCartesian(100, 100, 60, 90)).toStrictEqual({
|
|
||||||
x: 100,
|
|
||||||
y: 160,
|
|
||||||
});
|
|
||||||
expect(polarToCartesian(100, 100, 60, 135)).toStrictEqual({
|
|
||||||
x: 57.573593128807154,
|
|
||||||
y: 142.42640687119285,
|
|
||||||
});
|
|
||||||
expect(polarToCartesian(100, 100, 60, 180)).toStrictEqual({
|
|
||||||
x: 40,
|
|
||||||
y: 100.00000000000001,
|
|
||||||
});
|
|
||||||
expect(polarToCartesian(100, 100, 60, 225)).toStrictEqual({
|
|
||||||
x: 57.57359312880714,
|
|
||||||
y: 57.573593128807154,
|
|
||||||
});
|
|
||||||
expect(polarToCartesian(100, 100, 60, 270)).toStrictEqual({
|
|
||||||
x: 99.99999999999999,
|
|
||||||
y: 40,
|
|
||||||
});
|
|
||||||
expect(polarToCartesian(100, 100, 60, 315)).toStrictEqual({
|
|
||||||
x: 142.42640687119285,
|
|
||||||
y: 57.57359312880714,
|
|
||||||
});
|
|
||||||
expect(polarToCartesian(100, 100, 60, 360)).toStrictEqual({
|
|
||||||
x: 160,
|
|
||||||
y: 99.99999999999999,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cartesianToPolar", () => {
|
|
||||||
expect(cartesianToPolar(100, 100, 160, 100)).toStrictEqual({
|
|
||||||
radius: 60,
|
|
||||||
angleInDegrees: 0,
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
cartesianToPolar(100, 100, 142.42640687119285, 142.42640687119285),
|
|
||||||
).toStrictEqual({ radius: 60.00000000000001, angleInDegrees: 45 });
|
|
||||||
expect(cartesianToPolar(100, 100, 100, 160)).toStrictEqual({
|
|
||||||
radius: 60,
|
|
||||||
angleInDegrees: 90,
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
cartesianToPolar(100, 100, 57.573593128807154, 142.42640687119285),
|
|
||||||
).toStrictEqual({ radius: 60, angleInDegrees: 135 });
|
|
||||||
expect(cartesianToPolar(100, 100, 40, 100.00000000000001)).toStrictEqual({
|
|
||||||
radius: 60,
|
|
||||||
angleInDegrees: 180,
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
cartesianToPolar(100, 100, 57.57359312880714, 57.573593128807154),
|
|
||||||
).toStrictEqual({ radius: 60, angleInDegrees: 225 });
|
|
||||||
expect(cartesianToPolar(100, 100, 99.99999999999999, 40)).toStrictEqual({
|
|
||||||
radius: 60,
|
|
||||||
angleInDegrees: 270,
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
cartesianToPolar(100, 100, 142.42640687119285, 57.57359312880714),
|
|
||||||
).toStrictEqual({ radius: 60.00000000000001, angleInDegrees: 315 });
|
|
||||||
expect(cartesianToPolar(100, 100, 160, 99.99999999999999)).toStrictEqual({
|
|
||||||
radius: 60,
|
|
||||||
angleInDegrees: 360,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calculateCompactLayoutHeight", () => {
|
|
||||||
expect(calculateCompactLayoutHeight(0)).toBe(90);
|
|
||||||
expect(calculateCompactLayoutHeight(1)).toBe(115);
|
|
||||||
expect(calculateCompactLayoutHeight(2)).toBe(115);
|
|
||||||
expect(calculateCompactLayoutHeight(3)).toBe(140);
|
|
||||||
expect(calculateCompactLayoutHeight(4)).toBe(140);
|
|
||||||
expect(calculateCompactLayoutHeight(5)).toBe(165);
|
|
||||||
expect(calculateCompactLayoutHeight(6)).toBe(165);
|
|
||||||
expect(calculateCompactLayoutHeight(7)).toBe(190);
|
|
||||||
expect(calculateCompactLayoutHeight(8)).toBe(190);
|
|
||||||
expect(calculateCompactLayoutHeight(9)).toBe(215);
|
|
||||||
expect(calculateCompactLayoutHeight(10)).toBe(215);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calculateNormalLayoutHeight", () => {
|
|
||||||
expect(calculateNormalLayoutHeight(0)).toBe(85);
|
|
||||||
expect(calculateNormalLayoutHeight(1)).toBe(125);
|
|
||||||
expect(calculateNormalLayoutHeight(2)).toBe(165);
|
|
||||||
expect(calculateNormalLayoutHeight(3)).toBe(205);
|
|
||||||
expect(calculateNormalLayoutHeight(4)).toBe(245);
|
|
||||||
expect(calculateNormalLayoutHeight(5)).toBe(285);
|
|
||||||
expect(calculateNormalLayoutHeight(6)).toBe(325);
|
|
||||||
expect(calculateNormalLayoutHeight(7)).toBe(365);
|
|
||||||
expect(calculateNormalLayoutHeight(8)).toBe(405);
|
|
||||||
expect(calculateNormalLayoutHeight(9)).toBe(445);
|
|
||||||
expect(calculateNormalLayoutHeight(10)).toBe(485);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calculateDonutLayoutHeight", () => {
|
|
||||||
expect(calculateDonutLayoutHeight(0)).toBe(215);
|
|
||||||
expect(calculateDonutLayoutHeight(1)).toBe(215);
|
|
||||||
expect(calculateDonutLayoutHeight(2)).toBe(215);
|
|
||||||
expect(calculateDonutLayoutHeight(3)).toBe(215);
|
|
||||||
expect(calculateDonutLayoutHeight(4)).toBe(215);
|
|
||||||
expect(calculateDonutLayoutHeight(5)).toBe(215);
|
|
||||||
expect(calculateDonutLayoutHeight(6)).toBe(247);
|
|
||||||
expect(calculateDonutLayoutHeight(7)).toBe(279);
|
|
||||||
expect(calculateDonutLayoutHeight(8)).toBe(311);
|
|
||||||
expect(calculateDonutLayoutHeight(9)).toBe(343);
|
|
||||||
expect(calculateDonutLayoutHeight(10)).toBe(375);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calculatePieLayoutHeight", () => {
|
|
||||||
expect(calculatePieLayoutHeight(0)).toBe(300);
|
|
||||||
expect(calculatePieLayoutHeight(1)).toBe(325);
|
|
||||||
expect(calculatePieLayoutHeight(2)).toBe(325);
|
|
||||||
expect(calculatePieLayoutHeight(3)).toBe(350);
|
|
||||||
expect(calculatePieLayoutHeight(4)).toBe(350);
|
|
||||||
expect(calculatePieLayoutHeight(5)).toBe(375);
|
|
||||||
expect(calculatePieLayoutHeight(6)).toBe(375);
|
|
||||||
expect(calculatePieLayoutHeight(7)).toBe(400);
|
|
||||||
expect(calculatePieLayoutHeight(8)).toBe(400);
|
|
||||||
expect(calculatePieLayoutHeight(9)).toBe(425);
|
|
||||||
expect(calculatePieLayoutHeight(10)).toBe(425);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("donutCenterTranslation", () => {
|
|
||||||
expect(donutCenterTranslation(0)).toBe(-45);
|
|
||||||
expect(donutCenterTranslation(1)).toBe(-45);
|
|
||||||
expect(donutCenterTranslation(2)).toBe(-45);
|
|
||||||
expect(donutCenterTranslation(3)).toBe(-45);
|
|
||||||
expect(donutCenterTranslation(4)).toBe(-45);
|
|
||||||
expect(donutCenterTranslation(5)).toBe(-45);
|
|
||||||
expect(donutCenterTranslation(6)).toBe(-29);
|
|
||||||
expect(donutCenterTranslation(7)).toBe(-13);
|
|
||||||
expect(donutCenterTranslation(8)).toBe(3);
|
|
||||||
expect(donutCenterTranslation(9)).toBe(19);
|
|
||||||
expect(donutCenterTranslation(10)).toBe(35);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("trimTopLanguages", () => {
|
|
||||||
expect(trimTopLanguages([])).toStrictEqual({
|
|
||||||
langs: [],
|
|
||||||
totalLanguageSize: 0,
|
|
||||||
});
|
|
||||||
expect(trimTopLanguages([langs.javascript])).toStrictEqual({
|
|
||||||
langs: [langs.javascript],
|
|
||||||
totalLanguageSize: 200,
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
trimTopLanguages([langs.javascript, langs.HTML], [], 5),
|
|
||||||
).toStrictEqual({
|
|
||||||
langs: [langs.javascript, langs.HTML],
|
|
||||||
totalLanguageSize: 400,
|
|
||||||
});
|
|
||||||
expect(trimTopLanguages(langs, [], 5)).toStrictEqual({
|
|
||||||
langs: Object.values(langs),
|
|
||||||
totalLanguageSize: 500,
|
|
||||||
});
|
|
||||||
expect(trimTopLanguages(langs, [], 2)).toStrictEqual({
|
|
||||||
langs: Object.values(langs).slice(0, 2),
|
|
||||||
totalLanguageSize: 400,
|
|
||||||
});
|
|
||||||
expect(trimTopLanguages(langs, ["javascript"], 5)).toStrictEqual({
|
|
||||||
langs: [langs.HTML, langs.css],
|
|
||||||
totalLanguageSize: 300,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Test renderTopLanguages", () => {
|
describe("Test renderTopLanguages", () => {
|
||||||
it("should render correctly", () => {
|
it("should render correctly", () => {
|
||||||
document.body.innerHTML = renderTopLanguages(langs);
|
document.body.innerHTML = renderTopLanguages(langs);
|
||||||
@@ -477,7 +216,7 @@ describe("Test renderTopLanguages", () => {
|
|||||||
);
|
);
|
||||||
expect(queryAllByTestId(document.body, "lang-progress")[0]).toHaveAttribute(
|
expect(queryAllByTestId(document.body, "lang-progress")[0]).toHaveAttribute(
|
||||||
"width",
|
"width",
|
||||||
"100",
|
"120",
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent(
|
expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent(
|
||||||
@@ -485,7 +224,7 @@ describe("Test renderTopLanguages", () => {
|
|||||||
);
|
);
|
||||||
expect(queryAllByTestId(document.body, "lang-progress")[1]).toHaveAttribute(
|
expect(queryAllByTestId(document.body, "lang-progress")[1]).toHaveAttribute(
|
||||||
"width",
|
"width",
|
||||||
"100",
|
"120",
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent(
|
expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent(
|
||||||
@@ -493,153 +232,7 @@ describe("Test renderTopLanguages", () => {
|
|||||||
);
|
);
|
||||||
expect(queryAllByTestId(document.body, "lang-progress")[2]).toHaveAttribute(
|
expect(queryAllByTestId(document.body, "lang-progress")[2]).toHaveAttribute(
|
||||||
"width",
|
"width",
|
||||||
"50",
|
"60",
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render with layout donut", () => {
|
|
||||||
document.body.innerHTML = renderTopLanguages(langs, { layout: "donut" });
|
|
||||||
|
|
||||||
expect(queryByTestId(document.body, "header")).toHaveTextContent(
|
|
||||||
"Most Used Languages",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
|
|
||||||
"HTML 40.00%",
|
|
||||||
);
|
|
||||||
expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute(
|
|
||||||
"size",
|
|
||||||
"40",
|
|
||||||
);
|
|
||||||
const d = getNumbersFromSvgPathDefinitionAttribute(
|
|
||||||
queryAllByTestId(document.body, "lang-donut")[0].getAttribute("d"),
|
|
||||||
);
|
|
||||||
const center = { x: d[7], y: d[7] };
|
|
||||||
const HTMLLangPercent = langPercentFromDonutLayoutSvg(
|
|
||||||
queryAllByTestId(document.body, "lang-donut")[0].getAttribute("d"),
|
|
||||||
center.x,
|
|
||||||
center.y,
|
|
||||||
);
|
|
||||||
expect(HTMLLangPercent).toBeCloseTo(40);
|
|
||||||
|
|
||||||
expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent(
|
|
||||||
"javascript 40.00%",
|
|
||||||
);
|
|
||||||
expect(queryAllByTestId(document.body, "lang-donut")[1]).toHaveAttribute(
|
|
||||||
"size",
|
|
||||||
"40",
|
|
||||||
);
|
|
||||||
const javascriptLangPercent = langPercentFromDonutLayoutSvg(
|
|
||||||
queryAllByTestId(document.body, "lang-donut")[1].getAttribute("d"),
|
|
||||||
center.x,
|
|
||||||
center.y,
|
|
||||||
);
|
|
||||||
expect(javascriptLangPercent).toBeCloseTo(40);
|
|
||||||
|
|
||||||
expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent(
|
|
||||||
"css 20.00%",
|
|
||||||
);
|
|
||||||
expect(queryAllByTestId(document.body, "lang-donut")[2]).toHaveAttribute(
|
|
||||||
"size",
|
|
||||||
"20",
|
|
||||||
);
|
|
||||||
const cssLangPercent = langPercentFromDonutLayoutSvg(
|
|
||||||
queryAllByTestId(document.body, "lang-donut")[2].getAttribute("d"),
|
|
||||||
center.x,
|
|
||||||
center.y,
|
|
||||||
);
|
|
||||||
expect(cssLangPercent).toBeCloseTo(20);
|
|
||||||
|
|
||||||
expect(HTMLLangPercent + javascriptLangPercent + cssLangPercent).toBe(100);
|
|
||||||
|
|
||||||
// Should render full donut (circle) if one language is 100%.
|
|
||||||
document.body.innerHTML = renderTopLanguages(
|
|
||||||
{ HTML: langs.HTML },
|
|
||||||
{ layout: "donut" },
|
|
||||||
);
|
|
||||||
expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
|
|
||||||
"HTML 100.00%",
|
|
||||||
);
|
|
||||||
expect(queryAllByTestId(document.body, "lang-donut")[0]).toHaveAttribute(
|
|
||||||
"size",
|
|
||||||
"100",
|
|
||||||
);
|
|
||||||
expect(queryAllByTestId(document.body, "lang-donut")).toHaveLength(1);
|
|
||||||
expect(queryAllByTestId(document.body, "lang-donut")[0].tagName).toBe(
|
|
||||||
"circle",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it("should render with layout pie", () => {
|
|
||||||
document.body.innerHTML = renderTopLanguages(langs, { layout: "pie" });
|
|
||||||
|
|
||||||
expect(queryByTestId(document.body, "header")).toHaveTextContent(
|
|
||||||
"Most Used Languages",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
|
|
||||||
"HTML 40.00%",
|
|
||||||
);
|
|
||||||
expect(queryAllByTestId(document.body, "lang-pie")[0]).toHaveAttribute(
|
|
||||||
"size",
|
|
||||||
"40",
|
|
||||||
);
|
|
||||||
|
|
||||||
const d = getNumbersFromSvgPathDefinitionAttribute(
|
|
||||||
queryAllByTestId(document.body, "lang-pie")[0].getAttribute("d"),
|
|
||||||
);
|
|
||||||
const center = { x: d[0], y: d[1] };
|
|
||||||
const HTMLLangPercent = langPercentFromPieLayoutSvg(
|
|
||||||
queryAllByTestId(document.body, "lang-pie")[0].getAttribute("d"),
|
|
||||||
center.x,
|
|
||||||
center.y,
|
|
||||||
);
|
|
||||||
expect(HTMLLangPercent).toBeCloseTo(40);
|
|
||||||
|
|
||||||
expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent(
|
|
||||||
"javascript 40.00%",
|
|
||||||
);
|
|
||||||
expect(queryAllByTestId(document.body, "lang-pie")[1]).toHaveAttribute(
|
|
||||||
"size",
|
|
||||||
"40",
|
|
||||||
);
|
|
||||||
const javascriptLangPercent = langPercentFromPieLayoutSvg(
|
|
||||||
queryAllByTestId(document.body, "lang-pie")[1].getAttribute("d"),
|
|
||||||
center.x,
|
|
||||||
center.y,
|
|
||||||
);
|
|
||||||
expect(javascriptLangPercent).toBeCloseTo(40);
|
|
||||||
|
|
||||||
expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent(
|
|
||||||
"css 20.00%",
|
|
||||||
);
|
|
||||||
expect(queryAllByTestId(document.body, "lang-pie")[2]).toHaveAttribute(
|
|
||||||
"size",
|
|
||||||
"20",
|
|
||||||
);
|
|
||||||
const cssLangPercent = langPercentFromPieLayoutSvg(
|
|
||||||
queryAllByTestId(document.body, "lang-pie")[2].getAttribute("d"),
|
|
||||||
center.x,
|
|
||||||
center.y,
|
|
||||||
);
|
|
||||||
expect(cssLangPercent).toBeCloseTo(20);
|
|
||||||
|
|
||||||
expect(HTMLLangPercent + javascriptLangPercent + cssLangPercent).toBe(100);
|
|
||||||
|
|
||||||
// Should render full pie (circle) if one language is 100%.
|
|
||||||
document.body.innerHTML = renderTopLanguages(
|
|
||||||
{ HTML: langs.HTML },
|
|
||||||
{ layout: "pie" },
|
|
||||||
);
|
|
||||||
expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent(
|
|
||||||
"HTML 100.00%",
|
|
||||||
);
|
|
||||||
expect(queryAllByTestId(document.body, "lang-pie")[0]).toHaveAttribute(
|
|
||||||
"size",
|
|
||||||
"100",
|
|
||||||
);
|
|
||||||
expect(queryAllByTestId(document.body, "lang-pie")).toHaveLength(1);
|
|
||||||
expect(queryAllByTestId(document.body, "lang-pie")[0].tagName).toBe(
|
|
||||||
"circle",
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,15 +16,6 @@ describe("Test Render Wakatime Card", () => {
|
|||||||
expect(card).toMatchSnapshot();
|
expect(card).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render correctly with compact layout when langs_count is set", () => {
|
|
||||||
const card = renderWakatimeCard(wakaTimeData.data, {
|
|
||||||
layout: "compact",
|
|
||||||
langs_count: 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(card).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should hide languages when hide is passed", () => {
|
it("should hide languages when hide is passed", () => {
|
||||||
document.body.innerHTML = renderWakatimeCard(wakaTimeData.data, {
|
document.body.innerHTML = renderWakatimeCard(wakaTimeData.data, {
|
||||||
hide: ["YAML", "Other"],
|
hide: ["YAML", "Other"],
|
||||||
@@ -55,7 +46,7 @@ describe("Test Render Wakatime Card", () => {
|
|||||||
expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5");
|
expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show "no coding activity this week" message when there has not been activity', () => {
|
it('should show "no coding activitiy this week" message when there hasn not been activity', () => {
|
||||||
document.body.innerHTML = renderWakatimeCard(
|
document.body.innerHTML = renderWakatimeCard(
|
||||||
{
|
{
|
||||||
...wakaTimeData.data,
|
...wakaTimeData.data,
|
||||||
@@ -67,19 +58,4 @@ describe("Test Render Wakatime Card", () => {
|
|||||||
"No coding activity this week",
|
"No coding activity this week",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show "no coding activity this week" message when using compact layout and there has not been activity', () => {
|
|
||||||
document.body.innerHTML = renderWakatimeCard(
|
|
||||||
{
|
|
||||||
...wakaTimeData.data,
|
|
||||||
languages: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
layout: "compact",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
expect(document.querySelector(".stat").textContent).toBe(
|
|
||||||
"No coding activity this week",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,194 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Tests for the status/up cloud function.
|
|
||||||
*/
|
|
||||||
import { jest } from "@jest/globals";
|
|
||||||
import axios from "axios";
|
|
||||||
import MockAdapter from "axios-mock-adapter";
|
|
||||||
import up, { RATE_LIMIT_SECONDS } from "../api/status/up.js";
|
|
||||||
|
|
||||||
const mock = new MockAdapter(axios);
|
|
||||||
|
|
||||||
const successData = {
|
|
||||||
rateLimit: {
|
|
||||||
remaining: 4986,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const faker = (query) => {
|
|
||||||
const req = {
|
|
||||||
query: { ...query },
|
|
||||||
};
|
|
||||||
const res = {
|
|
||||||
setHeader: jest.fn(),
|
|
||||||
send: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
return { req, res };
|
|
||||||
};
|
|
||||||
|
|
||||||
const rate_limit_error = {
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
type: "RATE_LIMITED",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const bad_credentials_error = {
|
|
||||||
message: "Bad credentials",
|
|
||||||
};
|
|
||||||
|
|
||||||
const shields_up = {
|
|
||||||
schemaVersion: 1,
|
|
||||||
label: "Public Instance",
|
|
||||||
isError: true,
|
|
||||||
message: "up",
|
|
||||||
color: "brightgreen",
|
|
||||||
};
|
|
||||||
const shields_down = {
|
|
||||||
schemaVersion: 1,
|
|
||||||
label: "Public Instance",
|
|
||||||
isError: true,
|
|
||||||
message: "down",
|
|
||||||
color: "red",
|
|
||||||
};
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
mock.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Test /api/status/up", () => {
|
|
||||||
it("should return `true` if request was successful", async () => {
|
|
||||||
mock.onPost("https://api.github.com/graphql").replyOnce(200, successData);
|
|
||||||
|
|
||||||
const { req, res } = faker({}, {});
|
|
||||||
await up(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
|
||||||
expect(res.send).toBeCalledWith(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return `false` if all PATs are rate limited", async () => {
|
|
||||||
mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error);
|
|
||||||
|
|
||||||
const { req, res } = faker({}, {});
|
|
||||||
await up(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
|
||||||
expect(res.send).toBeCalledWith(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return JSON `true` if request was successful and type='json'", async () => {
|
|
||||||
mock.onPost("https://api.github.com/graphql").replyOnce(200, successData);
|
|
||||||
|
|
||||||
const { req, res } = faker({ type: "json" }, {});
|
|
||||||
await up(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
|
||||||
expect(res.send).toBeCalledWith({ up: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return JSON `false` if all PATs are rate limited and type='json'", async () => {
|
|
||||||
mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error);
|
|
||||||
|
|
||||||
const { req, res } = faker({ type: "json" }, {});
|
|
||||||
await up(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
|
||||||
expect(res.send).toBeCalledWith({ up: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return UP shields.io config if request was successful and type='shields'", async () => {
|
|
||||||
mock.onPost("https://api.github.com/graphql").replyOnce(200, successData);
|
|
||||||
|
|
||||||
const { req, res } = faker({ type: "shields" }, {});
|
|
||||||
await up(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
|
||||||
expect(res.send).toBeCalledWith(shields_up);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return DOWN shields.io config if all PATs are rate limited and type='shields'", async () => {
|
|
||||||
mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error);
|
|
||||||
|
|
||||||
const { req, res } = faker({ type: "shields" }, {});
|
|
||||||
await up(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
|
||||||
expect(res.send).toBeCalledWith(shields_down);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return `true` if the first PAT is rate limited but the second PATs works", async () => {
|
|
||||||
mock
|
|
||||||
.onPost("https://api.github.com/graphql")
|
|
||||||
.replyOnce(200, rate_limit_error)
|
|
||||||
.onPost("https://api.github.com/graphql")
|
|
||||||
.replyOnce(200, successData);
|
|
||||||
|
|
||||||
const { req, res } = faker({}, {});
|
|
||||||
await up(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
|
||||||
expect(res.send).toBeCalledWith(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return `true` if the first PAT has 'Bad credentials' but the second PAT works", async () => {
|
|
||||||
mock
|
|
||||||
.onPost("https://api.github.com/graphql")
|
|
||||||
.replyOnce(404, bad_credentials_error)
|
|
||||||
.onPost("https://api.github.com/graphql")
|
|
||||||
.replyOnce(200, successData);
|
|
||||||
|
|
||||||
const { req, res } = faker({}, {});
|
|
||||||
await up(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
|
||||||
expect(res.send).toBeCalledWith(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return `false` if all pats have 'Bad credentials'", async () => {
|
|
||||||
mock
|
|
||||||
.onPost("https://api.github.com/graphql")
|
|
||||||
.reply(404, bad_credentials_error);
|
|
||||||
|
|
||||||
const { req, res } = faker({}, {});
|
|
||||||
await up(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
|
||||||
expect(res.send).toBeCalledWith(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should throw an error if the request fails", async () => {
|
|
||||||
mock.onPost("https://api.github.com/graphql").networkError();
|
|
||||||
|
|
||||||
const { req, res } = faker({}, {});
|
|
||||||
await up(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader).toBeCalledWith("Content-Type", "application/json");
|
|
||||||
expect(res.send).toBeCalledWith(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should have proper cache when no error is thrown", async () => {
|
|
||||||
mock.onPost("https://api.github.com/graphql").replyOnce(200, successData);
|
|
||||||
|
|
||||||
const { req, res } = faker({}, {});
|
|
||||||
await up(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader.mock.calls).toEqual([
|
|
||||||
["Content-Type", "application/json"],
|
|
||||||
["Cache-Control", `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should have proper cache when error is thrown", async () => {
|
|
||||||
mock.onPost("https://api.github.com/graphql").networkError();
|
|
||||||
|
|
||||||
const { req, res } = faker({}, {});
|
|
||||||
await up(req, res);
|
|
||||||
|
|
||||||
expect(res.setHeader.mock.calls).toEqual([
|
|
||||||
["Content-Type", "application/json"],
|
|
||||||
["Cache-Control", "no-store"],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
+151
-62
@@ -1,48 +1,25 @@
|
|||||||
import { queryByTestId } from "@testing-library/dom";
|
import { queryByTestId } from "@testing-library/dom";
|
||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
import {
|
import {
|
||||||
|
chunkArray,
|
||||||
|
clampValue,
|
||||||
encodeHTML,
|
encodeHTML,
|
||||||
|
fallbackColor,
|
||||||
|
flexLayout,
|
||||||
getCardColors,
|
getCardColors,
|
||||||
|
isValidGradient,
|
||||||
|
isValidHexColor,
|
||||||
kFormatter,
|
kFormatter,
|
||||||
|
lowercaseTrim,
|
||||||
|
measureText,
|
||||||
|
parseArray,
|
||||||
parseBoolean,
|
parseBoolean,
|
||||||
|
parseEmojis,
|
||||||
renderError,
|
renderError,
|
||||||
wrapTextMultiline,
|
wrapTextMultiline,
|
||||||
} from "../src/common/utils.js";
|
} from "../src/common/utils.js";
|
||||||
|
|
||||||
describe("Test utils.js", () => {
|
describe("Test utils.js", () => {
|
||||||
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 parseBoolean", () => {
|
|
||||||
expect(parseBoolean(true)).toBe(true);
|
|
||||||
expect(parseBoolean(false)).toBe(false);
|
|
||||||
|
|
||||||
expect(parseBoolean("true")).toBe(true);
|
|
||||||
expect(parseBoolean("false")).toBe(false);
|
|
||||||
expect(parseBoolean("True")).toBe(true);
|
|
||||||
expect(parseBoolean("False")).toBe(false);
|
|
||||||
expect(parseBoolean("TRUE")).toBe(true);
|
|
||||||
expect(parseBoolean("FALSE")).toBe(false);
|
|
||||||
|
|
||||||
expect(parseBoolean("1")).toBe(undefined);
|
|
||||||
expect(parseBoolean("0")).toBe(undefined);
|
|
||||||
expect(parseBoolean("")).toBe(undefined);
|
|
||||||
expect(parseBoolean(undefined)).toBe(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should test encodeHTML", () => {
|
|
||||||
expect(encodeHTML(`<html>hello world<,.#4^&^@%!))`)).toBe(
|
|
||||||
"<html>hello world<,.#4^&^@%!))",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should test renderError", () => {
|
it("should test renderError", () => {
|
||||||
document.body.innerHTML = renderError("Something went wrong");
|
document.body.innerHTML = renderError("Something went wrong");
|
||||||
expect(
|
expect(
|
||||||
@@ -62,11 +39,115 @@ describe("Test utils.js", () => {
|
|||||||
).toHaveTextContent(/Secondary Message/gim);
|
).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", () => {
|
it("getCardColors: should return expected values", () => {
|
||||||
let colors = getCardColors({
|
let colors = getCardColors({
|
||||||
title_color: "f00",
|
title_color: "f00",
|
||||||
text_color: "0f0",
|
text_color: "0f0",
|
||||||
ring_color: "0000ff",
|
|
||||||
icon_color: "00f",
|
icon_color: "00f",
|
||||||
bg_color: "fff",
|
bg_color: "fff",
|
||||||
border_color: "fff",
|
border_color: "fff",
|
||||||
@@ -76,7 +157,6 @@ describe("Test utils.js", () => {
|
|||||||
titleColor: "#f00",
|
titleColor: "#f00",
|
||||||
textColor: "#0f0",
|
textColor: "#0f0",
|
||||||
iconColor: "#00f",
|
iconColor: "#00f",
|
||||||
ringColor: "#0000ff",
|
|
||||||
bgColor: "#fff",
|
bgColor: "#fff",
|
||||||
borderColor: "#fff",
|
borderColor: "#fff",
|
||||||
});
|
});
|
||||||
@@ -95,7 +175,6 @@ describe("Test utils.js", () => {
|
|||||||
titleColor: "#2f80ed",
|
titleColor: "#2f80ed",
|
||||||
textColor: "#0f0",
|
textColor: "#0f0",
|
||||||
iconColor: "#00f",
|
iconColor: "#00f",
|
||||||
ringColor: "#2f80ed",
|
|
||||||
bgColor: "#fff",
|
bgColor: "#fff",
|
||||||
borderColor: "#e4e2e2",
|
borderColor: "#e4e2e2",
|
||||||
});
|
});
|
||||||
@@ -108,41 +187,20 @@ describe("Test utils.js", () => {
|
|||||||
expect(colors).toStrictEqual({
|
expect(colors).toStrictEqual({
|
||||||
titleColor: "#fff",
|
titleColor: "#fff",
|
||||||
textColor: "#9f9f9f",
|
textColor: "#9f9f9f",
|
||||||
ringColor: "#fff",
|
|
||||||
iconColor: "#79ff97",
|
iconColor: "#79ff97",
|
||||||
bgColor: "#151515",
|
bgColor: "#151515",
|
||||||
borderColor: "#e4e2e2",
|
borderColor: "#e4e2e2",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getCardColors: should return ring color equal to title color if not ring color is defined", () => {
|
it("wrapTextMultiline: should not wrap small texts", () => {
|
||||||
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",
|
|
||||||
ringColor: "#f00",
|
|
||||||
bgColor: "#fff",
|
|
||||||
borderColor: "#fff",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("wrapTextMultiline", () => {
|
|
||||||
it("should not wrap small texts", () => {
|
|
||||||
{
|
{
|
||||||
let multiLineText = wrapTextMultiline("Small text should not wrap");
|
let multiLineText = wrapTextMultiline("Small text should not wrap");
|
||||||
expect(multiLineText).toEqual(["Small text should not wrap"]);
|
expect(multiLineText).toEqual(["Small text should not wrap"]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it("should wrap large texts", () => {
|
|
||||||
|
it("wrapTextMultiline: should wrap large texts", () => {
|
||||||
let multiLineText = wrapTextMultiline(
|
let multiLineText = wrapTextMultiline(
|
||||||
"Hello world long long long text",
|
"Hello world long long long text",
|
||||||
20,
|
20,
|
||||||
@@ -150,7 +208,8 @@ describe("wrapTextMultiline", () => {
|
|||||||
);
|
);
|
||||||
expect(multiLineText).toEqual(["Hello world long", "long long text"]);
|
expect(multiLineText).toEqual(["Hello world long", "long long text"]);
|
||||||
});
|
});
|
||||||
it("should wrap large texts and limit max lines", () => {
|
|
||||||
|
it("wrapTextMultiline: should wrap large texts and limit max lines", () => {
|
||||||
let multiLineText = wrapTextMultiline(
|
let multiLineText = wrapTextMultiline(
|
||||||
"Hello world long long long text",
|
"Hello world long long long text",
|
||||||
10,
|
10,
|
||||||
@@ -158,11 +217,41 @@ describe("wrapTextMultiline", () => {
|
|||||||
);
|
);
|
||||||
expect(multiLineText).toEqual(["Hello", "world long..."]);
|
expect(multiLineText).toEqual(["Hello", "world long..."]);
|
||||||
});
|
});
|
||||||
it("should wrap chinese by punctuation", () => {
|
|
||||||
|
it("wrapTextMultiline: should wrap chinese by punctuation", () => {
|
||||||
let multiLineText = wrapTextMultiline(
|
let multiLineText = wrapTextMultiline(
|
||||||
"专门为刚开始刷题的同学准备的算法基地,没有最细只有更细,立志用动画将晦涩难懂的算法说的通俗易懂!",
|
"专门为刚开始刷题的同学准备的算法基地,没有最细只有更细,立志用动画将晦涩难懂的算法说的通俗易懂!",
|
||||||
);
|
);
|
||||||
expect(multiLineText.length).toEqual(3);
|
expect(multiLineText.length).toEqual(3);
|
||||||
expect(multiLineText[0].length).toEqual(18 * 8); // &#xxxxx; x 8
|
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 😄 👍 😄");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+6
-8
@@ -32,11 +32,10 @@ Use `?theme=THEME_NAME` parameter like so :-
|
|||||||
| `jolly` ![jolly][jolly] | `maroongold` ![maroongold][maroongold] | `yeblu` ![yeblu][yeblu] |
|
| `jolly` ![jolly][jolly] | `maroongold` ![maroongold][maroongold] | `yeblu` ![yeblu][yeblu] |
|
||||||
| `blueberry` ![blueberry][blueberry] | `slateorange` ![slateorange][slateorange] | `kacho_ga` ![kacho_ga][kacho_ga] |
|
| `blueberry` ![blueberry][blueberry] | `slateorange` ![slateorange][slateorange] | `kacho_ga` ![kacho_ga][kacho_ga] |
|
||||||
| `outrun` ![outrun][outrun] | `ocean_dark` ![ocean_dark][ocean_dark] | `city_lights` ![city_lights][city_lights] |
|
| `outrun` ![outrun][outrun] | `ocean_dark` ![ocean_dark][ocean_dark] | `city_lights` ![city_lights][city_lights] |
|
||||||
| `github_dark` ![github_dark][github_dark] | `github_dark_dimmed` ![github_dark_dimmed][github_dark_dimmed] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple] |
|
| `github_dark` ![github_dark][github_dark] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple] | `aura_dark` ![aura_dark][aura_dark] |
|
||||||
| `panda` ![panda][panda] | `noctis_minimus` ![noctis_minimus][noctis_minimus] | `cobalt2` ![cobalt2][cobalt2] |
|
| `panda` ![panda][panda] | `noctis_minimus` ![noctis_minimus][noctis_minimus] | `cobalt2` ![cobalt2][cobalt2] |
|
||||||
| `swift` ![swift][swift] | `aura` ![aura][aura] | `apprentice` ![apprentice][apprentice] |
|
| `swift` ![swift][swift] | `aura` ![aura][aura] | `apprentice` ![apprentice][apprentice] |
|
||||||
| `moltack` ![moltack][moltack] | `codeSTACKr` ![codeSTACKr][codeSTACKr] | `rose_pine` ![rose_pine][rose_pine] |
|
| `moltack` ![moltack][moltack] | `codeSTACKr` ![codeSTACKr][codeSTACKr] | `rose_pine` ![rose_pine][rose_pine] |
|
||||||
| `aura_dark` ![aura_dark][aura_dark] | | |
|
|
||||||
| [Add your theme][add-theme] | | |
|
| [Add your theme][add-theme] | | |
|
||||||
|
|
||||||
## Repo Card
|
## Repo Card
|
||||||
@@ -61,11 +60,10 @@ Use `?theme=THEME_NAME` parameter like so :-
|
|||||||
| `jolly` ![jolly][jolly_repo] | `maroongold` ![maroongold][maroongold_repo] | `yeblu` ![yeblu][yeblu_repo] |
|
| `jolly` ![jolly][jolly_repo] | `maroongold` ![maroongold][maroongold_repo] | `yeblu` ![yeblu][yeblu_repo] |
|
||||||
| `blueberry` ![blueberry][blueberry_repo] | `slateorange` ![slateorange][slateorange_repo] | `kacho_ga` ![kacho_ga][kacho_ga_repo] |
|
| `blueberry` ![blueberry][blueberry_repo] | `slateorange` ![slateorange][slateorange_repo] | `kacho_ga` ![kacho_ga][kacho_ga_repo] |
|
||||||
| `outrun` ![outrun][outrun_repo] | `ocean_dark` ![ocean_dark][ocean_dark_repo] | `city_lights` ![city_lights][city_lights_repo] |
|
| `outrun` ![outrun][outrun_repo] | `ocean_dark` ![ocean_dark][ocean_dark_repo] | `city_lights` ![city_lights][city_lights_repo] |
|
||||||
| `github_dark` ![github_dark][github_dark_repo] | `github_dark_dimmed` ![github_dark_dimmed][github_dark_dimmed_repo] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple_repo] |
|
| `github_dark` ![github_dark][github_dark_repo] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple_repo] | `aura_dark` ![aura_dark][aura_dark_repo] |
|
||||||
| `panda` ![panda][panda_repo] | `noctis_minimus` ![noctis_minimus][noctis_minimus_repo] | `cobalt2` ![cobalt2][cobalt2_repo] |
|
| `panda` ![panda][panda_repo] | `noctis_minimus` ![noctis_minimus][noctis_minimus_repo] | `cobalt2` ![cobalt2][cobalt2_repo] |
|
||||||
| `swift` ![swift][swift_repo] | `aura` ![aura][aura_repo] | `apprentice` ![apprentice][apprentice_repo] |
|
| `swift` ![swift][swift_repo] | `aura` ![aura][aura_repo] | `apprentice` ![apprentice][apprentice_repo] |
|
||||||
| `moltack` ![moltack][moltack_repo] | `codeSTACKr` ![codeSTACKr][codeSTACKr_repo] | `rose_pine` ![rose_pine][rose_pine_repo] |
|
| `moltack` ![moltack][moltack_repo] | `codeSTACKr` ![codeSTACKr][codeSTACKr_repo] | `rose_pine` ![rose_pine][rose_pine_repo] |
|
||||||
| `aura_dark` ![aura_dark][aura_dark_repo] | | |
|
|
||||||
| [Add your theme][add-theme] | | |
|
| [Add your theme][add-theme] | | |
|
||||||
|
|
||||||
|
|
||||||
@@ -119,8 +117,8 @@ Use `?theme=THEME_NAME` parameter like so :-
|
|||||||
[ocean_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=ocean_dark
|
[ocean_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=ocean_dark
|
||||||
[city_lights]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=city_lights
|
[city_lights]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=city_lights
|
||||||
[github_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=github_dark
|
[github_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=github_dark
|
||||||
[github_dark_dimmed]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=github_dark_dimmed
|
|
||||||
[discord_old_blurple]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=discord_old_blurple
|
[discord_old_blurple]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=discord_old_blurple
|
||||||
|
[aura_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=aura_dark
|
||||||
[panda]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=panda
|
[panda]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=panda
|
||||||
[noctis_minimus]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=noctis_minimus
|
[noctis_minimus]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=noctis_minimus
|
||||||
[cobalt2]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=cobalt2
|
[cobalt2]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=cobalt2
|
||||||
@@ -130,7 +128,7 @@ Use `?theme=THEME_NAME` parameter like so :-
|
|||||||
[moltack]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=moltack
|
[moltack]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=moltack
|
||||||
[codeSTACKr]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=codeSTACKr
|
[codeSTACKr]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=codeSTACKr
|
||||||
[rose_pine]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=rose_pine
|
[rose_pine]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=rose_pine
|
||||||
[aura_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=aura_dark
|
|
||||||
|
|
||||||
[default_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default
|
[default_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default
|
||||||
[default_repocard_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default_repocard
|
[default_repocard_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default_repocard
|
||||||
@@ -182,8 +180,8 @@ Use `?theme=THEME_NAME` parameter like so :-
|
|||||||
[ocean_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=ocean_dark
|
[ocean_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=ocean_dark
|
||||||
[city_lights_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=city_lights
|
[city_lights_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=city_lights
|
||||||
[github_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=github_dark
|
[github_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=github_dark
|
||||||
[github_dark_dimmed_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=github_dark_dimmed
|
|
||||||
[discord_old_blurple_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=discord_old_blurple
|
[discord_old_blurple_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=discord_old_blurple
|
||||||
|
[aura_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=aura_dark
|
||||||
[panda_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=panda
|
[panda_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=panda
|
||||||
[noctis_minimus_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=noctis_minimus
|
[noctis_minimus_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=noctis_minimus
|
||||||
[cobalt2_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=cobalt2
|
[cobalt2_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=cobalt2
|
||||||
@@ -193,7 +191,7 @@ Use `?theme=THEME_NAME` parameter like so :-
|
|||||||
[moltack_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=moltack
|
[moltack_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=moltack
|
||||||
[codeSTACKr_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=codeSTACKr
|
[codeSTACKr_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=codeSTACKr
|
||||||
[rose_pine_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=rose_pine
|
[rose_pine_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=rose_pine
|
||||||
[aura_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=aura_dark
|
|
||||||
|
|
||||||
[add-theme]: https://github.com/anuraghazra/github-readme-stats/edit/master/themes/index.js
|
[add-theme]: https://github.com/anuraghazra/github-readme-stats/edit/master/themes/index.js
|
||||||
|
|
||||||
|
|||||||
@@ -18,27 +18,6 @@ export const themes = {
|
|||||||
text_color: "417E87",
|
text_color: "417E87",
|
||||||
bg_color: "ffffff00",
|
bg_color: "ffffff00",
|
||||||
},
|
},
|
||||||
shadow_red: {
|
|
||||||
title_color: "9A0000",
|
|
||||||
text_color: "444",
|
|
||||||
icon_color: "4F0000",
|
|
||||||
border_color: "4F0000",
|
|
||||||
bg_color: "ffffff00",
|
|
||||||
},
|
|
||||||
shadow_green: {
|
|
||||||
title_color: "007A00",
|
|
||||||
text_color: "444",
|
|
||||||
icon_color: "003D00",
|
|
||||||
border_color: "003D00",
|
|
||||||
bg_color: "ffffff00",
|
|
||||||
},
|
|
||||||
shadow_blue: {
|
|
||||||
title_color: "00779A",
|
|
||||||
text_color: "444",
|
|
||||||
icon_color: "004450",
|
|
||||||
border_color: "004490",
|
|
||||||
bg_color: "ffffff00",
|
|
||||||
},
|
|
||||||
dark: {
|
dark: {
|
||||||
title_color: "fff",
|
title_color: "fff",
|
||||||
icon_color: "79ff97",
|
icon_color: "79ff97",
|
||||||
@@ -321,13 +300,6 @@ export const themes = {
|
|||||||
text_color: "C3D1D9",
|
text_color: "C3D1D9",
|
||||||
bg_color: "0D1117",
|
bg_color: "0D1117",
|
||||||
},
|
},
|
||||||
github_dark_dimmed: {
|
|
||||||
title_color: "539bf5",
|
|
||||||
icon_color: "539bf5",
|
|
||||||
text_color: "ADBAC7",
|
|
||||||
bg_color: "24292F",
|
|
||||||
border_color: "373E47",
|
|
||||||
},
|
|
||||||
discord_old_blurple: {
|
discord_old_blurple: {
|
||||||
title_color: "7289DA",
|
title_color: "7289DA",
|
||||||
icon_color: "7289DA",
|
icon_color: "7289DA",
|
||||||
@@ -395,34 +367,6 @@ export const themes = {
|
|||||||
text_color: "e0def4",
|
text_color: "e0def4",
|
||||||
bg_color: "191724",
|
bg_color: "191724",
|
||||||
},
|
},
|
||||||
date_night: {
|
|
||||||
title_color: "DA7885",
|
|
||||||
text_color: "E1B2A2",
|
|
||||||
icon_color: "BB8470",
|
|
||||||
border_color: "170F0C",
|
|
||||||
bg_color: "170F0C",
|
|
||||||
},
|
|
||||||
one_dark_pro: {
|
|
||||||
title_color: "61AFEF",
|
|
||||||
text_color: "E5C06E",
|
|
||||||
icon_color: "C678DD",
|
|
||||||
border_color: "3B4048",
|
|
||||||
bg_color: "23272E",
|
|
||||||
},
|
|
||||||
rose: {
|
|
||||||
title_color: "8d192b",
|
|
||||||
text_color: "862931",
|
|
||||||
icon_color: "B71F36",
|
|
||||||
border_color: "e9d8d4",
|
|
||||||
bg_color: "e9d8d4",
|
|
||||||
},
|
|
||||||
holi: {
|
|
||||||
title_color: "5FABEE",
|
|
||||||
text_color: "D6E7FF",
|
|
||||||
icon_color: "5FABEE",
|
|
||||||
border_color: "85A4C0",
|
|
||||||
bg_color: "030314",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default themes;
|
export default themes;
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"functions": {
|
"functions": {
|
||||||
"api/*.js": {
|
"api/*.js": {
|
||||||
"memory": 128,
|
"memory": 128,
|
||||||
"maxDuration": 10
|
"maxDuration": 30
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redirects": [
|
"redirects": [
|
||||||
|
|||||||
Reference in New Issue
Block a user