diff --git a/api/index.js b/api/index.js index 0cda587..c983b43 100644 --- a/api/index.js +++ b/api/index.js @@ -34,8 +34,6 @@ module.exports = async (req, res) => { border_radius, border_color, } = req.query; - let stats; - res.setHeader("Content-Type", "image/svg+xml"); if (blacklist.includes(username)) { @@ -47,7 +45,7 @@ module.exports = async (req, res) => { } try { - stats = await fetchStats( + const stats = await fetchStats( username, parseBoolean(count_private), parseBoolean(include_all_commits), diff --git a/api/pin.js b/api/pin.js index 65d09f0..1df7fd0 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), diff --git a/api/top-langs.js b/api/top-langs.js index 89306fa..52ac593 100644 --- a/api/top-langs.js +++ b/api/top-langs.js @@ -31,8 +31,6 @@ module.exports = async (req, res) => { border_radius, border_color, } = req.query; - let topLangs; - res.setHeader("Content-Type", "image/svg+xml"); if (blacklist.includes(username)) { @@ -44,7 +42,7 @@ module.exports = async (req, res) => { } try { - topLangs = await fetchTopLanguages( + const topLangs = await fetchTopLanguages( username, parseArray(exclude_repo), parseArray(hide), diff --git a/src/cards/repo-card.js b/src/cards/repo-card.js index 4573fcf..5e6a28d 100644 --- a/src/cards/repo-card.js +++ b/src/cards/repo-card.js @@ -110,15 +110,14 @@ const renderRepoCard = (repo, options = {}) => { }); // 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 colors = getCardColors({ + title_color, + icon_color, + text_color, + bg_color, + border_color, + theme, + }); const svgLanguage = primaryLanguage ? createLanguageNode(langName, langColor) @@ -145,22 +144,16 @@ const renderRepoCard = (repo, options = {}) => { 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 } `); @@ -168,9 +161,9 @@ const renderRepoCard = (repo, options = {}) => { return card.render(` ${ isTemplate - ? getBadgeSVG(i18n.t("repocard.template"), textColor) + ? getBadgeSVG(i18n.t("repocard.template"), colors.textColor) : isArchived - ? getBadgeSVG(i18n.t("repocard.archived"), textColor) + ? getBadgeSVG(i18n.t("repocard.archived"), colors.textColor) : "" } diff --git a/src/common/Card.js b/src/common/Card.js index 520560f..fd1fa77 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 ` @@ -21,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) => { @@ -30,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; @@ -52,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; @@ -79,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", @@ -92,8 +136,8 @@ 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 {number[]?=} props.sizes + * @param {"column" | "row"?=} props.direction * * @returns {string[]} * @@ -115,14 +159,27 @@ function flexLayout({ items, gap, direction, sizes = [] }) { }); } -// 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]; @@ -157,6 +214,12 @@ function getCardColors({ return { titleColor, iconColor, textColor, bgColor, borderColor }; } +/** + * @param {string} text + * @param {number} width + * @param {number} maxLines + * @returns {string[]} + */ function wrapTextMultiline(text, width = 60, maxLines = 3) { const wrapped = wrap(encodeHTML(text), { width }) .split("\n") // Split wrapped lines to get an array of lines @@ -193,6 +256,10 @@ const SECONDARY_ERROR_MESSAGES = { }; class CustomError extends Error { + /** + * @param {string} message + * @param {string} type + */ constructor(message, type) { super(message); this.type = type; @@ -203,7 +270,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 = [ @@ -237,6 +309,8 @@ function measureText(str, fontSize = 10) { .reduce((cur, acc) => acc + cur) * fontSize ); } + +/** @param {string} name */ const lowercaseTrim = (name) => name.toLowerCase().trim(); /** diff --git a/src/getStyles.js b/src/getStyles.js index b15f46d..fd70c24 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,