diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 71b71275a2510..fa7228a6af615 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +github: [anuraghazra] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0eb02215f0312..89df34e6006e6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,17 +20,6 @@ jobs: with: node-version: "12.x" - - name: Cache node modules - uses: actions/cache@v2 - env: - cache-name: cache-node-modules - with: - path: ~/.npm - key: - ${{ runner.os }}-npm-cache-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-npm-cache- - - name: Install & Test run: | npm install diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04528df1566a8..35bfdbd1f2b2b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,6 +17,15 @@ Pull requests are the best way to propose changes. We actively welcome your pull 1. If you've changed APIs, update the documentation. 1. Issue that pull request! +## Under the hood of github-readme-stats + +Interested in diving deeper into understanding how github-readme-stats works? + +[Bohdan](https://github.com/Bogdan-Lyashenko) wrote an amazing in-depth post about it, check it out: + +**[Under the hood of github-readme-stats project](https://codecrumbs.io/library/github-readme-stats)** + + ## Local Development To run & test github-readme-stats you need to follow few simple steps :- @@ -71,7 +80,7 @@ We use GitHub issues to track public bugs. Report a bug by [opening a new issue] **Q:** How to count private stats? -> **Ans:** We can only count private commits & we cannot access any other private info of any users, so it's not possible. only way is to deploy on your own instance & use your own PAT (Personal Access Token) +> **Ans:** We can only count public commits & we cannot access any other private info of any users, so it's not possible. The only way to count your personal private stats is to deploy on your own instance & use your own PAT (Personal Access Token) ### Bug Reports diff --git a/api/index.js b/api/index.js index 235065bbde86b..c23177f7382df 100644 --- a/api/index.js +++ b/api/index.js @@ -17,6 +17,7 @@ module.exports = async (req, res) => { hide, hide_title, hide_border, + card_width, hide_rank, show_icons, count_private, @@ -25,6 +26,7 @@ module.exports = async (req, res) => { title_color, icon_color, text_color, + text_bold, bg_color, theme, cache_seconds, @@ -35,8 +37,6 @@ module.exports = async (req, res) => { border_color, role, } = req.query; - let stats; - res.setHeader("Content-Type", "image/svg+xml"); if (blacklist.includes(username)) { @@ -48,11 +48,11 @@ module.exports = async (req, res) => { } try { - stats = await fetchStats( + const stats = await fetchStats( username, + parseArray(role), parseBoolean(count_private), parseBoolean(include_all_commits), - parseArray(role), ); const cacheSeconds = clampValue( @@ -69,12 +69,14 @@ module.exports = async (req, res) => { show_icons: parseBoolean(show_icons), hide_title: parseBoolean(hide_title), hide_border: parseBoolean(hide_border), + card_width: parseInt(card_width, 10), hide_rank: parseBoolean(hide_rank), include_all_commits: parseBoolean(include_all_commits), line_height, title_color, icon_color, text_color, + text_bold: parseBoolean(text_bold), bg_color, theme, custom_title, diff --git a/api/pin.js b/api/pin.js index 7fad6c09f3746..1df7fd0780f8b 100644 --- a/api/pin.js +++ b/api/pin.js @@ -27,8 +27,6 @@ module.exports = async (req, res) => { border_color, } = req.query; - let repoData; - res.setHeader("Content-Type", "image/svg+xml"); if (blacklist.includes(username)) { @@ -40,7 +38,7 @@ module.exports = async (req, res) => { } try { - repoData = await fetchRepo(username, repo); + const repoData = await fetchRepo(username, repo); let cacheSeconds = clampValue( parseInt(cache_seconds || CONSTANTS.TWO_HOURS, 10), @@ -53,7 +51,7 @@ module.exports = async (req, res) => { and if both are zero we are not showing the stats so we can just make the cache longer, since there is no need to frequent updates */ - const stars = repoData.stargazers.totalCount; + const stars = repoData.starCount; const forks = repoData.forkCount; const isBothOver1K = stars > 1000 && forks > 1000; const isBothUnder1 = stars < 1 && forks < 1; diff --git a/api/top-langs.js b/api/top-langs.js index 6d0e089d1c384..ce2dfeb5f2e62 100644 --- a/api/top-langs.js +++ b/api/top-langs.js @@ -32,8 +32,6 @@ module.exports = async (req, res) => { border_color, role, } = req.query; - let topLangs; - res.setHeader("Content-Type", "image/svg+xml"); if (blacklist.includes(username)) { @@ -45,10 +43,10 @@ module.exports = async (req, res) => { } try { - topLangs = await fetchTopLanguages( + const topLangs = await fetchTopLanguages( username, - parseArray(exclude_repo), parseArray(role), + parseArray(exclude_repo), parseArray(hide), ); diff --git a/api/wakatime.js b/api/wakatime.js index 4bb8fca5d16c0..d585c4a025f09 100644 --- a/api/wakatime.js +++ b/api/wakatime.js @@ -3,9 +3,10 @@ const { renderError, parseBoolean, clampValue, + parseArray, CONSTANTS, - isLocaleAvailable, } = require("../src/common/utils"); +const { isLocaleAvailable } = require("../src/translations"); const { fetchWakatimeStats } = require("../src/fetchers/wakatime-fetcher"); const wakatimeCard = require("../src/cards/wakatime-card"); @@ -26,6 +27,7 @@ module.exports = async (req, res) => { locale, layout, langs_count, + hide, api_domain, range, border_radius, @@ -58,6 +60,7 @@ module.exports = async (req, res) => { custom_title, hide_title: parseBoolean(hide_title), hide_border: parseBoolean(hide_border), + hide: parseArray(hide), line_height, title_color, icon_color, diff --git a/docs/readme_cn.md b/docs/readme_cn.md index 60a7d26c036f4..36a5ff9359454 100644 --- a/docs/readme_cn.md +++ b/docs/readme_cn.md @@ -325,7 +325,7 @@ _注意:热门语言并不表示我的技能水平或类似的水平,它是 1. 选择 `Import Git Repository` ![](https://files.catbox.moe/pqub9q.png) 1. 选择 root 并将所有内容保持不变,并且只需添加名为 PAT_1 的环境变量(如图所示),其中将包含一个个人访问令牌(PAT),你可以在[这里](https://github.com/settings/tokens/new)轻松创建(保留默认,并且只需要命名下,名字随便) - ![](https://files.catbox.moe/caem5b.png) + ![](https://files.catbox.moe/0ez4g7.png) 1. 点击 deploy,这就完成了,查看你的域名就可使用 API 了! diff --git a/docs/readme_es.md b/docs/readme_es.md index ae0cc830f0e8e..b35e89c12c323 100644 --- a/docs/readme_es.md +++ b/docs/readme_es.md @@ -64,7 +64,7 @@ - [Tarjeta de estadísticas de GitHub](#tarjeta-de-estadísticas-de-github) - [Pins adicionales de GitHub](#pines-adicionales-de-github) -- [Top Languages Card](#tarjeta-de-lenguajes-principales) +- [Tarjeta de Lenguajes Principales](#tarjeta-de-lenguajes-principales) - [Wakatime Week Stats](#estadísticas-de-la-semana-de-wakatime) - [Temas](#temas) - [Personalización](#personalización) diff --git a/package.json b/package.json index 0cb50dc167e6e..345388f9714b1 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,14 @@ "@actions/github": "^4.0.0", "@testing-library/dom": "^7.20.0", "@testing-library/jest-dom": "^5.11.0", - "axios": "^0.19.2", + "axios": "^0.24.0", "axios-mock-adapter": "^1.18.1", + "color-contrast-checker": "^2.1.0", "css-to-object": "^1.1.0", + "hjson": "^3.2.2", "husky": "^4.2.5", "jest": "^26.1.0", + "lodash.snakecase": "^4.1.1", "parse-diff": "^0.7.0" }, "dependencies": { diff --git a/readme.md b/readme.md index 068e39789a71c..8b0c593792e98 100644 --- a/readme.md +++ b/readme.md @@ -83,6 +83,11 @@ Your small help goes a long way. :heart: - [Wakatime Week Stats](#wakatime-week-stats) - [Themes](#themes) - [Customization](#customization) + - [Common Options](#common-options) + - [Stats Card Exclusive Options](#stats-card-exclusive-options) + - [Repo Card Exclusive Options](#repo-card-exclusive-options) + - [Language Card Exclusive Options](#language-card-exclusive-options) + - [Wakatime Card Exclusive Option](#wakatime-card-exclusive-options) - [Deploy Yourself](#deploy-on-your-own-vercel-instance) # GitHub Stats Card @@ -113,7 +118,7 @@ To hide any specific stats, you can pass a query parameter `?hide=` with comma-s You can add the count of all your private contributions to the total commits count by using the query parameter `?count_private=true`. -_Note: If you are deploying this project yourself, the private contributions will be counted by default otherwise you need to chose to share your private contribution counts._ +_Note: If you are deploying this project yourself, the private contributions will be counted by default. Otherwise, you need to choose to share your private contribution counts._ > Options: `&count_private=true` @@ -133,7 +138,7 @@ To enable icons, you can pass `show_icons=true` in the query param, like so: With inbuilt themes, you can customize the look of the card without doing any [manual customization](#customization). -Use `?theme=THEME_NAME` parameter like so :- +Use `&theme=THEME_NAME` parameter like so :- ```md ![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&theme=radical) @@ -178,12 +183,14 @@ You can provide multiple comma-separated values in bg_color option to render a g - `hide` - Hides the specified items from stats _(Comma-separated values)_ - `hide_title` - _(boolean)_ +- `card_width` - Set the card's width manually _(number)_ - `hide_rank` - _(boolean)_ hides the rank and automatically resizes the card width - `show_icons` - _(boolean)_ - `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_ - `count_private` - Count private commits _(boolean)_ - `line_height` - Sets the line-height between text _(number)_ - `custom_title` - Sets a custom title for the card +- `text_bold` - Use bold text _(boolean)_ - `disable_animations` - Disables all animations in the card _(boolean)_ #### Repo Card Exclusive Options: @@ -207,6 +214,7 @@ You can provide multiple comma-separated values in bg_color option to render a g #### Wakatime Card Exclusive Options: +- `hide` - Hide the languages specified from the card _(Comma-separated values)_ - `hide_title` - _(boolean)_ - `line_height` - Sets the line-height between text _(number)_ - `hide_progress` - Hides the progress bar and percentage _(boolean)_ @@ -405,6 +413,7 @@ NOTE: Since [#58](https://github.com/anuraghazra/github-readme-stats/pull/58) we ![](https://files.catbox.moe/btd78j.jpeg) 1. Sign into GitHub and allow access to all repositories, if prompted 1. Fork this repo +1. After forking the repo, open the [`vercel.json`](https://github.com/anuraghazra/github-readme-stats/blob/master/vercel.json#L5) file and change the `maxDuration` field to `10` 1. Go back to your [Vercel dashboard](https://vercel.com/dashboard) 1. Select `Import Project` ![](https://files.catbox.moe/qckos0.png) @@ -437,3 +446,4 @@ Thanks! :heart: Contributions are welcome! <3 Made with :heart: and JavaScript. + diff --git a/scripts/generate-theme-doc.js b/scripts/generate-theme-doc.js index 806b2cc3fc2b4..fcb3c5eee705c 100644 --- a/scripts/generate-theme-doc.js +++ b/scripts/generate-theme-doc.js @@ -12,7 +12,7 @@ const THEME_TEMPLATE = `## Available Themes -With inbuilt themes you can customize the look of the card without doing any manual customization. +With inbuilt themes, you can customize the look of the card without doing any manual customization. Use \`?theme=THEME_NAME\` parameter like so :- @@ -43,7 +43,7 @@ ${REPO_CARD_LINKS_FLAG} [add-theme]: https://github.com/anuraghazra/github-readme-stats/edit/master/themes/index.js -Wanted to add a new theme? Consider reading the [contribution guidelines](../CONTRIBUTING.md#themes-contribution) :D +Want to add a new theme? Consider reading the [contribution guidelines](../CONTRIBUTING.md#themes-contribution) :D `; const createRepoMdLink = (theme) => { diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index e809777b16e6a..ca9effae79cc0 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -1,8 +1,16 @@ const core = require("@actions/core"); const github = require("@actions/github"); const parse = require("parse-diff"); +const Hjson = require("hjson"); +const snakeCase = require("lodash.snakecase"); +const ColorContrastChecker = require("color-contrast-checker"); + require("dotenv").config(); +const OWNER = "anuraghazra"; +const REPO = "github-readme-stats"; +const COMMENT_TITLE = "Automated Theme Preview"; + function getPrNumber() { const pullRequest = github.context.payload.pull_request; if (!pullRequest) { @@ -12,17 +20,72 @@ function getPrNumber() { return pullRequest.number; } -const themeContribGuidelines = ` - \r> Hi thanks for the theme contribution, please read our theme contribution guidelines +function findCommentPredicate(inputs, comment) { + return ( + (inputs.commentAuthor && comment.user + ? comment.user.login === inputs.commentAuthor + : true) && + (inputs.bodyIncludes && comment.body + ? comment.body.includes(inputs.bodyIncludes) + : true) + ); +} + +async function findComment(octokit, issueNumber) { + const parameters = { + owner: OWNER, + repo: REPO, + issue_number: issueNumber, + }; + const inputs = { + commentAuthor: OWNER, + bodyIncludes: COMMENT_TITLE, + }; + + for await (const { data: comments } of octokit.paginate.iterator( + octokit.rest.issues.listComments, + parameters, + )) { + // Search each page for the comment + const comment = comments.find((comment) => + findCommentPredicate(inputs, comment), + ); + if (comment) return comment; + } +} + +async function upsertComment(octokit, props) { + if (props.comment_id !== undefined) { + await octokit.issues.updateComment(props); + } else { + await octokit.issues.createComment(props); + } +} + +function getWebAimLink(color1, color2) { + return `https://webaim.org/resources/contrastchecker/?fcolor=${color1}&bcolor=${color2}`; +} - \r> We are currently only accepting color combinations from any vscode theme or which has good color combination to minimize bloating the themes collection. +function getGrsLink(colors) { + const url = `https://github-readme-stats.vercel.app/api?username=anuraghazra`; + const colorString = Object.keys(colors) + .map((colorKey) => `${colorKey}=${colors[colorKey]}`) + .join("&"); - \r> Also note that if this theme is exclusively for your personal use then instead of adding it to our theme collection you can use card [customization options](https://github.com/anuraghazra/github-readme-stats#customization) - \r> Read our [contribution guidelines](https://github.com/anuraghazra/github-readme-stats/blob/master/CONTRIBUTING.md) for more info + return `${url}&${colorString}&show_icons=true`; +} + +const themeContribGuidelines = ` + \rHi, thanks for the theme contribution, please read our theme [contribution guidelines](https://github.com/anuraghazra/github-readme-stats/blob/master/CONTRIBUTING.md#themes-contribution). + \rWe are currently only accepting color combinations from any VSCode theme or themes which have good color combination to minimize bloating the themes collection. + + \r> Also note that if this theme is exclusively for your personal use, then instead of adding it to our theme collection you can use card [customization options](https://github.com/anuraghazra/github-readme-stats#customization) `; async function run() { try { + const ccc = new ColorContrastChecker(); + const warnings = []; const token = core.getInput("token"); const octokit = github.getOctokit(token || process.env.PERSONAL_TOKEN); const pullRequestId = getPrNumber(); @@ -32,65 +95,90 @@ async function run() { return; } - let res = await octokit.pulls.get({ - owner: "anuraghazra", - repo: "github-readme-stats", + const res = await octokit.pulls.get({ + owner: OWNER, + repo: REPO, pull_number: pullRequestId, mediaType: { format: "diff", }, }); + const comment = await findComment(octokit, pullRequestId); - let diff = parse(res.data); - let colorStrings = diff + const diff = parse(res.data); + const content = diff .find((file) => file.to === "themes/index.js") .chunks[0].changes.filter((c) => c.type === "add") .map((c) => c.content.replace("+", "")) .join(""); - let matches = colorStrings.match(/(title_color:.*bg_color.*\")/); - let colors = matches && matches[0].split(","); + const themeObject = Hjson.parse(content); + const themeName = Object.keys(themeObject)[0]; + const colors = themeObject[themeName]; + + if (themeName !== snakeCase(themeName)) { + warnings.push("Theme name isn't in snake_case"); + } if (!colors) { - await octokit.issues.createComment({ - owner: "anuraghazra", - repo: "github-readme-stats", + await upsertComment({ + comment_id: comment?.id, + owner: OWNER, + repo: REPO, + issue_number: pullRequestId, body: ` - \rTheme preview (bot) + \r**${COMMENT_TITLE}** \rCannot create theme preview ${themeContribGuidelines} `, - issue_number: pullRequestId, }); return; } - colors = colors.map((color) => - color.replace(/.*\:\s/, "").replace(/\"/g, ""), - ); - const titleColor = colors[0]; - const iconColor = colors[1]; - const textColor = colors[2]; - const bgColor = colors[3]; - const url = `https://github-readme-stats.vercel.app/api?username=anuraghazra&title_color=${titleColor}&icon_color=${iconColor}&text_color=${textColor}&bg_color=${bgColor}&show_icons=true`; + const titleColor = colors.title_color; + const iconColor = colors.icon_color; + const textColor = colors.text_color; + const bgColor = colors.bg_color; + const url = getGrsLink(colors); + + const colorPairs = { + title_color: [titleColor, bgColor], + icon_color: [iconColor, bgColor], + text_color: [textColor, bgColor], + }; + + // check color contrast + Object.keys(colorPairs).forEach((key) => { + const color1 = colorPairs[key][0]; + const color2 = colorPairs[key][1]; + if (!ccc.isLevelAA(`#${color1}`, `#${color2}`)) { + const permalink = getWebAimLink(color1, color2); + warnings.push( + `\`${key}\` does not passes [AA contrast ratio](${permalink})`, + ); + } + }); - await octokit.issues.createComment({ - owner: "anuraghazra", - repo: "github-readme-stats", + await upsertComment(octokit, { + comment_id: comment?.id, + issue_number: pullRequestId, + owner: OWNER, + repo: REPO, body: ` - \rTheme preview (bot) + \r**${COMMENT_TITLE}** + \r${warnings.map((warning) => `- :warning: ${warning}\n`).join("")} + \ntitle_color: #${titleColor} | icon_color: #${iconColor} | text_color: #${textColor} | bg_color: #${bgColor} - \rLink: ${url} + \r[Preview Link](${url}) \r[![](${url})](${url}) ${themeContribGuidelines} `, - issue_number: pullRequestId, }); } catch (error) { console.log(error); diff --git a/src/cards/repo-card.js b/src/cards/repo-card.js index 9a21fcbdeb358..5295b174735a5 100644 --- a/src/cards/repo-card.js +++ b/src/cards/repo-card.js @@ -1,25 +1,79 @@ -const toEmoji = require("emoji-name-map"); const { kFormatter, encodeHTML, getCardColors, flexLayout, wrapTextMultiline, + measureText, + parseEmojis, } = require("../common/utils"); const I18n = require("../common/I18n"); const Card = require("../common/Card"); const icons = require("../common/icons"); const { repoCardLocales } = require("../translations"); +/** + * @param {string} label + * @param {string} textColor + * @returns {string} + */ +const getBadgeSVG = (label, textColor) => ` + + + + ${label} + + +`; + +/** + * @param {string} langName + * @param {string} langColor + * @returns {string} + */ +const createLanguageNode = (langName, langColor) => { + return ` + + + ${langName} + + `; +}; + +const ICON_SIZE = 16; +const iconWithLabel = (icon, label, testid) => { + if (label <= 0) return ""; + const iconSvg = ` + + ${icon} + + `; + const text = `${label}`; + return flexLayout({ items: [iconSvg, text], gap: 20 }).join(""); +}; + const renderRepoCard = (repo, options = {}) => { const { name, nameWithOwner, description, primaryLanguage, - stargazers, isArchived, isTemplate, + starCount, forkCount, } = repo; const { @@ -35,22 +89,17 @@ const renderRepoCard = (repo, options = {}) => { locale, } = options; + const lineHeight = 10; const header = show_owner ? nameWithOwner : name; const langName = (primaryLanguage && primaryLanguage.name) || "Unspecified"; const langColor = (primaryLanguage && primaryLanguage.color) || "#333"; - const shiftText = langName.length > 15 ? 0 : 30; - - let desc = description || "No description provided"; - - // parse emojis to unicode - desc = desc.replace(/:\w+:/gm, (emoji) => { - return toEmoji.get(emoji) || ""; - }); - + const desc = parseEmojis(description || "No description provided"); const multiLineDescription = wrapTextMultiline(desc); const descriptionLines = multiLineDescription.length; - const lineHeight = 10; + const descriptionSvg = multiLineDescription + .map((line) => `${encodeHTML(line)}`) + .join(""); const height = (descriptionLines > 1 ? 120 : 110) + descriptionLines * lineHeight; @@ -61,13 +110,7 @@ const renderRepoCard = (repo, options = {}) => { }); // returns theme based colors with proper overrides and defaults - const { - titleColor, - textColor, - iconColor, - bgColor, - borderColor, - } = getCardColors({ + const colors = getCardColors({ title_color, icon_color, text_color, @@ -76,74 +119,41 @@ const renderRepoCard = (repo, options = {}) => { theme, }); - const totalStars = kFormatter(stargazers.totalCount); - const totalForks = kFormatter(forkCount); - - const getBadgeSVG = (label) => ` - - - - ${label} - - - `; - const svgLanguage = primaryLanguage - ? ` - - - ${langName} - - ` + ? createLanguageNode(langName, langColor) : ""; - const iconWithLabel = (icon, label, testid) => { - return ` - - ${icon} - - ${label} - `; - }; - const svgStars = - stargazers.totalCount > 0 && - iconWithLabel(icons.star, totalStars, "stargazers"); - const svgForks = - forkCount > 0 && iconWithLabel(icons.fork, totalForks, "forkcount"); + const totalStars = kFormatter(starCount); + const totalForks = kFormatter(forkCount); + const svgStars = iconWithLabel(icons.star, totalStars, "stargazers"); + const svgForks = iconWithLabel(icons.fork, totalForks, "forkcount"); const starAndForkCount = flexLayout({ - items: [svgStars, svgForks], - gap: 65, + items: [svgLanguage, svgStars, svgForks], + sizes: [ + measureText(langName, 12), + ICON_SIZE + measureText(`${totalStars}`, 12), + ICON_SIZE + measureText(`${totalForks}`, 12), + ], + gap: 25, }).join(""); const card = new Card({ - defaultTitle: header, + defaultTitle: header.length > 35 ? `${header.slice(0, 35)}...` : header, titlePrefixIcon: icons.contribs, width: 400, height, border_radius, - colors: { - titleColor, - textColor, - iconColor, - bgColor, - borderColor, - }, + colors, }); card.disableAnimations(); card.setHideBorder(hide_border); card.setHideTitle(false); card.setCSS(` - .description { font: 400 13px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} } - .gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} } - .icon { fill: ${iconColor} } + .description { font: 400 13px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} } + .gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} } + .icon { fill: ${colors.iconColor} } .badge { font: 600 11px 'Segoe UI', Ubuntu, Sans-Serif; } .badge rect { opacity: 0.2 } `); @@ -151,27 +161,18 @@ const renderRepoCard = (repo, options = {}) => { return card.render(` ${ isTemplate - ? getBadgeSVG(i18n.t("repocard.template")) + ? getBadgeSVG(i18n.t("repocard.template"), colors.textColor) : isArchived - ? getBadgeSVG(i18n.t("repocard.archived")) + ? getBadgeSVG(i18n.t("repocard.archived"), colors.textColor) : "" } - ${multiLineDescription - .map((line) => `${encodeHTML(line)}`) - .join("")} + ${descriptionSvg} - - ${svgLanguage} - - - ${starAndForkCount} - + + ${starAndForkCount} `); }; diff --git a/src/cards/stats-card.js b/src/cards/stats-card.js index 99582e0abe2a8..fee830dc74e00 100644 --- a/src/cards/stats-card.js +++ b/src/cards/stats-card.js @@ -19,6 +19,7 @@ const createTextNode = ({ index, showIcons, shiftValuePos, + bold, }) => { const kValue = kFormatter(value); const staggerDelay = (index + 3) * 150; @@ -34,18 +35,18 @@ const createTextNode = ({ return ` ${iconSvg} - ${label}: - ${label}: + ${kValue} `; }; -const renderStatsCard = (stats = {}, options = { hide: [] }) => { +const renderStatsCard = (stats = {}, options = { hide: []}) => { const { name, totalStars, @@ -60,12 +61,14 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { show_icons = false, hide_title = false, hide_border = false, + card_width, hide_rank = false, include_all_commits = false, line_height = 25, title_color, icon_color, text_color, + text_bold = true, bg_color, theme = "default", custom_title, @@ -78,20 +81,15 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { const lheight = parseInt(line_height, 10); // returns theme based colors with proper overrides and defaults - const { - titleColor, - textColor, - iconColor, - bgColor, - borderColor, - } = getCardColors({ - title_color, - icon_color, - text_color, - bg_color, - border_color, - theme, - }); + const { titleColor, textColor, iconColor, bgColor, borderColor } = + getCardColors({ + title_color, + icon_color, + text_color, + bg_color, + border_color, + theme, + }); const apostrophe = ["x", "s"].includes(name.slice(-1).toLocaleLowerCase()) ? "" @@ -147,6 +145,8 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { "id", "my", "pl", + "de", + "nl", ]; const isLongLocale = longLocales.includes(locale) === true; @@ -160,7 +160,8 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { index, showIcons: show_icons, shiftValuePos: - (!include_all_commits ? 50 : 20) + (isLongLocale ? 50 : 0), + (!include_all_commits ? 50 : 35) + (isLongLocale ? 50 : 0), + bold: text_bold, }), ); @@ -171,26 +172,6 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { hide_rank ? 0 : 150, ); - // Conditionally rendered elements - const rankCircle = hide_rank - ? "" - : ` - - - - - ${rank.level} - - - `; - // the better user's score the the rank will be closer to zero so // subtracting 100 to get the progress in 100% const progress = 100 - rank.score; @@ -206,13 +187,20 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { return measureText(custom_title ? custom_title : i18n.t("statcard.title")); }; - const width = hide_rank - ? clampValue( - 50 /* padding */ + calculateTextWidth() * 2, - 270 /* min */, - Infinity, - ) - : 495; + /* + When hide_rank=true, the minimum card width is 270 px + the title length and padding. + When hide_rank=false, the minimum card_width is 340 px + the icon width (if show_icons=true). + Numbers are picked by looking at existing dimensions on production. + */ + const iconWidth = show_icons ? 16 : 0; + const minCardWidth = hide_rank + ? clampValue(50 /* padding */ + calculateTextWidth() * 2, 270, Infinity) + : 340 + iconWidth; + const defaultCardWidth = hide_rank ? 270 : 495; + let width = isNaN(card_width) ? defaultCardWidth : card_width; + if (width < minCardWidth) { + width = minCardWidth; + } const card = new Card({ customTitle: custom_title, @@ -235,6 +223,45 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { if (disable_animations) card.disableAnimations(); + /** + * Calculates the right rank circle translation values such that the rank circle + * keeps respecting the padding. + * + * width > 450: The default left padding of 50 px will be used. + * width < 450: The left and right padding will shrink equally. + * + * @returns {number} - Rank circle translation value. + */ + const calculateRankXTranslation = () => { + if (width < 450) { + return width - 95 + (45 * (450 - 340)) / 110; + } else { + return width - 95; + } + }; + + // Conditionally rendered elements + const rankCircle = hide_rank + ? "" + : ` + + + + + ${rank.level} + + + `; + return card.render(` ${rankCircle} @@ -244,7 +271,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { gap: lheight, direction: "column", }).join("")} - + `); }; diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index f281b12567239..c8109f36d6955 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -2,15 +2,20 @@ const Card = require("../common/Card"); const I18n = require("../common/I18n"); const { langCardLocales } = require("../translations"); const { createProgressNode } = require("../common/createProgressNode"); -const { clampValue, getCardColors, flexLayout } = require("../common/utils"); +const { + clampValue, + getCardColors, + flexLayout, + lowercaseTrim, + measureText, + chunkArray, +} = require("../common/utils"); const DEFAULT_CARD_WIDTH = 300; const DEFAULT_LANGS_COUNT = 5; const DEFAULT_LANG_COLOR = "#858585"; const CARD_PADDING = 25; -const lowercaseTrim = (name) => name.toLowerCase().trim(); - const createProgressTextNode = ({ width, color, name, progress }) => { const paddingRight = 95; const progressTextX = width - paddingRight + 10; @@ -30,12 +35,12 @@ const createProgressTextNode = ({ width, color, name, progress }) => { `; }; -const createCompactLangNode = ({ lang, totalSize, x, y }) => { +const createCompactLangNode = ({ lang, totalSize }) => { const percentage = ((lang.size / totalSize) * 100).toFixed(2); const color = lang.color || "#858585"; return ` - + ${lang.name} ${percentage}% @@ -44,25 +49,38 @@ const createCompactLangNode = ({ lang, totalSize, x, y }) => { `; }; -const createLanguageTextNode = ({ langs, totalSize, x, y }) => { - return langs.map((lang, index) => { - if (index % 2 === 0) { - return createCompactLangNode({ +const getLongestLang = (arr) => + arr.reduce( + (savedLang, lang) => + lang.name.length > savedLang.name.length ? lang : savedLang, + { name: "" }, + ); + +const createLanguageTextNode = ({ langs, totalSize }) => { + const longestLang = getLongestLang(langs); + const chunked = chunkArray(langs, langs.length / 2); + const layouts = chunked.map((array) => { + const items = array.map((lang, index) => + createCompactLangNode({ lang, - x, - y: 12.5 * index + y, totalSize, index, - }); - } - return createCompactLangNode({ - lang, - x: 150, - y: 12.5 + 12.5 * index, - totalSize, - index, - }); + }), + ); + return flexLayout({ + items, + gap: 25, + direction: "column", + }).join(""); }); + + const percent = ((longestLang.size / totalSize) * 100).toFixed(2); + const minGap = 150; + const maxGap = 20 + measureText(`${longestLang.name} ${percent}%`, 11); + return flexLayout({ + items: layouts, + gap: maxGap < minGap ? minGap : maxGap, + }).join(""); }; /** @@ -129,12 +147,14 @@ const renderCompactLayout = (langs, width, totalLanguageSize) => { ${compactProgressBar} - ${createLanguageTextNode({ - x: 0, - y: 25, - langs, - totalSize: totalLanguageSize, - }).join("")} + + + ${createLanguageTextNode({ + langs, + totalSize: totalLanguageSize, + width, + })} + `; }; diff --git a/src/cards/wakatime-card.js b/src/cards/wakatime-card.js index 5e12cf3910d19..905e40a08fefc 100644 --- a/src/cards/wakatime-card.js +++ b/src/cards/wakatime-card.js @@ -4,7 +4,12 @@ const { getStyles } = require("../getStyles"); const { wakatimeCardLocales } = require("../translations"); const languageColors = require("../common/languageColors.json"); const { createProgressNode } = require("../common/createProgressNode"); -const { clampValue, getCardColors, flexLayout } = require("../common/utils"); +const { + clampValue, + getCardColors, + flexLayout, + lowercaseTrim, +} = require("../common/utils"); const noCodingActivityNode = ({ color, text }) => { return ` @@ -61,34 +66,47 @@ const createTextNode = ({ const cardProgress = hideProgress ? null : createProgressNode({ - x: 110, - y: 4, - progress: percent, - color: progressBarColor, - width: 220, - name: label, - progressBarBackgroundColor, - }); + x: 110, + y: 4, + progress: percent, + color: progressBarColor, + width: 220, + name: label, + progressBarBackgroundColor, + }); return ` - ${label}: + ${label}: ${value} ${cardProgress} `; }; +const recalculatePercentages = (languages) => { + // recalculating percentages so that, + // compact layout's progress bar does not break when hiding languages + const totalSum = languages.reduce( + (totalSum, language) => totalSum + language.percent, + 0, + ); + const weight = (100 / totalSum).toFixed(2); + languages.forEach((language) => { + language.percent = (language.percent * weight).toFixed(2); + }); +}; + const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { - const { languages } = stats; + let { languages } = stats; const { hide_title = false, hide_border = false, + hide, line_height = 25, title_color, icon_color, @@ -104,6 +122,15 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { border_color, } = options; + const shouldHideLangs = Array.isArray(hide) && hide.length > 0; + if (shouldHideLangs && languages !== undefined) { + const languagesToHide = new Set(hide.map((lang) => lowercaseTrim(lang))); + languages = languages.filter( + (lang) => !languagesToHide.has(lowercaseTrim(lang.name)), + ); + recalculatePercentages(languages); + } + const i18n = new I18n({ locale, translations: wakatimeCardLocales, @@ -111,7 +138,7 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { const lheight = parseInt(line_height, 10); - langsCount = clampValue(parseInt(langs_count), 1, langs_count); + const langsCount = clampValue(parseInt(langs_count), 1, langs_count); // returns theme based colors with proper overrides and defaults const { @@ -131,8 +158,8 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { const filteredLanguages = languages ? languages - .filter((language) => language.hours || language.minutes) - .slice(0, langsCount) + .filter((language) => language.hours || language.minutes) + .slice(0, langsCount) : []; // Calculate the card height depending on how many items there are @@ -186,17 +213,16 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { ${compactProgressBar} ${createLanguageTextNode({ - x: 0, - y: 25, - langs: filteredLanguages, - totalSize: 100, - }).join("")} + x: 0, + y: 25, + langs: filteredLanguages, + totalSize: 100, + }).join("")} `; } else { finalLayout = flexLayout({ items: filteredLanguages.length - ? filteredLanguages - .map((language) => { + ? filteredLanguages.map((language) => { return createTextNode({ id: language.name, label: language.name, @@ -208,11 +234,11 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { }); }) : [ - noCodingActivityNode({ - color: textColor, - text: i18n.t("wakatimecard.nocodingactivity"), - }), - ], + noCodingActivityNode({ + color: textColor, + text: i18n.t("wakatimecard.nocodingactivity"), + }), + ], gap: lheight, direction: "column", }).join(""); diff --git a/src/common/Card.js b/src/common/Card.js index 520560f94d24d..fd1fa77d09c61 100644 --- a/src/common/Card.js +++ b/src/common/Card.js @@ -2,6 +2,16 @@ const { getAnimations } = require("../getStyles"); const { flexLayout, encodeHTML } = require("../common/utils"); class Card { + /** + * @param {object} args + * @param {number?=} args.width + * @param {number?=} args.height + * @param {number?=} args.border_radius + * @param {string?=} args.customTitle + * @param {string?=} args.defaultTitle + * @param {string?=} args.titlePrefixIcon + * @param {ReturnType?=} args.colors + */ constructor({ width = 100, height = 100, @@ -38,14 +48,23 @@ class Card { this.animations = false; } + /** + * @param {string} value + */ setCSS(value) { this.css = value; } + /** + * @param {boolean} value + */ setHideBorder(value) { this.hideBorder = value; } + /** + * @param {boolean} value + */ setHideTitle(value) { this.hideTitle = value; if (value) { @@ -53,6 +72,9 @@ class Card { } } + /** + * @param {string} text + */ setTitle(text) { this.title = text; } @@ -114,6 +136,9 @@ class Card { : ""; } + /** + * @param {string} body + */ render(body) { return ` { return ` @@ -20,7 +27,11 @@ const renderError = (message, secondaryMessage = "") => { `; }; -// https://stackoverflow.com/a/48073476/10629172 +/** + * @see https://stackoverflow.com/a/48073476/10629172 + * @param {string} str + * @returns {string} + */ function encodeHTML(str) { return str .replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => { @@ -29,18 +40,29 @@ function encodeHTML(str) { .replace(/\u0008/gim, ""); } +/** + * @param {number} num + */ function kFormatter(num) { return Math.abs(num) > 999 - ? Math.sign(num) * (Math.abs(num) / 1000).toFixed(1) + "k" + ? Math.sign(num) * parseFloat((Math.abs(num) / 1000).toFixed(1)) + "k" : Math.sign(num) * Math.abs(num); } +/** + * @param {string} hexColor + * @returns {boolean} + */ function isValidHexColor(hexColor) { return new RegExp( /^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{4})$/, ).test(hexColor); } +/** + * @param {string} value + * @returns {boolean | string} + */ function parseBoolean(value) { if (value === "true") { return true; @@ -51,19 +73,37 @@ function parseBoolean(value) { } } +/** + * @param {string} str + */ function parseArray(str) { if (!str) return []; return str.split(","); } +/** + * @param {number} number + * @param {number} min + * @param {number} max + */ function clampValue(number, min, max) { + // @ts-ignore + if (Number.isNaN(parseInt(number))) return min; return Math.max(min, Math.min(number, max)); } +/** + * @param {string[]} colors + */ function isValidGradient(colors) { return isValidHexColor(colors[1]) && isValidHexColor(colors[2]); } +/** + * @param {string} color + * @param {string} fallbackColor + * @returns {string | string[]} + */ function fallbackColor(color, fallbackColor) { let colors = color.split(","); let gradient = null; @@ -78,7 +118,12 @@ function fallbackColor(color, fallbackColor) { ); } +/** + * @param {import('axios').AxiosRequestConfig['data']} data + * @param {import('axios').AxiosRequestConfig['headers']} headers + */ function request(data, headers) { + // @ts-ignore return axios({ url: "https://api.github.com/graphql", method: "post", @@ -88,34 +133,53 @@ function request(data, headers) { } /** + * @param {object} props + * @param {string[]} props.items + * @param {number} props.gap + * @param {number[]?=} props.sizes + * @param {"column" | "row"?=} props.direction * - * @param {String[]} items - * @param {Number} gap - * @param {string} direction + * @returns {string[]} * * @description * Auto layout utility, allows us to layout things * vertically or horizontally with proper gaping */ -function flexLayout({ items, gap, direction }) { +function flexLayout({ items, gap, direction, sizes = [] }) { + let lastSize = 0; // filter() for filtering out empty strings return items.filter(Boolean).map((item, i) => { - let transform = `translate(${gap * i}, 0)`; + const size = sizes[i] || 0; + let transform = `translate(${lastSize}, 0)`; if (direction === "column") { - transform = `translate(0, ${gap * i})`; + transform = `translate(0, ${lastSize})`; } + lastSize += size + gap; return `${item}`; }); } -// returns theme based colors with proper overrides and defaults +/** + * @typedef {object} CardColors + * @prop {string} title_color + * @prop {string} text_color + * @prop {string} icon_color + * @prop {string} bg_color + * @prop {string} border_color + * @prop {keyof typeof import('../../themes')?=} fallbackTheme + * @prop {keyof typeof import('../../themes')?=} theme + */ +/** + * returns theme based colors with proper overrides and defaults + * @param {CardColors} options + */ function getCardColors({ title_color, text_color, icon_color, bg_color, - theme, border_color, + theme, fallbackTheme = "default", }) { const defaultTheme = themes[fallbackTheme]; @@ -150,12 +214,28 @@ function getCardColors({ return { titleColor, iconColor, textColor, bgColor, borderColor }; } -function wrapTextMultiline(text, width = 60, maxLines = 3) { - const wrapped = wrap(encodeHTML(text), { width }) - .split("\n") // Split wrapped lines to get an array of lines - .map((line) => line.trim()); // Remove leading and trailing whitespace of each line +/** + * @param {string} text + * @param {number} width + * @param {number} maxLines + * @returns {string[]} + */ +function wrapTextMultiline(text, width = 59, maxLines = 3) { + const fullWidthComma = ","; + const encoded = encodeHTML(text); + const isChinese = encoded.includes(fullWidthComma); + + let wrapped = []; + + if (isChinese) { + wrapped = encoded.split(fullWidthComma); // Chinese full punctuation + } else { + wrapped = wrap(encoded, { + width, + }).split("\n"); // Split wrapped lines to get an array of lines + } - const lines = wrapped.slice(0, maxLines); // Only consider maxLines lines + const lines = wrapped.map((line) => line.trim()).slice(0, maxLines); // Only consider maxLines lines // Add "..." to the last line if the text exceeds maxLines if (wrapped.length > maxLines) { @@ -186,6 +266,10 @@ const SECONDARY_ERROR_MESSAGES = { }; class CustomError extends Error { + /** + * @param {string} message + * @param {string} type + */ constructor(message, type) { super(message); this.type = type; @@ -196,7 +280,12 @@ class CustomError extends Error { static USER_NOT_FOUND = "USER_NOT_FOUND"; } -// https://stackoverflow.com/a/48172630/10629172 +/** + * @see https://stackoverflow.com/a/48172630/10629172 + * @param {string} str + * @param {number} fontSize + * @returns + */ function measureText(str, fontSize = 10) { // prettier-ignore const widths = [ @@ -231,6 +320,41 @@ function measureText(str, fontSize = 10) { ); } +/** @param {string} name */ +const lowercaseTrim = (name) => name.toLowerCase().trim(); + +/** + * @template T + * @param {Array} arr + * @param {number} perChunk + * @returns {Array} + */ +function chunkArray(arr, perChunk) { + return arr.reduce((resultArray, item, index) => { + const chunkIndex = Math.floor(index / perChunk); + + if (!resultArray[chunkIndex]) { + resultArray[chunkIndex] = []; // start a new chunk + } + + resultArray[chunkIndex].push(item); + + return resultArray; + }, []); +} + +/** + * + * @param {string} str + * @returns {string} + */ +function parseEmojis(str) { + if (!str) throw new Error("[parseEmoji]: str argument not provided"); + return str.replace(/:\w+:/gm, (emoji) => { + return toEmoji.get(emoji) || ""; + }); +} + module.exports = { renderError, kFormatter, @@ -248,4 +372,7 @@ module.exports = { logger, CONSTANTS, CustomError, + lowercaseTrim, + chunkArray, + parseEmojis, }; diff --git a/src/fetchers/repo-fetcher.js b/src/fetchers/repo-fetcher.js index 7e441084618f6..9ddaadfcdc30b 100644 --- a/src/fetchers/repo-fetcher.js +++ b/src/fetchers/repo-fetcher.js @@ -38,7 +38,7 @@ const fetcher = (variables, token) => { variables, }, { - Authorization: `bearer ${token}`, + Authorization: `token ${token}`, }, ); }; @@ -63,7 +63,10 @@ async function fetchRepo(username, reponame) { if (!data.user.repository || data.user.repository.isPrivate) { throw new Error("User Repository Not found"); } - return data.user.repository; + return { + ...data.user.repository, + starCount: data.user.repository.stargazers.totalCount, + }; } if (isOrg) { @@ -73,7 +76,10 @@ async function fetchRepo(username, reponame) { ) { throw new Error("Organization Repository Not found"); } - return data.organization.repository; + return { + ...data.organization.repository, + starCount: data.organization.repository.stargazers.totalCount, + }; } } diff --git a/src/fetchers/stats-fetcher.js b/src/fetchers/stats-fetcher.js index 3fafe96b9b339..c01db11b5a02f 100644 --- a/src/fetchers/stats-fetcher.js +++ b/src/fetchers/stats-fetcher.js @@ -25,7 +25,10 @@ const fetcher = (variables, token) => { pullRequests(first: 1) { totalCount } - issues(first: 1) { + openIssues: issues(states: OPEN) { + totalCount + } + closedIssues: issues(states: CLOSED) { totalCount } followers { @@ -66,7 +69,7 @@ const totalCommitsFetcher = async (username) => { headers: { "Content-Type": "application/json", Accept: "application/vnd.github.cloak-preview", - Authorization: `bearer ${token}`, + Authorization: `token ${token}`, }, }); }; @@ -86,9 +89,9 @@ const totalCommitsFetcher = async (username) => { async function fetchStats( username, + ownerAffiliations, count_private = false, include_all_commits = false, - ownerAffiliations, ) { if (!username) throw Error("Invalid username"); @@ -120,7 +123,7 @@ async function fetchStats( const user = res.data.data.user; stats.name = user.name || user.login; - stats.totalIssues = user.issues.totalCount; + stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount; // normal commits stats.totalCommits = user.contributionsCollection.totalCommitContributions; diff --git a/src/fetchers/top-languages-fetcher.js b/src/fetchers/top-languages-fetcher.js index 92443ada0fbe1..50283740a20d5 100644 --- a/src/fetchers/top-languages-fetcher.js +++ b/src/fetchers/top-languages-fetcher.js @@ -29,12 +29,12 @@ const fetcher = (variables, token) => { variables, }, { - Authorization: `bearer ${token}`, + Authorization: `token ${token}`, }, ); }; -async function fetchTopLanguages(username, exclude_repo = [], ownerAffiliations) { +async function fetchTopLanguages(username, ownerAffiliations, exclude_repo = []) { if (!username) throw Error("Invalid username"); // Set default value for ownerAffiliations in GraphQL query won't work because diff --git a/src/fetchers/wakatime-fetcher.js b/src/fetchers/wakatime-fetcher.js index 55809e2a0ab7b..92371ff52fb07 100644 --- a/src/fetchers/wakatime-fetcher.js +++ b/src/fetchers/wakatime-fetcher.js @@ -4,7 +4,7 @@ const fetchWakatimeStats = async ({ username, api_domain, range }) => { try { const { data } = await axios.get( `https://${ - api_domain ? api_domain.replace(/[^a-z-.0-9]/gi, "") : "wakatime.com" + api_domain ? api_domain.replace(/\/$/gi, "") : "wakatime.com" }/api/v1/users/${username}/stats/${range || ''}?is_including_today=true`, ); diff --git a/src/getStyles.js b/src/getStyles.js index b15f46d09be08..37a41290732aa 100644 --- a/src/getStyles.js +++ b/src/getStyles.js @@ -1,14 +1,21 @@ +/** + * @param {number} value + */ const calculateCircleProgress = (value) => { - let radius = 40; - let c = Math.PI * (radius * 2); + const radius = 40; + const c = Math.PI * (radius * 2); if (value < 0) value = 0; if (value > 100) value = 100; - let percentage = ((100 - value) / 100) * c; - return percentage; + return ((100 - value) / 100) * c; }; +/** + * + * @param {{progress: number}} param0 + * @returns + */ const getProgressAnimation = ({ progress }) => { return ` @keyframes rankAnimation { @@ -44,6 +51,15 @@ const getAnimations = () => { `; }; +/** + * @param {{ + * titleColor: string; + * textColor: string; + * iconColor: string; + * show_icons: boolean; + * progress: number; + * }} args + */ const getStyles = ({ titleColor, textColor, @@ -64,6 +80,7 @@ const getStyles = ({ animation: scaleInAnimation 0.3s ease-in-out forwards; } + .not_bold { font-weight: 400 } .bold { font-weight: 700 } .icon { fill: ${iconColor}; diff --git a/src/translations.js b/src/translations.js index 7f3a0f1f7dff5..e8a85bd773524 100644 --- a/src/translations.js +++ b/src/translations.js @@ -33,8 +33,8 @@ const statCardLocales = ({ name, apostrophe }) => { ar: "مجموع النجوم", cn: "获标星数(star)", cs: "Celkem hvězd", - de: "Sterne Insgesamt", - en: "Total Stars", + de: "Insgesamt erhaltene Sterne", + en: "Total Stars Earned", bn: "সর্বমোট Stars", es: "Estrellas totales", fr: "Total d'étoiles", @@ -42,7 +42,7 @@ const statCardLocales = ({ name, apostrophe }) => { it: "Stelle totali", ja: "スターされた数", kr: "받은 스타 수", - nl: "Totale Sterren", + nl: "Totaal sterren ontvangen", "pt-pt": "Total de estrelas", "pt-br": "Total de estrelas", np: "कुल ताराहरू", @@ -53,7 +53,7 @@ const statCardLocales = ({ name, apostrophe }) => { my: "Jumlah Bintang", sk: "Hviezdy", tr: "Toplam Yıldız", - pl: "Liczba Gwiazdek", + pl: "Liczba Gwiazdek dostanych", }, "statcard.commits": { ar: "مجموع الحفظ", diff --git a/tests/__snapshots__/renderWakatimeCard.test.js.snap b/tests/__snapshots__/renderWakatimeCard.test.js.snap index c46f950c40dfe..0f1a7bfbf295b 100644 --- a/tests/__snapshots__/renderWakatimeCard.test.js.snap +++ b/tests/__snapshots__/renderWakatimeCard.test.js.snap @@ -29,6 +29,7 @@ exports[`Test Render Wakatime Card should render correctly 1`] = ` animation: scaleInAnimation 0.3s ease-in-out forwards; } + .not_bold { font-weight: 400 } .bold { font-weight: 700 } .icon { fill: #4c71f2; @@ -99,12 +100,11 @@ exports[`Test Render Wakatime Card should render correctly 1`] = ` - Other: + Other: 19 mins @@ -122,12 +122,11 @@ exports[`Test Render Wakatime Card should render correctly 1`] = ` - TypeScript: + TypeScript: 1 min @@ -180,6 +179,7 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1 animation: scaleInAnimation 0.3s ease-in-out forwards; } + .not_bold { font-weight: 400 } .bold { font-weight: 700 } .icon { fill: #4c71f2; diff --git a/tests/api.test.js b/tests/api.test.js index a468d5f57c9cd..ffdf5e1948597 100644 --- a/tests/api.test.js +++ b/tests/api.test.js @@ -35,7 +35,8 @@ const data = { restrictedContributionsCount: 100, }, pullRequests: { totalCount: stats.totalPRs }, - issues: { totalCount: stats.totalIssues }, + openIssues: { totalCount: stats.totalIssues }, + closedIssues: { totalCount: 0 }, followers: { totalCount: 0 }, repositories: { totalCount: 1, diff --git a/tests/fetchRepo.test.js b/tests/fetchRepo.test.js index 891cb4af296bf..277d627043ace 100644 --- a/tests/fetchRepo.test.js +++ b/tests/fetchRepo.test.js @@ -19,14 +19,14 @@ const data_repo = { const data_user = { data: { - user: { repository: data_repo }, + user: { repository: data_repo.repository }, organization: null, }, }; const data_org = { data: { user: null, - organization: { repository: data_repo }, + organization: { repository: data_repo.repository }, }, }; @@ -41,14 +41,21 @@ describe("Test fetchRepo", () => { mock.onPost("https://api.github.com/graphql").reply(200, data_user); let repo = await fetchRepo("anuraghazra", "convoychat"); - expect(repo).toStrictEqual(data_repo); + + expect(repo).toStrictEqual({ + ...data_repo.repository, + starCount: data_repo.repository.stargazers.totalCount, + }); }); it("should fetch correct org repo", async () => { mock.onPost("https://api.github.com/graphql").reply(200, data_org); let repo = await fetchRepo("anuraghazra", "convoychat"); - expect(repo).toStrictEqual(data_repo); + expect(repo).toStrictEqual({ + ...data_repo.repository, + starCount: data_repo.repository.stargazers.totalCount, + }); }); it("should throw error if user is found but repo is null", async () => { diff --git a/tests/fetchStats.test.js b/tests/fetchStats.test.js index e627e498a3535..f6a332eab5591 100644 --- a/tests/fetchStats.test.js +++ b/tests/fetchStats.test.js @@ -14,7 +14,8 @@ const data = { restrictedContributionsCount: 50, }, pullRequests: { totalCount: 300 }, - issues: { totalCount: 200 }, + openIssues: { totalCount: 100 }, + closedIssues: { totalCount: 100 }, followers: { totalCount: 100 }, repositories: { totalCount: 5, @@ -51,7 +52,7 @@ describe("Test fetchStats", () => { it("should fetch correct stats", async () => { mock.onPost("https://api.github.com/graphql").reply(200, data); - let stats = await fetchStats("anuraghazra"); + let stats = await fetchStats("anuraghazra", []); const rank = calculateRank({ totalCommits: 100, totalRepos: 5, @@ -76,7 +77,7 @@ describe("Test fetchStats", () => { it("should throw error", async () => { mock.onPost("https://api.github.com/graphql").reply(200, error); - await expect(fetchStats("anuraghazra")).rejects.toThrow( + await expect(fetchStats("anuraghazra", [])).rejects.toThrow( "Could not resolve to a User with the login of 'noname'.", ); }); @@ -84,7 +85,7 @@ describe("Test fetchStats", () => { it("should fetch and add private contributions", async () => { mock.onPost("https://api.github.com/graphql").reply(200, data); - let stats = await fetchStats("anuraghazra", true); + let stats = await fetchStats("anuraghazra", [], true); const rank = calculateRank({ totalCommits: 150, totalRepos: 5, @@ -112,7 +113,7 @@ describe("Test fetchStats", () => { .onGet("https://api.github.com/search/commits?q=author:anuraghazra") .reply(200, { total_count: 1000 }); - let stats = await fetchStats("anuraghazra", true, true); + let stats = await fetchStats("anuraghazra", [], true, true); const rank = calculateRank({ totalCommits: 1050, totalRepos: 5, diff --git a/tests/fetchTopLanguages.test.js b/tests/fetchTopLanguages.test.js index f9aaa024390e5..2df977f1a9c90 100644 --- a/tests/fetchTopLanguages.test.js +++ b/tests/fetchTopLanguages.test.js @@ -59,7 +59,7 @@ describe("FetchTopLanguages", () => { it("should fetch correct language data", async () => { mock.onPost("https://api.github.com/graphql").reply(200, data_langs); - let repo = await fetchTopLanguages("anuraghazra"); + let repo = await fetchTopLanguages("anuraghazra", []); expect(repo).toStrictEqual({ HTML: { color: "#0f0", @@ -77,7 +77,7 @@ describe("FetchTopLanguages", () => { it("should throw error", async () => { mock.onPost("https://api.github.com/graphql").reply(200, error); - await expect(fetchTopLanguages("anuraghazra")).rejects.toThrow( + await expect(fetchTopLanguages("anuraghazra", [])).rejects.toThrow( "Could not resolve to a User with the login of 'noname'.", ); }); diff --git a/tests/flexLayout.test.js b/tests/flexLayout.test.js new file mode 100644 index 0000000000000..5f2defd6ea805 --- /dev/null +++ b/tests/flexLayout.test.js @@ -0,0 +1,46 @@ +const { flexLayout } = require("../src/common/utils"); + +describe("flexLayout", () => { + it("should work with row & col layouts", () => { + const layout = flexLayout({ + items: ["1", "2"], + gap: 60, + }); + + expect(layout).toStrictEqual([ + `1`, + `2`, + ]); + + const columns = flexLayout({ + items: ["1", "2"], + gap: 60, + direction: "column", + }); + + expect(columns).toStrictEqual([ + `1`, + `2`, + ]); + }); + + it("should work with sizes", () => { + const layout = flexLayout({ + items: [ + "1", + "2", + "3", + "4", + ], + gap: 20, + sizes: [200, 100, 55, 25], + }); + + expect(layout).toStrictEqual([ + `1`, + `2`, + `3`, + `4`, + ]); + }); +}); diff --git a/tests/pin.test.js b/tests/pin.test.js index 8abad44e1619c..29c19c63480fc 100644 --- a/tests/pin.test.js +++ b/tests/pin.test.js @@ -9,7 +9,9 @@ const data_repo = { repository: { username: "anuraghazra", name: "convoychat", - stargazers: { totalCount: 38000 }, + stargazers: { + totalCount: 38000, + }, description: "Help us take over the world! React + TS + GraphQL Chat App", primaryLanguage: { color: "#2b7489", @@ -51,7 +53,12 @@ describe("Test /api/pin", () => { await pin(req, res); expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); - expect(res.send).toBeCalledWith(renderRepoCard(data_repo.repository)); + expect(res.send).toBeCalledWith( + renderRepoCard({ + ...data_repo.repository, + starCount: data_repo.repository.stargazers.totalCount, + }), + ); }); it("should get the query options", async () => { @@ -76,7 +83,13 @@ describe("Test /api/pin", () => { expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); expect(res.send).toBeCalledWith( - renderRepoCard(data_repo.repository, { ...req.query }), + renderRepoCard( + { + ...data_repo.repository, + starCount: data_repo.repository.stargazers.totalCount, + }, + { ...req.query }, + ), ); }); diff --git a/tests/renderRepoCard.test.js b/tests/renderRepoCard.test.js index e375d35383ca5..8f1d6ef44a3b4 100644 --- a/tests/renderRepoCard.test.js +++ b/tests/renderRepoCard.test.js @@ -9,13 +9,13 @@ const data_repo = { repository: { nameWithOwner: "anuraghazra/convoychat", name: "convoychat", - stargazers: { totalCount: 38000 }, description: "Help us take over the world! React + TS + GraphQL Chat App", primaryLanguage: { color: "#2b7489", id: "MDg6TGFuZ3VhZ2UyODc=", name: "TypeScript", }, + starCount: 38000, forkCount: 100, }, }; @@ -51,6 +51,17 @@ describe("Test renderRepoCard", () => { ); }); + it("should trim header", () => { + document.body.innerHTML = renderRepoCard({ + ...data_repo.repository, + name: "some-really-long-repo-name-for-test-purposes", + }); + + expect(document.getElementsByClassName("header")[0].textContent).toBe( + "some-really-long-repo-name-for-test...", + ); + }); + it("should trim description", () => { document.body.innerHTML = renderRepoCard({ ...data_repo.repository, @@ -89,36 +100,6 @@ describe("Test renderRepoCard", () => { ); }); - it("should shift the text position depending on language length", () => { - document.body.innerHTML = renderRepoCard({ - ...data_repo.repository, - primaryLanguage: { - ...data_repo.repository.primaryLanguage, - name: "Jupyter Notebook", - }, - }); - - expect(queryByTestId(document.body, "primary-lang")).toBeInTheDocument(); - expect(queryByTestId(document.body, "star-fork-group")).toHaveAttribute( - "transform", - "translate(155, 0)", - ); - - // Small lang - document.body.innerHTML = renderRepoCard({ - ...data_repo.repository, - primaryLanguage: { - ...data_repo.repository.primaryLanguage, - name: "Ruby", - }, - }); - - expect(queryByTestId(document.body, "star-fork-group")).toHaveAttribute( - "transform", - "translate(125, 0)", - ); - }); - it("should hide language if primaryLanguage is null & fallback to correct values", () => { document.body.innerHTML = renderRepoCard({ ...data_repo.repository, @@ -261,7 +242,7 @@ describe("Test renderRepoCard", () => { it("should not render star count or fork count if either of the are zero", () => { document.body.innerHTML = renderRepoCard({ ...data_repo.repository, - stargazers: { totalCount: 0 }, + starCount: 0, }); expect(queryByTestId(document.body, "stargazers")).toBeNull(); @@ -269,7 +250,7 @@ describe("Test renderRepoCard", () => { document.body.innerHTML = renderRepoCard({ ...data_repo.repository, - stargazers: { totalCount: 1 }, + starCount: 1, forkCount: 0, }); @@ -278,7 +259,7 @@ describe("Test renderRepoCard", () => { document.body.innerHTML = renderRepoCard({ ...data_repo.repository, - stargazers: { totalCount: 0 }, + starCount: 0, forkCount: 0, }); @@ -332,11 +313,24 @@ describe("Test renderRepoCard", () => { ); expect(queryByTestId(document.body, "badge")).toHaveTextContent("模板"); }); - + it("should render without rounding", () => { - document.body.innerHTML = renderRepoCard(data_repo.repository, { border_radius: "0" }); + document.body.innerHTML = renderRepoCard(data_repo.repository, { + border_radius: "0", + }); expect(document.querySelector("rect")).toHaveAttribute("rx", "0"); - document.body.innerHTML = renderRepoCard(data_repo.repository, { }); + document.body.innerHTML = renderRepoCard(data_repo.repository, {}); expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5"); }); + + it("should fallback to default description", () => { + document.body.innerHTML = renderRepoCard({ + ...data_repo.repository, + description: undefined, + isArchived: true, + }); + expect(document.getElementsByClassName("description")[0]).toHaveTextContent( + "No description provided", + ); + }); }); diff --git a/tests/renderStatsCard.test.js b/tests/renderStatsCard.test.js index 96a48d1a9d7bd..fc0d4fab6e498 100644 --- a/tests/renderStatsCard.test.js +++ b/tests/renderStatsCard.test.js @@ -75,6 +75,22 @@ describe("Test renderStatsCard", () => { expect(queryByTestId(document.body, "rank-circle")).not.toBeInTheDocument(); }); + it("should render with custom width set", () => { + document.body.innerHTML = renderStatsCard(stats); + expect(document.querySelector("svg")).toHaveAttribute("width", "495"); + + document.body.innerHTML = renderStatsCard(stats, { card_width: 400 }); + expect(document.querySelector("svg")).toHaveAttribute("width", "400"); + }); + + it("should render with custom width set and limit minimum width", () => { + document.body.innerHTML = renderStatsCard(stats, { card_width: 1 }); + expect(document.querySelector("svg")).toHaveAttribute("width", "340"); + + document.body.innerHTML = renderStatsCard(stats, { card_width: 1, hide_rank: true }); + expect(document.querySelector("svg")).toHaveAttribute("width", "305.81250000000006"); + }); + it("should render default colors properly", () => { document.body.innerHTML = renderStatsCard(stats); diff --git a/tests/renderTopLanguages.test.js b/tests/renderTopLanguages.test.js index 66946ce56e1c8..3591bc504a8af 100644 --- a/tests/renderTopLanguages.test.js +++ b/tests/renderTopLanguages.test.js @@ -1,6 +1,5 @@ require("@testing-library/jest-dom"); const cssToObject = require("css-to-object"); -const fetchTopLanguages = require("../src/fetchers/top-languages-fetcher"); const renderTopLanguages = require("../src/cards/top-languages-card"); const { queryByTestId, queryAllByTestId } = require("@testing-library/dom"); diff --git a/tests/renderWakatimeCard.test.js b/tests/renderWakatimeCard.test.js index 47f718cb33b07..51d63e6cf14c1 100644 --- a/tests/renderWakatimeCard.test.js +++ b/tests/renderWakatimeCard.test.js @@ -1,6 +1,7 @@ require("@testing-library/jest-dom"); -const renderWakatimeCard = require("../src/cards/wakatime-card"); +const { queryByTestId } = require("@testing-library/dom"); +const renderWakatimeCard = require("../src/cards/wakatime-card"); const { wakaTimeData } = require("./fetchWakatime.test"); describe("Test Render Wakatime Card", () => { @@ -16,6 +17,16 @@ describe("Test Render Wakatime Card", () => { expect(card).toMatchSnapshot(); }); + it("should hide languages when hide is passed", () => { + document.body.innerHTML = renderWakatimeCard(wakaTimeData.data, { + hide: ["YAML", "Other"], + }); + + expect(queryByTestId(document.body, /YAML/i)).toBeNull(); + expect(queryByTestId(document.body, /Other/i)).toBeNull(); + expect(queryByTestId(document.body, /TypeScript/i)).not.toBeNull(); + }); + it("should render translations", () => { document.body.innerHTML = renderWakatimeCard({}, { locale: "cn" }); expect(document.getElementsByClassName("header")[0].textContent).toBe( @@ -28,9 +39,19 @@ describe("Test Render Wakatime Card", () => { }); it("should render without rounding", () => { - document.body.innerHTML = renderWakatimeCard(wakaTimeData.data, { border_radius: "0" }); + document.body.innerHTML = renderWakatimeCard(wakaTimeData.data, { + border_radius: "0", + }); expect(document.querySelector("rect")).toHaveAttribute("rx", "0"); - document.body.innerHTML = renderWakatimeCard(wakaTimeData.data, { }); + document.body.innerHTML = renderWakatimeCard(wakaTimeData.data, {}); expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5"); }); + + it('should show "no coding activitiy this week" message when there hasn not been activity', () => { + document.body.innerHTML = renderWakatimeCard({ + ...wakaTimeData.data, + languages: undefined + }, {}); + expect(document.querySelector(".stat").textContent).toBe("No coding activity this week") + }) }); diff --git a/tests/utils.test.js b/tests/utils.test.js index 66f55d5d25129..4fdd6a8e8b12b 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -44,27 +44,6 @@ describe("Test utils.js", () => { ).toHaveTextContent(/Secondary Message/gim); }); - it("should test flexLayout", () => { - const layout = flexLayout({ - items: ["1", "2"], - gap: 60, - }).join(""); - - expect(layout).toBe( - `12`, - ); - - const columns = flexLayout({ - items: ["1", "2"], - gap: 60, - direction: "column", - }).join(""); - - expect(columns).toBe( - `12`, - ); - }); - it("getCardColors: should return expected values", () => { let colors = getCardColors({ title_color: "f00", @@ -138,4 +117,11 @@ describe("wrapTextMultiline", () => { ); expect(multiLineText).toEqual(["Hello", "world long..."]); }); + it("should wrap chinese by punctuation", () => { + let multiLineText = wrapTextMultiline( + "专门为刚开始刷题的同学准备的算法基地,没有最细只有更细,立志用动画将晦涩难懂的算法说的通俗易懂!", + ); + expect(multiLineText.length).toEqual(3); + expect(multiLineText[0].length).toEqual(18 * 8); // &#xxxxx; x 8 + }); }); diff --git a/themes/README.md b/themes/README.md index f6f299ff24d1e..ca34155db3fbf 100644 --- a/themes/README.md +++ b/themes/README.md @@ -2,7 +2,7 @@ -With inbuilt themes you can customize the look of the card without doing any manual customization. +With inbuilt themes, you can customize the look of the card without doing any manual customization. Use `?theme=THEME_NAME` parameter like so :- @@ -17,20 +17,24 @@ Use `?theme=THEME_NAME` parameter like so :- | | | | | :--: | :--: | :--: | | `default` ![default][default] | `dark` ![dark][dark] | `radical` ![radical][radical] | -| `merko` ![merko][merko] | `gruvbox` ![gruvbox][gruvbox] | `tokyonight` ![tokyonight][tokyonight] | -| `onedark` ![onedark][onedark] | `cobalt` ![cobalt][cobalt] | `synthwave` ![synthwave][synthwave] | -| `highcontrast` ![highcontrast][highcontrast] | `dracula` ![dracula][dracula] | `prussian` ![prussian][prussian] | -| `monokai` ![monokai][monokai] | `vue` ![vue][vue] | `vue-dark` ![vue-dark][vue-dark] | -| `shades-of-purple` ![shades-of-purple][shades-of-purple] | `nightowl` ![nightowl][nightowl] | `buefy` ![buefy][buefy] | -| `blue-green` ![blue-green][blue-green] | `algolia` ![algolia][algolia] | `great-gatsby` ![great-gatsby][great-gatsby] | -| `darcula` ![darcula][darcula] | `bear` ![bear][bear] | `solarized-dark` ![solarized-dark][solarized-dark] | -| `solarized-light` ![solarized-light][solarized-light] | `chartreuse-dark` ![chartreuse-dark][chartreuse-dark] | `nord` ![nord][nord] | -| `gotham` ![gotham][gotham] | `material-palenight` ![material-palenight][material-palenight] | `graywhite` ![graywhite][graywhite] | -| `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark] | `ayu-mirage` ![ayu-mirage][ayu-mirage] | `midnight-purple` ![midnight-purple][midnight-purple] | -| `calm` ![calm][calm] | `flag-india` ![flag-india][flag-india] | `omni` ![omni][omni] | -| `react` ![react][react] | `jolly` ![jolly][jolly] | `maroongold` ![maroongold][maroongold] | -| `yeblu` ![yeblu][yeblu] | `blueberry` ![blueberry][blueberry] | `slateorange` ![slateorange][slateorange] | -| `kacho_ga` ![kacho_ga][kacho_ga] | `outrun` ![outrun][outrun] | [Add your theme][add-theme] | +| `merko` ![merko][merko] | `gruvbox` ![gruvbox][gruvbox] | `gruvbox_light` ![gruvbox_light][gruvbox_light] | +| `tokyonight` ![tokyonight][tokyonight] | `onedark` ![onedark][onedark] | `cobalt` ![cobalt][cobalt] | +| `synthwave` ![synthwave][synthwave] | `highcontrast` ![highcontrast][highcontrast] | `dracula` ![dracula][dracula] | +| `prussian` ![prussian][prussian] | `monokai` ![monokai][monokai] | `vue` ![vue][vue] | +| `vue-dark` ![vue-dark][vue-dark] | `shades-of-purple` ![shades-of-purple][shades-of-purple] | `nightowl` ![nightowl][nightowl] | +| `buefy` ![buefy][buefy] | `blue-green` ![blue-green][blue-green] | `algolia` ![algolia][algolia] | +| `great-gatsby` ![great-gatsby][great-gatsby] | `darcula` ![darcula][darcula] | `bear` ![bear][bear] | +| `solarized-dark` ![solarized-dark][solarized-dark] | `solarized-light` ![solarized-light][solarized-light] | `chartreuse-dark` ![chartreuse-dark][chartreuse-dark] | +| `nord` ![nord][nord] | `gotham` ![gotham][gotham] | `material-palenight` ![material-palenight][material-palenight] | +| `graywhite` ![graywhite][graywhite] | `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark] | `ayu-mirage` ![ayu-mirage][ayu-mirage] | +| `midnight-purple` ![midnight-purple][midnight-purple] | `calm` ![calm][calm] | `flag-india` ![flag-india][flag-india] | +| `omni` ![omni][omni] | `react` ![react][react] | `jolly` ![jolly][jolly] | +| `maroongold` ![maroongold][maroongold] | `yeblu` ![yeblu][yeblu] | `blueberry` ![blueberry][blueberry] | +| `slateorange` ![slateorange][slateorange] | `kacho_ga` ![kacho_ga][kacho_ga] | `outrun` ![outrun][outrun] | +| `ocean_dark` ![ocean_dark][ocean_dark] | `city_lights` ![city_lights][city_lights] | `github_dark` ![github_dark][github_dark] | +| `discord_old_blurple` ![discord_old_blurple][discord_old_blurple] | `aura_dark` ![aura_dark][aura_dark] | `panda` ![panda][panda] | +| `noctis_minimus` ![noctis_minimus][noctis_minimus] | `cobalt2` ![cobalt2][cobalt2] | `swift` ![swift][swift] | +| `aura` ![aura][aura] | | [Add your theme][add-theme] | ## Repo Card @@ -39,20 +43,24 @@ Use `?theme=THEME_NAME` parameter like so :- | | | | | :--: | :--: | :--: | | `default_repocard` ![default_repocard][default_repocard_repo] | `dark` ![dark][dark_repo] | `radical` ![radical][radical_repo] | -| `merko` ![merko][merko_repo] | `gruvbox` ![gruvbox][gruvbox_repo] | `tokyonight` ![tokyonight][tokyonight_repo] | -| `onedark` ![onedark][onedark_repo] | `cobalt` ![cobalt][cobalt_repo] | `synthwave` ![synthwave][synthwave_repo] | -| `highcontrast` ![highcontrast][highcontrast_repo] | `dracula` ![dracula][dracula_repo] | `prussian` ![prussian][prussian_repo] | -| `monokai` ![monokai][monokai_repo] | `vue` ![vue][vue_repo] | `vue-dark` ![vue-dark][vue-dark_repo] | -| `shades-of-purple` ![shades-of-purple][shades-of-purple_repo] | `nightowl` ![nightowl][nightowl_repo] | `buefy` ![buefy][buefy_repo] | -| `blue-green` ![blue-green][blue-green_repo] | `algolia` ![algolia][algolia_repo] | `great-gatsby` ![great-gatsby][great-gatsby_repo] | -| `darcula` ![darcula][darcula_repo] | `bear` ![bear][bear_repo] | `solarized-dark` ![solarized-dark][solarized-dark_repo] | -| `solarized-light` ![solarized-light][solarized-light_repo] | `chartreuse-dark` ![chartreuse-dark][chartreuse-dark_repo] | `nord` ![nord][nord_repo] | -| `gotham` ![gotham][gotham_repo] | `material-palenight` ![material-palenight][material-palenight_repo] | `graywhite` ![graywhite][graywhite_repo] | -| `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark_repo] | `ayu-mirage` ![ayu-mirage][ayu-mirage_repo] | `midnight-purple` ![midnight-purple][midnight-purple_repo] | -| `calm` ![calm][calm_repo] | `flag-india` ![flag-india][flag-india_repo] | `omni` ![omni][omni_repo] | -| `react` ![react][react_repo] | `jolly` ![jolly][jolly_repo] | `maroongold` ![maroongold][maroongold_repo] | -| `yeblu` ![yeblu][yeblu_repo] | `blueberry` ![blueberry][blueberry_repo] | `slateorange` ![slateorange][slateorange_repo] | -| `kacho_ga` ![kacho_ga][kacho_ga_repo] | `outrun` ![outrun][outrun_repo] | [Add your theme][add-theme] | +| `merko` ![merko][merko_repo] | `gruvbox` ![gruvbox][gruvbox_repo] | `gruvbox_light` ![gruvbox_light][gruvbox_light_repo] | +| `tokyonight` ![tokyonight][tokyonight_repo] | `onedark` ![onedark][onedark_repo] | `cobalt` ![cobalt][cobalt_repo] | +| `synthwave` ![synthwave][synthwave_repo] | `highcontrast` ![highcontrast][highcontrast_repo] | `dracula` ![dracula][dracula_repo] | +| `prussian` ![prussian][prussian_repo] | `monokai` ![monokai][monokai_repo] | `vue` ![vue][vue_repo] | +| `vue-dark` ![vue-dark][vue-dark_repo] | `shades-of-purple` ![shades-of-purple][shades-of-purple_repo] | `nightowl` ![nightowl][nightowl_repo] | +| `buefy` ![buefy][buefy_repo] | `blue-green` ![blue-green][blue-green_repo] | `algolia` ![algolia][algolia_repo] | +| `great-gatsby` ![great-gatsby][great-gatsby_repo] | `darcula` ![darcula][darcula_repo] | `bear` ![bear][bear_repo] | +| `solarized-dark` ![solarized-dark][solarized-dark_repo] | `solarized-light` ![solarized-light][solarized-light_repo] | `chartreuse-dark` ![chartreuse-dark][chartreuse-dark_repo] | +| `nord` ![nord][nord_repo] | `gotham` ![gotham][gotham_repo] | `material-palenight` ![material-palenight][material-palenight_repo] | +| `graywhite` ![graywhite][graywhite_repo] | `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark_repo] | `ayu-mirage` ![ayu-mirage][ayu-mirage_repo] | +| `midnight-purple` ![midnight-purple][midnight-purple_repo] | `calm` ![calm][calm_repo] | `flag-india` ![flag-india][flag-india_repo] | +| `omni` ![omni][omni_repo] | `react` ![react][react_repo] | `jolly` ![jolly][jolly_repo] | +| `maroongold` ![maroongold][maroongold_repo] | `yeblu` ![yeblu][yeblu_repo] | `blueberry` ![blueberry][blueberry_repo] | +| `slateorange` ![slateorange][slateorange_repo] | `kacho_ga` ![kacho_ga][kacho_ga_repo] | `outrun` ![outrun][outrun_repo] | +| `ocean_dark` ![ocean_dark][ocean_dark_repo] | `city_lights` ![city_lights][city_lights_repo] | `github_dark` ![github_dark][github_dark_repo] | +| `discord_old_blurple` ![discord_old_blurple][discord_old_blurple_repo] | `aura_dark` ![aura_dark][aura_dark_repo] | `panda` ![panda][panda_repo] | +| `noctis_minimus` ![noctis_minimus][noctis_minimus_repo] | `cobalt2` ![cobalt2][cobalt2_repo] | `swift` ![swift][swift_repo] | +| `aura` ![aura][aura_repo] | | [Add your theme][add-theme] | [default]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=default @@ -61,6 +69,7 @@ Use `?theme=THEME_NAME` parameter like so :- [radical]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=radical [merko]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=merko [gruvbox]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=gruvbox +[gruvbox_light]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=gruvbox_light [tokyonight]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=tokyonight [onedark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=onedark [cobalt]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=cobalt @@ -100,6 +109,16 @@ Use `?theme=THEME_NAME` parameter like so :- [slateorange]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=slateorange [kacho_ga]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=kacho_ga [outrun]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=outrun +[ocean_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=ocean_dark +[city_lights]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=city_lights +[github_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=github_dark +[discord_old_blurple]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=discord_old_blurple +[aura_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=aura_dark +[panda]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=panda +[noctis_minimus]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=noctis_minimus +[cobalt2]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=cobalt2 +[swift]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=swift +[aura]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=aura [default_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default @@ -108,6 +127,7 @@ Use `?theme=THEME_NAME` parameter like so :- [radical_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=radical [merko_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=merko [gruvbox_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=gruvbox +[gruvbox_light_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=gruvbox_light [tokyonight_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=tokyonight [onedark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=onedark [cobalt_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=cobalt @@ -147,8 +167,18 @@ Use `?theme=THEME_NAME` parameter like so :- [slateorange_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=slateorange [kacho_ga_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=kacho_ga [outrun_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=outrun +[ocean_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=ocean_dark +[city_lights_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=city_lights +[github_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=github_dark +[discord_old_blurple_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=discord_old_blurple +[aura_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=aura_dark +[panda_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=panda +[noctis_minimus_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=noctis_minimus +[cobalt2_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=cobalt2 +[swift_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=swift +[aura_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=aura [add-theme]: https://github.com/anuraghazra/github-readme-stats/edit/master/themes/index.js -Wanted to add a new theme? Consider reading the [contribution guidelines](../CONTRIBUTING.md#themes-contribution) :D +Want to add a new theme? Consider reading the [contribution guidelines](../CONTRIBUTING.md#themes-contribution) :D diff --git a/themes/index.js b/themes/index.js index 046ef47cd8739..c727cf9333068 100644 --- a/themes/index.js +++ b/themes/index.js @@ -36,6 +36,12 @@ const themes = { text_color: "8ec07c", bg_color: "282828", }, + gruvbox_light: { + title_color: "b57614", + icon_color: "af3a03", + text_color: "427b58", + bg_color: "fbf1c7", + }, tokyonight: { title_color: "70a5fd", icon_color: "bf91f3", @@ -294,6 +300,48 @@ const themes = { text_color: "FFFFFF", bg_color: "2C2F33", }, + aura_dark: { + title_color: "ff7372", + icon_color: "6cffd0", + text_color: "dbdbdb", + bg_color: "252334", + }, + panda: { + title_color: "19f9d899", + icon_color: "19f9d899", + text_color: "FF75B5", + bg_color: "31353a", + }, + noctis_minimus: { + title_color: "d3b692", + icon_color: "72b7c0", + text_color: "c5cdd3", + bg_color: "1b2932", + }, + cobalt2: { + title_color: "ffc600", + icon_color: "ffffff", + text_color: "0088ff", + bg_color: "193549", + }, + swift: { + title_color: "000000", + icon_color: "f05237", + text_color: "000000", + bg_color: "f7f7f7", + }, + aura: { + title_color: "a277ff", + icon_color: "ffca85", + text_color: "61ffca", + bg_color: "15141b", + }, + apprentice: { + title_color: "ffffff", + icon_color: "ffffaf", + text_color: "bcbcbc", + bg_color: "262626", + }, }; module.exports = themes; diff --git a/vercel.json b/vercel.json index 8306d9f7896bb..ddf82eb15666f 100644 --- a/vercel.json +++ b/vercel.json @@ -1,4 +1,10 @@ { + "functions": { + "api/*.js": { + "memory": 128, + "maxDuration": 10 + } + }, "redirects": [ { "source": "/",