From 411d8f1ffa4181410ad514200271745a13a3be9f Mon Sep 17 00:00:00 2001 From: anuraghazra Date: Tue, 21 Jul 2020 17:03:05 +0530 Subject: [PATCH 1/5] feat: added Top languages card --- api/top-langs.js | 44 ++++++++++++++++++++ src/fetchTopLanguages.js | 84 +++++++++++++++++++++++++++++++++++++++ src/renderTopLanguages.js | 67 +++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+) create mode 100644 api/top-langs.js create mode 100644 src/fetchTopLanguages.js create mode 100644 src/renderTopLanguages.js diff --git a/api/top-langs.js b/api/top-langs.js new file mode 100644 index 0000000..5899d9d --- /dev/null +++ b/api/top-langs.js @@ -0,0 +1,44 @@ +require("dotenv").config(); +const { renderError, clampValue, CONSTANTS } = require("../src/utils"); +const fetchTopLanguages = require("../src/fetchTopLanguages"); +const renderTopLanguages = require("../src/renderTopLanguages"); + +module.exports = async (req, res) => { + const { + username, + card_width, + title_color, + text_color, + bg_color, + theme, + cache_seconds, + } = req.query; + let topLangs; + + res.setHeader("Content-Type", "image/svg+xml"); + + try { + topLangs = await fetchTopLanguages(username); + } catch (err) { + return res.send(renderError(err.message)); + } + + const cacheSeconds = clampValue( + parseInt(cache_seconds || CONSTANTS.THIRTY_MINUTES, 10), + CONSTANTS.THIRTY_MINUTES, + CONSTANTS.ONE_DAY + ); + + res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`); + + res.send( + renderTopLanguages(topLangs, { + theme, + card_width: parseInt(card_width, 10), + title_color, + text_color, + bg_color, + theme, + }) + ); +}; diff --git a/src/fetchTopLanguages.js b/src/fetchTopLanguages.js new file mode 100644 index 0000000..95c9b48 --- /dev/null +++ b/src/fetchTopLanguages.js @@ -0,0 +1,84 @@ +const { request } = require("./utils"); +const retryer = require("./retryer"); +require("dotenv").config(); + +const fetcher = (variables, token) => { + return request( + { + query: ` + query userInfo($login: String!) { + user(login: $login) { + repositories(isFork: false, first: 100) { + nodes { + languages(first: 1) { + edges { + size + node { + color + name + } + } + } + } + } + } + } + `, + variables, + }, + { + Authorization: `bearer ${token}`, + } + ); +}; + +async function fetchTopLanguages(username) { + if (!username) throw Error("Invalid username"); + + let res = await retryer(fetcher, { login: username }); + + if (res.data.errors) { + console.log(res.data.errors); + throw Error(res.data.errors[0].message || "Could not fetch user"); + } + + let repoNodes = res.data.data.user.repositories.nodes; + + // TODO: perf improvement + repoNodes = repoNodes + .filter((node) => { + return node.languages.edges.length > 0; + }) + .sort((a, b) => { + return b.languages.edges[0].size - a.languages.edges[0].size; + }) + .map((node) => { + return node.languages.edges[0]; + }) + .reduce((acc, prev) => { + let langSize = prev.size; + if (acc[prev.node.name] && prev.node.name === acc[prev.node.name].name) { + langSize = prev.size + acc[prev.node.name].size; + } + + return { + ...acc, + [prev.node.name]: { + name: prev.node.name, + color: prev.node.color, + size: langSize, + }, + }; + }, {}); + + const topLangs = Object.keys(repoNodes) + .slice(0, 5) + .reduce((result, key) => { + result[key] = repoNodes[key]; + return result; + }, {}); + + return topLangs; +} + +module.exports = fetchTopLanguages; diff --git a/src/renderTopLanguages.js b/src/renderTopLanguages.js new file mode 100644 index 0000000..608afbe --- /dev/null +++ b/src/renderTopLanguages.js @@ -0,0 +1,67 @@ +const { getCardColors, FlexLayout, clampValue } = require("../src/utils"); + +const createProgressNode = ({ width, color, name, progress }) => { + const paddingRight = 95; + const progressTextX = width - paddingRight + 10; + const progressWidth = width - paddingRight; + const progressPercentage = clampValue(progress, 2, 100); + + return ` + ${name} + ${progress}% + + + + + `; +}; + +const renderTopLanguages = (topLangs, options = {}) => { + const { title_color, text_color, bg_color, theme, card_width } = options; + + const langs = Object.values(topLangs); + + const totalSize = langs.reduce((acc, curr) => { + return acc + curr.size; + }, 0); + + // returns theme based colors with proper overrides and defaults + const { titleColor, textColor, bgColor } = getCardColors({ + title_color, + text_color, + bg_color, + theme, + }); + + const width = isNaN(card_width) ? 300 : card_width; + const height = 45 + (langs.length + 1) * 40; + + return ` + + + + + Top Languages + + + ${FlexLayout({ + items: langs.map((lang) => { + return createProgressNode({ + width: width, + name: lang.name, + color: lang.color || "#858585", + progress: ((lang.size / totalSize) * 100).toFixed(2), + }); + }), + gap: 40, + direction: "column", + })} + + + `; +}; + +module.exports = renderTopLanguages; From b50555ad9afc008729b5a7e15cad3bbe47a561cf Mon Sep 17 00:00:00 2001 From: anuraghazra Date: Tue, 21 Jul 2020 17:22:22 +0530 Subject: [PATCH 2/5] tests: added test for fetchTopLanguages --- tests/fetchTopLanguages.test.js | 84 +++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 tests/fetchTopLanguages.test.js diff --git a/tests/fetchTopLanguages.test.js b/tests/fetchTopLanguages.test.js new file mode 100644 index 0000000..9f90e77 --- /dev/null +++ b/tests/fetchTopLanguages.test.js @@ -0,0 +1,84 @@ +require("@testing-library/jest-dom"); +const axios = require("axios"); +const MockAdapter = require("axios-mock-adapter"); +const fetchTopLanguages = require("../src/fetchTopLanguages"); + +const mock = new MockAdapter(axios); + +afterEach(() => { + mock.reset(); +}); + +const data_langs = { + data: { + user: { + repositories: { + nodes: [ + { + languages: { + edges: [{ size: 100, node: { color: "#0f0", name: "HTML" } }], + }, + }, + { + languages: { + edges: [{ size: 100, node: { color: "#0f0", name: "HTML" } }], + }, + }, + { + languages: { + edges: [ + { size: 100, node: { color: "#0ff", name: "javascript" } }, + ], + }, + }, + { + languages: { + edges: [ + { size: 100, node: { color: "#0ff", name: "javascript" } }, + ], + }, + }, + ], + }, + }, + }, +}; + +const error = { + errors: [ + { + type: "NOT_FOUND", + path: ["user"], + locations: [], + message: "Could not resolve to a User with the login of 'noname'.", + }, + ], +}; + +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"); + expect(repo).toStrictEqual({ + HTML: { + color: "#0f0", + name: "HTML", + size: 200, + }, + javascript: { + color: "#0ff", + name: "javascript", + size: 200, + }, + }); + }); + + it("should throw error", async () => { + mock.onPost("https://api.github.com/graphql").reply(200, error); + + await expect(fetchTopLanguages("anuraghazra")).rejects.toThrow( + "Could not resolve to a User with the login of 'noname'." + ); + }); +}); From 7c104cf8c58d55cd76a07806760a3455dfd40364 Mon Sep 17 00:00:00 2001 From: anuraghazra Date: Tue, 21 Jul 2020 18:04:58 +0530 Subject: [PATCH 3/5] tests: added tests for renderTopLanguages & top-langs --- api/top-langs.js | 9 +- src/renderTopLanguages.js | 36 ++++-- tests/renderTopLanguages.test.js | 188 +++++++++++++++++++++++++++++++ tests/top-langs.test.js | 142 +++++++++++++++++++++++ 4 files changed, 367 insertions(+), 8 deletions(-) create mode 100644 tests/renderTopLanguages.test.js create mode 100644 tests/top-langs.test.js diff --git a/api/top-langs.js b/api/top-langs.js index 5899d9d..6c6634c 100644 --- a/api/top-langs.js +++ b/api/top-langs.js @@ -1,11 +1,17 @@ require("dotenv").config(); -const { renderError, clampValue, CONSTANTS } = require("../src/utils"); +const { + renderError, + clampValue, + parseBoolean, + CONSTANTS, +} = require("../src/utils"); const fetchTopLanguages = require("../src/fetchTopLanguages"); const renderTopLanguages = require("../src/renderTopLanguages"); module.exports = async (req, res) => { const { username, + hide_title, card_width, title_color, text_color, @@ -34,6 +40,7 @@ module.exports = async (req, res) => { res.send( renderTopLanguages(topLangs, { theme, + hide_title: parseBoolean(hide_title), card_width: parseInt(card_width, 10), title_color, text_color, diff --git a/src/renderTopLanguages.js b/src/renderTopLanguages.js index 608afbe..a8f5eb7 100644 --- a/src/renderTopLanguages.js +++ b/src/renderTopLanguages.js @@ -7,17 +7,31 @@ const createProgressNode = ({ width, color, name, progress }) => { const progressPercentage = clampValue(progress, 2, 100); return ` - ${name} + ${name} ${progress}% - + + `; }; const renderTopLanguages = (topLangs, options = {}) => { - const { title_color, text_color, bg_color, theme, card_width } = options; + const { + hide_title, + card_width, + title_color, + text_color, + bg_color, + theme, + } = options; const langs = Object.values(topLangs); @@ -34,8 +48,11 @@ const renderTopLanguages = (topLangs, options = {}) => { }); const width = isNaN(card_width) ? 300 : card_width; - const height = 45 + (langs.length + 1) * 40; + let height = 45 + (langs.length + 1) * 40; + if (hide_title) { + height -= 30; + } return ` - Top Languages + + ${ + hide_title + ? "" + : `Top Languages` + } - + ${FlexLayout({ items: langs.map((lang) => { return createProgressNode({ @@ -58,7 +80,7 @@ const renderTopLanguages = (topLangs, options = {}) => { }), gap: 40, direction: "column", - })} + }).join("")} `; diff --git a/tests/renderTopLanguages.test.js b/tests/renderTopLanguages.test.js new file mode 100644 index 0000000..b934dac --- /dev/null +++ b/tests/renderTopLanguages.test.js @@ -0,0 +1,188 @@ +require("@testing-library/jest-dom"); +const cssToObject = require("css-to-object"); +const renderTopLanguages = require("../src/renderTopLanguages"); + +const { + getByTestId, + queryByTestId, + queryAllByTestId, +} = require("@testing-library/dom"); +const themes = require("../themes"); + +describe("Test renderTopLanguages", () => { + const langs = { + HTML: { + color: "#0f0", + name: "HTML", + size: 200, + }, + javascript: { + color: "#0ff", + name: "javascript", + size: 200, + }, + css: { + color: "#ff0", + name: "css", + size: 100, + }, + }; + + it("should render correctly", () => { + document.body.innerHTML = renderTopLanguages(langs); + + expect(queryByTestId(document.body, "header")).toHaveTextContent( + "Top Languages" + ); + + expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent( + "HTML" + ); + expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent( + "javascript" + ); + expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent( + "css" + ); + expect(queryAllByTestId(document.body, "lang-progress")[0]).toHaveAttribute( + "width", + "40%" + ); + expect(queryAllByTestId(document.body, "lang-progress")[1]).toHaveAttribute( + "width", + "40%" + ); + expect(queryAllByTestId(document.body, "lang-progress")[2]).toHaveAttribute( + "width", + "20%" + ); + }); + + it("should resize the height correctly depending on langs", () => { + document.body.innerHTML = renderTopLanguages(langs, {}); + expect(document.querySelector("svg")).toHaveAttribute("height", "205"); + + document.body.innerHTML = renderTopLanguages( + { + ...langs, + python: { + color: "#ff0", + name: "python", + size: 100, + }, + }, + {} + ); + expect(document.querySelector("svg")).toHaveAttribute("height", "245"); + }); + + it("should hide_title", () => { + document.body.innerHTML = renderTopLanguages(langs, { hide_title: false }); + expect(document.querySelector("svg")).toHaveAttribute("height", "205"); + expect(queryByTestId(document.body, "lang-items")).toHaveAttribute( + "y", + "55" + ); + + // Lets hide now + document.body.innerHTML = renderTopLanguages(langs, { hide_title: true }); + expect(document.querySelector("svg")).toHaveAttribute("height", "175"); + + expect(queryByTestId(document.body, "header")).not.toBeInTheDocument(); + expect(queryByTestId(document.body, "lang-items")).toHaveAttribute( + "y", + "25" + ); + }); + + it("should render with custom width set", () => { + document.body.innerHTML = renderTopLanguages(langs, {}); + + expect(document.querySelector("svg")).toHaveAttribute("width", "300"); + + document.body.innerHTML = renderTopLanguages(langs, { card_width: 400 }); + expect(document.querySelector("svg")).toHaveAttribute("width", "400"); + }); + + it("should render default colors properly", () => { + document.body.innerHTML = renderTopLanguages(langs); + + const styleTag = document.querySelector("style"); + const stylesObject = cssToObject(styleTag.textContent); + + const headerStyles = stylesObject[".header"]; + const langNameStyles = stylesObject[".lang-name"]; + + expect(headerStyles.fill).toBe("#2f80ed"); + expect(langNameStyles.fill).toBe("#333"); + expect(queryByTestId(document.body, "card-bg")).toHaveAttribute( + "fill", + "#FFFEFE" + ); + }); + + it("should render custom colors properly", () => { + const customColors = { + title_color: "5a0", + icon_color: "1b998b", + text_color: "9991", + bg_color: "252525", + }; + + document.body.innerHTML = renderTopLanguages(langs, { ...customColors }); + + const styleTag = document.querySelector("style"); + const stylesObject = cssToObject(styleTag.innerHTML); + + const headerStyles = stylesObject[".header"]; + const langNameStyles = stylesObject[".lang-name"]; + + expect(headerStyles.fill).toBe(`#${customColors.title_color}`); + expect(langNameStyles.fill).toBe(`#${customColors.text_color}`); + expect(queryByTestId(document.body, "card-bg")).toHaveAttribute( + "fill", + "#252525" + ); + }); + + it("should render custom colors with themes", () => { + document.body.innerHTML = renderTopLanguages(langs, { + title_color: "5a0", + theme: "radical", + }); + + const styleTag = document.querySelector("style"); + const stylesObject = cssToObject(styleTag.innerHTML); + + const headerStyles = stylesObject[".header"]; + const langNameStyles = stylesObject[".lang-name"]; + + expect(headerStyles.fill).toBe("#5a0"); + expect(langNameStyles.fill).toBe(`#${themes.radical.text_color}`); + expect(queryByTestId(document.body, "card-bg")).toHaveAttribute( + "fill", + `#${themes.radical.bg_color}` + ); + }); + + it("should render with all the themes", () => { + Object.keys(themes).forEach((name) => { + document.body.innerHTML = renderTopLanguages(langs, { + theme: name, + }); + + const styleTag = document.querySelector("style"); + const stylesObject = cssToObject(styleTag.innerHTML); + + const headerStyles = stylesObject[".header"]; + const langNameStyles = stylesObject[".lang-name"]; + + expect(headerStyles.fill).toBe(`#${themes[name].title_color}`); + expect(langNameStyles.fill).toBe(`#${themes[name].text_color}`); + expect(queryByTestId(document.body, "card-bg")).toHaveAttribute( + "fill", + `#${themes[name].bg_color}` + ); + }); + }); +}); diff --git a/tests/top-langs.test.js b/tests/top-langs.test.js new file mode 100644 index 0000000..330acfa --- /dev/null +++ b/tests/top-langs.test.js @@ -0,0 +1,142 @@ +require("@testing-library/jest-dom"); +const axios = require("axios"); +const MockAdapter = require("axios-mock-adapter"); +const topLangs = require("../api/top-langs"); +const renderTopLanguages = require("../src/renderTopLanguages"); +const { renderError } = require("../src/utils"); + +const data_langs = { + data: { + user: { + repositories: { + nodes: [ + { + languages: { + edges: [{ size: 100, node: { color: "#0f0", name: "HTML" } }], + }, + }, + { + languages: { + edges: [{ size: 100, node: { color: "#0f0", name: "HTML" } }], + }, + }, + { + languages: { + edges: [ + { size: 100, node: { color: "#0ff", name: "javascript" } }, + ], + }, + }, + { + languages: { + edges: [ + { size: 100, node: { color: "#0ff", name: "javascript" } }, + ], + }, + }, + ], + }, + }, + }, +}; + +const error = { + errors: [ + { + type: "NOT_FOUND", + path: ["user"], + locations: [], + message: "Could not fetch user", + }, + ], +}; + +const langs = { + HTML: { + color: "#0f0", + name: "HTML", + size: 200, + }, + javascript: { + color: "#0ff", + name: "javascript", + size: 200, + }, +}; + +const mock = new MockAdapter(axios); + +afterEach(() => { + mock.reset(); +}); + +describe("Test /api/top-langs", () => { + it("should test the request", async () => { + const req = { + query: { + username: "anuraghazra", + }, + }; + const res = { + setHeader: jest.fn(), + send: jest.fn(), + }; + mock.onPost("https://api.github.com/graphql").reply(200, data_langs); + + await topLangs(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); + expect(res.send).toBeCalledWith(renderTopLanguages(langs)); + }); + + it("should work with the query options", async () => { + const req = { + query: { + username: "anuraghazra", + hide_title: true, + card_width: 100, + title_color: "fff", + icon_color: "fff", + text_color: "fff", + bg_color: "fff", + }, + }; + const res = { + setHeader: jest.fn(), + send: jest.fn(), + }; + mock.onPost("https://api.github.com/graphql").reply(200, data_langs); + + await topLangs(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); + expect(res.send).toBeCalledWith( + renderTopLanguages(langs, { + hide_title: true, + card_width: 100, + title_color: "fff", + icon_color: "fff", + text_color: "fff", + bg_color: "fff", + }) + ); + }); + + it("should render error card on error", async () => { + const req = { + query: { + username: "anuraghazra", + }, + }; + const res = { + setHeader: jest.fn(), + send: jest.fn(), + }; + mock.onPost("https://api.github.com/graphql").reply(200, error); + + await topLangs(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml"); + expect(res.send).toBeCalledWith(renderError(error.errors[0].message)); + }); +}); From 4a8fd03d8d1a1da813d4f13dae1afe42fe57079b Mon Sep 17 00:00:00 2001 From: anuraghazra Date: Tue, 21 Jul 2020 18:37:16 +0530 Subject: [PATCH 4/5] feat: added hide_langs_below option --- api/top-langs.js | 2 ++ src/renderTopLanguages.js | 9 ++++++++- tests/renderTopLanguages.test.js | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/api/top-langs.js b/api/top-langs.js index 6c6634c..026705c 100644 --- a/api/top-langs.js +++ b/api/top-langs.js @@ -11,6 +11,7 @@ const renderTopLanguages = require("../src/renderTopLanguages"); module.exports = async (req, res) => { const { username, + hide_langs_below, hide_title, card_width, title_color, @@ -42,6 +43,7 @@ module.exports = async (req, res) => { theme, hide_title: parseBoolean(hide_title), card_width: parseInt(card_width, 10), + hide_langs_below: parseFloat(hide_langs_below, 10), title_color, text_color, bg_color, diff --git a/src/renderTopLanguages.js b/src/renderTopLanguages.js index a8f5eb7..a233a09 100644 --- a/src/renderTopLanguages.js +++ b/src/renderTopLanguages.js @@ -30,15 +30,22 @@ const renderTopLanguages = (topLangs, options = {}) => { title_color, text_color, bg_color, + hide_langs_below, theme, } = options; - const langs = Object.values(topLangs); + let langs = Object.values(topLangs); const totalSize = langs.reduce((acc, curr) => { return acc + curr.size; }, 0); + // hide langs + langs = langs.filter((lang) => { + if (!hide_langs_below) return true; + return (lang.size / totalSize) * 100 > hide_langs_below; + }); + // returns theme based colors with proper overrides and defaults const { titleColor, textColor, bgColor } = getCardColors({ title_color, diff --git a/tests/renderTopLanguages.test.js b/tests/renderTopLanguages.test.js index b934dac..17e07c5 100644 --- a/tests/renderTopLanguages.test.js +++ b/tests/renderTopLanguages.test.js @@ -58,6 +58,20 @@ describe("Test renderTopLanguages", () => { ); }); + it("should hide_langs_below", () => { + document.body.innerHTML = renderTopLanguages(langs, { + hide_langs_below: 34, + }); + + expect(queryAllByTestId(document.body, "lang-name")[0]).toBeInTheDocument( + "HTML" + ); + expect(queryAllByTestId(document.body, "lang-name")[1]).toBeInTheDocument( + "javascript" + ); + expect(queryAllByTestId(document.body, "lang-name")[2]).not.toBeDefined(); + }); + it("should resize the height correctly depending on langs", () => { document.body.innerHTML = renderTopLanguages(langs, {}); expect(document.querySelector("svg")).toHaveAttribute("height", "205"); From bc7a9857a9e08668ef2856d39d9c14ac408eabd8 Mon Sep 17 00:00:00 2001 From: anuraghazra Date: Tue, 21 Jul 2020 18:53:24 +0530 Subject: [PATCH 5/5] docs: added top lang card docs --- readme.md | 112 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 73 insertions(+), 39 deletions(-) diff --git a/readme.md b/readme.md index c7012e1..0719d2e 100644 --- a/readme.md +++ b/readme.md @@ -35,6 +35,7 @@ - [GitHub Stats Card](#github-stats-card) - [GitHub Extra Pins](#github-extra-pins) +- [Top Languages Card](#top-languages-card) - [Themes](#themes) - [Customization](#customization) - [Deploy Yourself](#deploy-on-your-own-vercel-instance) @@ -93,27 +94,80 @@ You can customize the appearance of your `Stats Card` or `Repo Card` however you Customization Options: -| Option | type | description | Stats Card (default) | Repo Card (default) | -| ------------- | --------- | ------------------------------------ | -------------------- | ------------------- | -| title_color | hex color | title color | 2f80ed | 2f80ed | -| text_color | hex color | body color | 333 | 333 | -| icon_color | hex color | icon color | 4c71f2 | 586069 | -| bg_color | hex color | card bg color | FFFEFE | FFFEFE | -| line_height | number | control the line-height between text | 30 | N/A | -| hide_rank | boolean | hides the ranking | false | N/A | -| hide_title | boolean | hides the stats title | false | N/A | -| hide_border | boolean | hides the stats card border | false | N/A | -| show_owner | boolean | shows owner name in repo card | N/A | false | -| show_icons | boolean | shows icons | false | N/A | -| theme | string | sets inbuilt theme | 'default' | 'default_repocard' | -| cache_seconds | number | manually set custom cache control | 1800 | 1800 | +| Option | type | description | Stats Card (default) | Repo Card (default) | Top Lang Card (default) | +| ---------------- | --------- | ---------------------------------------------- | -------------------- | ------------------- | ----------------------- | +| title_color | hex color | title color | 2f80ed | 2f80ed | 2f80ed | +| text_color | hex color | body color | 333 | 333 | 333 | +| icon_color | hex color | icon color | 4c71f2 | 586069 | 586069 | +| bg_color | hex color | card bg color | FFFEFE | FFFEFE | FFFEFE | +| line_height | number | control the line-height between text | 30 | N/A | N/A | +| hide_rank | boolean | hides the ranking | false | N/A | N/A | +| hide_title | boolean | hides the stats title | false | N/A | false | +| hide_border | boolean | hides the stats card border | false | N/A | N/A | +| show_owner | boolean | shows owner name in repo card | N/A | false | N/A | +| show_icons | boolean | shows icons | false | N/A | N/A | +| theme | string | sets inbuilt theme | 'default' | 'default_repocard' | 'default | +| cache_seconds | number | manually set custom cache control | 1800 | 1800 | '1800' | +| hide_langs_below | number | hide langs below certain threshold (lang card) | N/A | N/A | undefined | > Note on cache: Repo cards have default cache of 30mins (1800 seconds) if the fork count & star count is less than 1k otherwise it's 2hours (7200). Also note that cache is clamped to minimum of 30min and maximum of 24hours ---- +# GitHub Extra Pins + +GitHub extra pins allow you to pin more than 6 repositories in your profile using a GitHub readme profile. + +Yey! You are no longer limited to 6 pinned repositories. + +### Usage + +Copy-paste this code into your readme and change the links. + +Endpoint: `api/pin?username=anuraghazra&repo=github-readme-stats` + +```md +[![ReadMe Card](https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats)](https://github.com/anuraghazra/github-readme-stats) +``` ### Demo +[![ReadMe Card](https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats)](https://github.com/anuraghazra/github-readme-stats) + +Use [show_owner](#customization) variable to include the repo's owner username + +[![ReadMe Card](https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&show_owner=true)](https://github.com/anuraghazra/github-readme-stats) + +# Top Languages Card + +Top languages card shows github user's top langauges which has been mostly used. + +_NOTE: Top languages does not indicate my skill level or something like that, it's a github metric of which languages i have the most code on github, it's a new feature of github-readme-stats_ + +### Usage + +Copy-paste this code into your readme and change the links. + +Endpoint: `api/top-langs?username=anuraghazra` + +```md +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats) +``` + +### Hide languages below certain threshold + +You can use `?hide_langs_below=NUMBER` parameter to hide languages below a specified threshold percentage. + +```md +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide_langs_below=1)](https://github.com/anuraghazra/github-readme-stats) +``` + +### Demo + +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats) + +--- + +### All Demos + - Default ![Anurag's github stats](https://github-readme-stats.vercel.app/api?username=anuraghazra) @@ -140,32 +194,12 @@ Choose from any of the [default themes](#themes) ![Customized Card](https://github-readme-stats.vercel.app/api/pin?username=anuraghazra&repo=github-readme-stats&title_color=fff&icon_color=f9f9f9&text_color=9f9f9f&bg_color=151515) +- Top languages + +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats) + --- -# GitHub Extra Pins - -GitHub extra pins allow you to pin more than 6 repositories in your profile using a GitHub readme profile. - -Yey! You are no longer limited to 6 pinned repositories. - -### Usage - -Copy-paste this code into your readme and change the links. - -Endpoint: `api/pin?username=anuraghazra&repo=github-readme-stats` - -```md -[![ReadMe Card](https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats)](https://github.com/anuraghazra/github-readme-stats) -``` - -### Demo - -[![ReadMe Card](https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats)](https://github.com/anuraghazra/github-readme-stats) - -Use [show_owner](#customization) variable to include the repo's owner username - -[![ReadMe Card](https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&show_owner=true)](https://github.com/anuraghazra/github-readme-stats) - ### Quick Tip (Align The Repo Cards) You usually won't be able to layout the images side by side. To do that you can use this approach: