-
Notifications
You must be signed in to change notification settings - Fork 395
feat: Add custom theme support #188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughCards and internal SVG generators were extended to accept an optional custom Theme built from six new color query parameters; numerous card APIs and card modules now pass a Theme object through updated function signatures to drive SVG color overrides. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API as Card API Endpoint
participant Theme as Theme Constructor
participant CardModule as Card SVG Generator
participant ThemeMap as ThemeMap
participant SVG as SVG Template
Note over Client,API: Request with optional color query params
Client->>API: GET /api/card?username=...&title_color=...&...
API->>Theme: validate params & new Theme(...)
Theme-->>API: customTheme
API->>CardModule: get...SVGWithThemeName(username, themeName, customTheme, ...)
CardModule->>ThemeMap: base = ThemeMap.get(themeName)
alt customTheme provided
CardModule->>CardModule: merge base with customTheme (prefix '#' where needed, set strokeOpacity)
end
CardModule->>SVG: render with merged theme
SVG-->>API: SVG string
API-->>Client: 200 SVG
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
Outside diff range and nitpick comments (9)
api/cards/stats.ts (1)
Line range hint
4-64: Refactor repeated type checks to a utility function for cleaner code.+ function validateParameter(param, paramName, res) { + if (typeof param !== 'string') { + res.status(400).send(`${paramName} must be a string`); + return false; + } + return true; + } - if (typeof theme !== 'string') { + if (!validateParameter(theme, 'theme', res)) { - res.status(400).send('theme must be a string'); - return; - } ...api/cards/profile-details.ts (1)
Line range hint
4-64: Refactor repeated type checks to a utility function for cleaner code, similar to the suggestion instats.ts.api/cards/productive-time.ts (1)
Line range hint
4-69: Refactor repeated type checks to a utility function for cleaner code, similar to the suggestion instats.ts.src/cards/most-commit-language-card.ts (1)
Line range hint
26-59: Consider converting this function expression to an arrow function to reduce complexity and improve readability.- const getCommitsLanguageSVG = function ( + const getCommitsLanguageSVG = ( langData: {name: string; value: number; color: string}[], themeName: string, customTheme: Theme | undefined ): string => {Tools
GitHub Check: lint
[failure] 48-48:
'theme' is never reassigned. Use 'const' instead
[failure] 48-48:
Replace·...ThemeMap.get(themeName)!·with...ThemeMap.get(themeName)!
[failure] 50-50:
Replace"#"with'#'
[failure] 51-51:
Replace"#"with'#'
[failure] 52-52:
Replace"#"with'#'
[failure] 53-53:
Replace·theme.stroke·=·"#"·+·customTheme.stroke;·theme.strokeOpacity·=·1;with⏎············theme.stroke·=·'#'·+·customTheme.stroke;⏎············theme.strokeOpacity·=·1;⏎·······
[failure] 54-54:
Replace"#"with'#'
[failure] 55-55:
Replace"#"with'#'api/cards/repos-per-language.ts (1)
Line range hint
67-69: Consider using an arrow function for theforEachcallback to enhance readability and align with modern JavaScript practices.- exclude.split(',').forEach(function (val) { + exclude.split(',').forEach(val => {src/cards/stats-card.ts (1)
Line range hint
47-112: Consider converting this function into an arrow function to make the code more concise and to use lexical scoping.- const getStatsData = async function ( + const getStatsData = async (src/cards/profile-details-card.ts (3)
Line range hint
9-24: Use arrow functions for these kinds of simple transformations to improve readability and maintain lexical scoping.- export const createProfileDetailsCard = async function (username: string) { + export const createProfileDetailsCard = async (username: string) => {Tools
Biome
[error] 25-34: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 36-54: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
Line range hint
56-72: Consider using arrow functions to simplify these utility functions, which do not require their own this context.- const getProfileDateJoined = function (profileDetails: ProfileDetails): string { + const getProfileDateJoined = (profileDetails: ProfileDetails): string => {
Line range hint
74-147: This function could benefit from the arrow function syntax for better readability and scoping.- const getProfileDetailsData = async function ( + const getProfileDetailsData = async (
Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Files selected for processing (10)
- api/cards/most-commit-language.ts (2 hunks)
- api/cards/productive-time.ts (2 hunks)
- api/cards/profile-details.ts (2 hunks)
- api/cards/repos-per-language.ts (3 hunks)
- api/cards/stats.ts (2 hunks)
- src/cards/most-commit-language-card.ts (3 hunks)
- src/cards/productive-time-card.ts (2 hunks)
- src/cards/profile-details-card.ts (2 hunks)
- src/cards/repos-per-language-card.ts (2 hunks)
- src/cards/stats-card.ts (2 hunks)
Additional context used
Biome
src/cards/repos-per-language-card.ts
[error] 6-13: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 15-24: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 26-42: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 56-58: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 44-61: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.src/cards/most-commit-language-card.ts
[error] 6-13: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 15-24: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 26-59: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 76-78: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 61-82: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.api/cards/repos-per-language.ts
[error] 67-69: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.api/cards/most-commit-language.ts
[error] 58-60: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.src/cards/productive-time-card.ts
[error] 6-13: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 15-24: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 26-43: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 49-60: This else clause can be omitted because previous branches break early. (lint/style/noUselessElse)
Unsafe fix: Omit the else clause.
[error] 53-60: This else clause can be omitted because previous branches break early. (lint/style/noUselessElse)
[error] 57-60: This else clause can be omitted because previous branches break early. (lint/style/noUselessElse)
[error] 45-61: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 63-91: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.src/cards/stats-card.ts
[error] 9-16: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 18-26: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 28-45: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 47-112: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.src/cards/profile-details-card.ts
[error] 9-24: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 25-34: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 36-54: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 56-72: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
[error] 74-147: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
GitHub Check: lint
src/cards/most-commit-language-card.ts
[failure] 48-48:
'theme' is never reassigned. Use 'const' instead
[failure] 48-48:
Replace·...ThemeMap.get(themeName)!·with...ThemeMap.get(themeName)!
[failure] 50-50:
Replace"#"with'#'
[failure] 51-51:
Replace"#"with'#'
[failure] 52-52:
Replace"#"with'#'
[failure] 53-53:
Replace·theme.stroke·=·"#"·+·customTheme.stroke;·theme.strokeOpacity·=·1;with⏎············theme.stroke·=·'#'·+·customTheme.stroke;⏎············theme.strokeOpacity·=·1;⏎·······
[failure] 54-54:
Replace"#"with'#'
[failure] 55-55:
Replace"#"with'#'src/cards/productive-time-card.ts
[failure] 32-32:
'theme' is never reassigned. Use 'const' instead
[failure] 32-32:
Replace·...ThemeMap.get(themeName)!·with...ThemeMap.get(themeName)!
Additional comments not posted (7)
src/cards/most-commit-language-card.ts (2)
18-18: The addition ofcustomThemeparameter allows for dynamic theming which is a great feature enhancement.Also applies to: 23-23
9-9: Consider using an arrow function for better consistency and to leverage ES6 features.- for (const themeName of ThemeMap.keys()) { + ThemeMap.keys().forEach(themeName => {Likely invalid or redundant comment.
api/cards/repos-per-language.ts (1)
9-19: The addition of multiple theme-related parameters enhances customization capabilities. Ensure thorough validation for each parameter to prevent potential security risks or errors.api/cards/most-commit-language.ts (1)
9-19: Similar to the previous file, ensure that all new query parameters are validated properly to avoid errors and potential security issues.src/cards/productive-time-card.ts (2)
18-23: The addition ofcustomThemeparameter allows for dynamic theming which is a great feature enhancement.
9-9: Using an arrow function here would align with ES6 best practices and improve readability.- for (const themeName of ThemeMap.keys()) { + ThemeMap.keys().forEach(themeName => {Likely invalid or redundant comment.
src/cards/stats-card.ts (1)
18-26: Refactor to use arrow function for consistency and to leverage lexical this.
[REFACTOR_SUGGESTion]- export const getStatsSVGWithThemeName = async function ( + export const getStatsSVGWithThemeName = async (Tools
Biome
[error] 18-26: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
| StatsData: {index: number; icon: string; name: string; value: string}[], | ||
| themeName: string | ||
| themeName: string, | ||
| customTheme: Theme | undefined | ||
| ) { | ||
| const title = 'Stats'; | ||
| const svgString = statsCard(`${title}`, StatsData, ThemeMap.get(themeName)!); | ||
| let theme = { ...ThemeMap.get(themeName)! }; | ||
| if (customTheme !== undefined) { | ||
| if (customTheme.title) theme.title = "#" + customTheme.title; | ||
| if (customTheme.text) theme.text = "#" + customTheme.text; | ||
| if (customTheme.background) theme.background = "#" + customTheme.background; | ||
| if (customTheme.stroke) { theme.stroke = "#" + customTheme.stroke; theme.strokeOpacity = 1; } | ||
| if (customTheme.icon) theme.icon = "#" + customTheme.icon; | ||
| if (customTheme.chart) theme.chart = "#" + customTheme.chart; | ||
| } | ||
| const svgString = statsCard(`${title}`, StatsData, theme); | ||
| return svgString; | ||
| }; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function can be simplified using an arrow function, improving readability and scoping.
- const getStatsSVG = function (
+ const getStatsSVG = (Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const getStatsSVG = function ( | |
| StatsData: {index: number; icon: string; name: string; value: string}[], | |
| themeName: string | |
| themeName: string, | |
| customTheme: Theme | undefined | |
| ) { | |
| const title = 'Stats'; | |
| const svgString = statsCard(`${title}`, StatsData, ThemeMap.get(themeName)!); | |
| let theme = { ...ThemeMap.get(themeName)! }; | |
| if (customTheme !== undefined) { | |
| if (customTheme.title) theme.title = "#" + customTheme.title; | |
| if (customTheme.text) theme.text = "#" + customTheme.text; | |
| if (customTheme.background) theme.background = "#" + customTheme.background; | |
| if (customTheme.stroke) { theme.stroke = "#" + customTheme.stroke; theme.strokeOpacity = 1; } | |
| if (customTheme.icon) theme.icon = "#" + customTheme.icon; | |
| if (customTheme.chart) theme.chart = "#" + customTheme.chart; | |
| } | |
| const svgString = statsCard(`${title}`, StatsData, theme); | |
| return svgString; | |
| }; | |
| const getStatsSVG = ( | |
| StatsData: {index: number; icon: string; name: string; value: string}[], | |
| themeName: string, | |
| customTheme: Theme | undefined | |
| ) => { | |
| const title = 'Stats'; | |
| let theme = { ...ThemeMap.get(themeName)! }; | |
| if (customTheme !== undefined) { | |
| if (customTheme.title) theme.title = "#" + customTheme.title; | |
| if (customTheme.text) theme.text = "#" + customTheme.text; | |
| if (customTheme.background) theme.background = "#" + customTheme.background; | |
| if (customTheme.stroke) { theme.stroke = "#" + customTheme.stroke; theme.strokeOpacity = 1; } | |
| if (customTheme.icon) theme.icon = "#" + customTheme.icon; | |
| if (customTheme.chart) theme.chart = "#" + customTheme.chart; | |
| } | |
| const svgString = statsCard(`${title}`, StatsData, theme); | |
| return svgString; | |
| }; |
Tools
Biome
[error] 28-45: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
| export const getReposPerLanguageSVGWithThemeName = async function ( | ||
| username: string, | ||
| themeName: string, | ||
| customTheme: Theme, | ||
| exclude: Array<string> | ||
| ) { | ||
| if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist'); | ||
| const langData = await getRepoLanguageData(username, exclude); | ||
| return getReposPerLanguageSVG(langData, themeName); | ||
| return getReposPerLanguageSVG(langData, themeName, customTheme); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Convert to arrow function for improved readability and consistency.
- export const getReposPerLanguageSVGWithThemeName = async function (
+ export const getReposPerLanguageSVGWithThemeName = async (Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const getReposPerLanguageSVGWithThemeName = async function ( | |
| username: string, | |
| themeName: string, | |
| customTheme: Theme, | |
| exclude: Array<string> | |
| ) { | |
| if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist'); | |
| const langData = await getRepoLanguageData(username, exclude); | |
| return getReposPerLanguageSVG(langData, themeName); | |
| return getReposPerLanguageSVG(langData, themeName, customTheme); | |
| }; | |
| export const getReposPerLanguageSVGWithThemeName = async ( | |
| username: string, | |
| themeName: string, | |
| customTheme: Theme, | |
| exclude: Array<string> | |
| ) => { | |
| if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist'); | |
| const langData = await getRepoLanguageData(username, exclude); | |
| return getReposPerLanguageSVG(langData, themeName, customTheme); | |
| }; |
Tools
Biome
[error] 15-24: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
| if (typeof title_color !== 'string') { | ||
| res.status(400).send('title_color must be a string'); | ||
| return; | ||
| } | ||
| if (typeof text_color !== 'string') { | ||
| res.status(400).send('text_color must be a string'); | ||
| return; | ||
| } | ||
| if (typeof bg_color !== 'string') { | ||
| res.status(400).send('bg_color must be a string'); | ||
| return; | ||
| } | ||
| if (typeof border_color !== 'string') { | ||
| res.status(400).send('border_color must be a string'); | ||
| return; | ||
| } | ||
| if (typeof icon_color !== 'string') { | ||
| res.status(400).send('icon_color must be a string'); | ||
| return; | ||
| } | ||
| if (typeof chart_color !== 'string') { | ||
| res.status(400).send('chart_color must be a string'); | ||
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The repeated pattern of parameter validation can be refactored into a utility function to adhere to DRY principles.
+ function validateParam(param, paramName) {
+ if (typeof param !== 'string') {
+ res.status(400).send(`${paramName} must be a string`);
+ return false;
+ }
+ return true;
+ }
- if (typeof title_color !== 'string') {
- res.status(400).send('title_color must be a string');
- return;
- }
+ if (!validateParam(title_color, 'title_color')) return;Committable suggestion was skipped due to low confidence.
| exclude.split(',').forEach(function (val) { | ||
| excludeArr.push(translateLanguage(val)); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using an arrow function for the forEach callback to improve consistency with modern JavaScript syntax.
- exclude.split(',').forEach(function (val) {
+ exclude.split(',').forEach(val => {Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| exclude.split(',').forEach(function (val) { | |
| excludeArr.push(translateLanguage(val)); | |
| }); | |
| exclude.split(',').forEach(val => { | |
| excludeArr.push(translateLanguage(val)); | |
| }); |
Tools
Biome
[error] 58-60: This function expression can be turned into an arrow function. (lint/complexity/useArrowFunction)
Function expressions that don't use this can be turned into arrow functions.
Safe fix: Use an arrow function instead.
| if (typeof title_color !== 'string') { | ||
| res.status(400).send('title_color must be a string'); | ||
| return; | ||
| } | ||
| if (typeof text_color !== 'string') { | ||
| res.status(400).send('text_color must be a string'); | ||
| return; | ||
| } | ||
| if (typeof bg_color !== 'string') { | ||
| res.status(400).send('bg_color must be a string'); | ||
| return; | ||
| } | ||
| if (typeof border_color !== 'string') { | ||
| res.status(400).send('border_color must be a string'); | ||
| return; | ||
| } | ||
| if (typeof icon_color !== 'string') { | ||
| res.status(400).send('icon_color must be a string'); | ||
| return; | ||
| } | ||
| if (typeof chart_color !== 'string') { | ||
| res.status(400).send('chart_color must be a string'); | ||
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactor the repeated validation code into a utility function to reduce redundancy and enhance maintainability.
+ function validateParam(param, paramName) {
+ if (typeof param !== 'string') {
+ res.status(400).send(`${paramName} must be a string`);
+ return false;
+ }
+ return true;
+ }
- if (typeof title_color !== 'string') {
- res.status(400).send('title_color must be a string');
- return;
- }
+ if (!validateParam(title_color, 'title_color')) return;Committable suggestion was skipped due to low confidence.
|
Hi, sorry for the delayed reply. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
api/cards/most-commit-language.ts (1)
33-56: Refactor repetitive validation into a utility function.The validation logic for the six color parameters is highly repetitive and duplicated across both card endpoint files. This violates DRY principles and increases maintenance burden.
Create a shared validation utility function:
// api/utils/query-validator.ts export function validateStringParam( value: any, paramName: string, res: VercelResponse ): boolean { if (typeof value !== 'string') { res.status(400).send(`${paramName} must be a string`); return false; } return true; }Then refactor this validation block:
- if (typeof title_color !== 'string') { - res.status(400).send('title_color must be a string'); - return; - } - if (typeof text_color !== 'string') { - res.status(400).send('text_color must be a string'); - return; - } - if (typeof bg_color !== 'string') { - res.status(400).send('bg_color must be a string'); - return; - } - if (typeof border_color !== 'string') { - res.status(400).send('border_color must be a string'); - return; - } - if (typeof icon_color !== 'string') { - res.status(400).send('icon_color must be a string'); - return; - } - if (typeof chart_color !== 'string') { - res.status(400).send('chart_color must be a string'); - return; - } + const colorParams = [ + { value: title_color, name: 'title_color' }, + { value: text_color, name: 'text_color' }, + { value: bg_color, name: 'bg_color' }, + { value: border_color, name: 'border_color' }, + { value: icon_color, name: 'icon_color' }, + { value: chart_color, name: 'chart_color' } + ]; + for (const param of colorParams) { + if (!validateStringParam(param.value, param.name, res)) return; + }Apply the same refactoring to
api/cards/repos-per-language.ts.api/cards/repos-per-language.ts (1)
33-56: Apply the same validation refactoring as most-commit-language.ts.This validation block is identical to lines 33-56 in
api/cards/most-commit-language.ts. The same refactoring using a shared utility function should be applied here to eliminate duplication across both files.
🧹 Nitpick comments (1)
api/cards/most-commit-language.ts (1)
67-67: Consider making strokeOpacity optional in Theme constructor.The hardcoded
-1value with the comment "strokeOpacity is not used in custom themes" suggests this parameter isn't relevant for custom themes. Consider refactoring the Theme class to make strokeOpacity optional or creating a separate factory method for custom themes to avoid passing placeholder values.Example refactoring in
src/const/theme.ts:export class Theme { // ... existing properties constructor( title: string, text: string, background: string, stroke: string, strokeOpacity: number, icon: string, chart: string ) { /* ... */ } static fromCustomColors( title: string, text: string, background: string, stroke: string, icon: string, chart: string ): Theme { return new Theme(title, text, background, stroke, -1, icon, chart); } }Then use:
let customTheme = Theme.fromCustomColors(title_color, text_color, bg_color, border_color, icon_color, chart_color);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
api/cards/most-commit-language.ts(2 hunks)api/cards/repos-per-language.ts(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
api/cards/repos-per-language.ts (3)
api/cards/most-commit-language.ts (1)
req(8-91)src/const/theme.ts (1)
Theme(3-28)src/cards/repos-per-language-card.ts (1)
getReposPerLanguageSVGWithThemeName(15-24)
api/cards/most-commit-language.ts (3)
src/utils/translator.ts (1)
translateLanguage(1-273)src/const/theme.ts (1)
Theme(3-28)src/cards/most-commit-language-card.ts (1)
getCommitsLanguageSVGWithThemeName(15-24)
🔇 Additional comments (6)
api/cards/most-commit-language.ts (3)
5-5: LGTM!The Theme import is correctly added to support custom theming functionality.
76-76: LGTM!The function call correctly passes the customTheme parameter to enable custom theme rendering.
9-19: Empty string color parameters are correctly handled—no changes required.Empty strings default for color parameters (when not provided in query) are safely ignored through truthiness checks in the card rendering logic. When
customThemeproperties contain empty strings, the conditions likeif (customTheme.title)evaluate to false and don't override the selected theme's defaults. The implementation is correct and secure.api/cards/repos-per-language.ts (3)
5-5: LGTM!The Theme import is correctly added, consistent with the implementation in
api/cards/most-commit-language.ts.
9-19: Verify empty string handling (same concern as most-commit-language.ts).Like in
most-commit-language.ts, these color parameters default to empty strings. Ensure consistent handling of empty values across both card endpoints.
76-76: LGTM!The function call is correctly updated to pass the customTheme parameter, consistent with the updated signature.
| let customTheme = new Theme( | ||
| title_color, | ||
| text_color, | ||
| bg_color, | ||
| border_color, | ||
| -1, // strokeOpacity is not used in custom themes | ||
| icon_color, | ||
| chart_color | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider validating color format to prevent rendering issues.
The color parameters are passed directly to the Theme constructor without validating that they're valid color values (e.g., hex codes like #ffffff, rgb(), or named colors). Malformed color values could cause unexpected SVG rendering behavior or visual glitches.
Consider adding basic color format validation:
function isValidColor(color: string): boolean {
if (color === '') return true; // Empty string handled as fallback
// Validate hex colors (with or without alpha)
const hexPattern = /^[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/;
// Validate rgb/rgba
const rgbPattern = /^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+(\s*,\s*[\d.]+)?\s*\)$/;
return hexPattern.test(color) || rgbPattern.test(color) || CSS_COLOR_NAMES.includes(color.toLowerCase());
}
// Validate each color parameter before creating the theme
const colors = { title_color, text_color, bg_color, border_color, icon_color, chart_color };
for (const [name, value] of Object.entries(colors)) {
if (value && !isValidColor(value)) {
res.status(400).send(`${name} must be a valid color format`);
return;
}
}🤖 Prompt for AI Agents
In api/cards/most-commit-language.ts around lines 62 to 70, the Theme is
constructed with raw color inputs which can produce broken SVGs if malformed;
validate each incoming color (title_color, text_color, bg_color, border_color,
icon_color, chart_color) before building the Theme by: ensure values are strings
or empty (empty = fallback), accept and normalize common formats (hex with
optional leading '#', 3/6/8 hex digits, rgb(...) and rgba(...), and known CSS
color names), reject anything else and respond early with a 400 and a clear
message identifying the invalid parameter; perform normalization (e.g.,
add/strip leading '#' for hex consistently) and only then call new Theme(...) so
rendering always receives valid color values.
| let customTheme = new Theme( | ||
| title_color, | ||
| text_color, | ||
| bg_color, | ||
| border_color, | ||
| -1, // strokeOpacity is not used in custom themes | ||
| icon_color, | ||
| chart_color | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same color validation and strokeOpacity concerns apply here.
The Theme construction in this file has the same issues noted in api/cards/most-commit-language.ts:
- No color format validation before passing to Theme constructor
- strokeOpacity placeholder value of -1
Consider applying the same color validation logic and strokeOpacity refactoring suggestions mentioned for the other file.
🤖 Prompt for AI Agents
In api/cards/repos-per-language.ts around lines 57 to 65, the Theme is
constructed without validating color inputs and uses a placeholder strokeOpacity
of -1; apply the same fixes as in api/cards/most-commit-language.ts by
validating/normalizing each color (title_color, text_color, bg_color,
border_color, icon_color, chart_color) before passing them to Theme (use the
shared color validation helper or implement the same validation logic), and stop
using -1 as a magic value for strokeOpacity—either pass a proper opacity value
(0-1) or make strokeOpacity optional in Theme and supply a sensible default when
constructing the Theme; ensure invalid colors fall back to safe defaults or
cause a clear error, and update callers accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/cards/most-commit-language-card.ts (1)
15-60: Avoid breaking consumers; accept customTheme as optionalJust like the productive-time card, making
customThemea requiredThemehere breaks existing call sites that only pass(username, themeName, exclude)and clashes with theundefinedguard. Please keep it optional (and partial) so the old API contract still works.export const getCommitsLanguageSVGWithThemeName = async function ( username: string, themeName: string, - customTheme: Theme, + customTheme?: Partial<Theme>, exclude: Array<string> ): Promise<string> { @@ -const getCommitsLanguageSVG = function ( - langData: {name: string; value: number; color: string}[], - themeName: string, - customTheme: Theme | undefined +const getCommitsLanguageSVG = function ( + langData: {name: string; value: number; color: string}[], + themeName: string, + customTheme?: Partial<Theme> ): string { @@ - const theme = {...ThemeMap.get(themeName)!}; - if (customTheme !== undefined) { + const theme = {...ThemeMap.get(themeName)!}; + if (customTheme) {
🧹 Nitpick comments (3)
src/cards/profile-details-card.ts (3)
51-59: Consider more explicit checks for optional properties.The truthiness checks (e.g.,
if (customTheme.title)) will treat empty strings as falsy. While empty strings aren't valid colors, using explicit checks like!== undefinedwould make the intent clearer and improve code readability.Example refactor:
- if (customTheme.title) theme.title = '#' + customTheme.title; + if (customTheme.title !== undefined) theme.title = '#' + customTheme.title;
51-59: Consider defensive handling for color values with existing '#' prefix.The code unconditionally prepends '#' to color values. If a user accidentally includes the '#' prefix in their query parameter (e.g.,
title_color=%23ff0), this would result in '##ff0'. While the API layer might strip this, adding a defensive check would improve robustness.Example:
if (customTheme.title) { theme.title = customTheme.title.startsWith('#') ? customTheme.title : '#' + customTheme.title; }
54-57: Review the strokeOpacity hardcoding behavior.Setting
strokeOpacity = 1whenever a custom stroke color is provided will override the base theme's stroke opacity. If the base theme uses a semi-transparent stroke (e.g.,strokeOpacity: 0.5), this behavior would force it to be fully opaque.Consider whether:
- The opacity should be preserved from the base theme when only the color is customized
- A separate
stroke_opacityquery parameter should be supported- The current behavior (forcing opacity to 1) is the desired UX
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
jest.config.json(1 hunks)src/cards/most-commit-language-card.ts(3 hunks)src/cards/productive-time-card.ts(2 hunks)src/cards/profile-details-card.ts(2 hunks)src/cards/repos-per-language-card.ts(2 hunks)src/cards/stats-card.ts(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
src/cards/repos-per-language-card.ts (3)
dist/index.js (4)
username(179-179)exclude(181-181)getRepoLanguageData(562-580)getReposPerLanguageSVG(558-561)src/const/theme.ts (2)
ThemeMap(1-1)Theme(3-28)src/templates/donut-chart-card.ts (1)
createDonutChartCard(6-77)
src/cards/most-commit-language-card.ts (3)
dist/index.js (3)
username(179-179)exclude(181-181)getCommitsLanguageData(254-272)src/const/theme.ts (2)
ThemeMap(1-1)Theme(3-28)src/templates/donut-chart-card.ts (1)
createDonutChartCard(6-77)
src/cards/productive-time-card.ts (2)
dist/index.js (4)
username(179-179)utcOffset(180-180)getProductiveTimeData(341-372)getProductiveTimeSVG(317-320)src/const/theme.ts (2)
ThemeMap(1-1)Theme(3-28)
src/cards/profile-details-card.ts (3)
src/utils/file-writer.ts (1)
writeSVG(14-18)src/const/theme.ts (2)
Theme(3-28)ThemeMap(1-1)src/templates/profile-details-card.ts (1)
createDetailCard(6-155)
src/cards/stats-card.ts (2)
dist/index.js (1)
getStatsSVG(628-632)src/const/theme.ts (2)
Theme(3-28)ThemeMap(1-1)
🔇 Additional comments (4)
src/cards/profile-details-card.ts (4)
1-1: LGTM!The import of the
Themetype is necessary for the new custom theme parameter typing.
18-19: LGTM!Correctly passes
undefinedfor the custom theme during batch generation, maintaining existing behavior for the default card generation workflow.
25-40: LGTM!The function signature extension and pass-through of
customThemeare implemented correctly.
49-49: LGTM!The shallow clone using the spread operator is appropriate since
Themecontains only primitive values (strings and numbers).
| export const getProductiveTimeSVGWithThemeName = async function ( | ||
| username: string, | ||
| themeName: string, | ||
| customTheme: Theme, | ||
| utcOffset: number | ||
| ) { | ||
| if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist'); | ||
| const productiveTimeData = await getProductiveTimeData(username, utcOffset); | ||
| return getProductiveTimeSVG(productiveTimeData, themeName, utcOffset); | ||
| return getProductiveTimeSVG(productiveTimeData, themeName, customTheme, utcOffset); | ||
| }; | ||
|
|
||
| const getProductiveTimeSVG = function ( | ||
| productiveTimeData: Array<number>, | ||
| themeName: string, | ||
| customTheme: Theme | undefined, | ||
| utcOffset: number | ||
| ): string { | ||
| const svgString = productiveTimeCard(productiveTimeData, ThemeMap.get(themeName)!, utcOffset); | ||
| const theme = {...ThemeMap.get(themeName)!}; | ||
| if (customTheme !== undefined) { | ||
| if (customTheme.title) theme.title = '#' + customTheme.title; | ||
| if (customTheme.text) theme.text = '#' + customTheme.text; | ||
| if (customTheme.background) theme.background = '#' + customTheme.background; | ||
| if (customTheme.stroke) { | ||
| theme.stroke = '#' + customTheme.stroke; | ||
| theme.strokeOpacity = 1; | ||
| } | ||
| if (customTheme.icon) theme.icon = '#' + customTheme.icon; | ||
| if (customTheme.chart) theme.chart = '#' + customTheme.chart; | ||
| } | ||
| const svgString = productiveTimeCard(productiveTimeData, theme, utcOffset); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep the API backward-compatible by making customTheme optional
Previously this export only required (username, themeName, utcOffset). Making customTheme mandatory is now a breaking change for existing callers (including our own routes/tests) and contradicts the downstream if (customTheme !== undefined) guard. Please keep the override optional (and type it as a partial) so consumers without overrides keep working.
export const getProductiveTimeSVGWithThemeName = async function (
username: string,
themeName: string,
- customTheme: Theme,
+ customTheme?: Partial<Theme>,
utcOffset: number
) {
@@
-const getProductiveTimeSVG = function (
- productiveTimeData: Array<number>,
- themeName: string,
- customTheme: Theme | undefined,
- utcOffset: number
+const getProductiveTimeSVG = function (
+ productiveTimeData: Array<number>,
+ themeName: string,
+ customTheme?: Partial<Theme>,
+ utcOffset: number
): string {
const theme = {...ThemeMap.get(themeName)!};
- if (customTheme !== undefined) {
+ if (customTheme) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const getProductiveTimeSVGWithThemeName = async function ( | |
| username: string, | |
| themeName: string, | |
| customTheme: Theme, | |
| utcOffset: number | |
| ) { | |
| if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist'); | |
| const productiveTimeData = await getProductiveTimeData(username, utcOffset); | |
| return getProductiveTimeSVG(productiveTimeData, themeName, utcOffset); | |
| return getProductiveTimeSVG(productiveTimeData, themeName, customTheme, utcOffset); | |
| }; | |
| const getProductiveTimeSVG = function ( | |
| productiveTimeData: Array<number>, | |
| themeName: string, | |
| customTheme: Theme | undefined, | |
| utcOffset: number | |
| ): string { | |
| const svgString = productiveTimeCard(productiveTimeData, ThemeMap.get(themeName)!, utcOffset); | |
| const theme = {...ThemeMap.get(themeName)!}; | |
| if (customTheme !== undefined) { | |
| if (customTheme.title) theme.title = '#' + customTheme.title; | |
| if (customTheme.text) theme.text = '#' + customTheme.text; | |
| if (customTheme.background) theme.background = '#' + customTheme.background; | |
| if (customTheme.stroke) { | |
| theme.stroke = '#' + customTheme.stroke; | |
| theme.strokeOpacity = 1; | |
| } | |
| if (customTheme.icon) theme.icon = '#' + customTheme.icon; | |
| if (customTheme.chart) theme.chart = '#' + customTheme.chart; | |
| } | |
| const svgString = productiveTimeCard(productiveTimeData, theme, utcOffset); | |
| export const getProductiveTimeSVGWithThemeName = async function ( | |
| username: string, | |
| themeName: string, | |
| customTheme?: Partial<Theme>, | |
| utcOffset: number | |
| ) { | |
| if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist'); | |
| const productiveTimeData = await getProductiveTimeData(username, utcOffset); | |
| return getProductiveTimeSVG(productiveTimeData, themeName, customTheme, utcOffset); | |
| }; | |
| const getProductiveTimeSVG = function ( | |
| productiveTimeData: Array<number>, | |
| themeName: string, | |
| customTheme?: Partial<Theme>, | |
| utcOffset: number | |
| ): string { | |
| const theme = {...ThemeMap.get(themeName)!}; | |
| if (customTheme) { | |
| if (customTheme.title) theme.title = '#' + customTheme.title; | |
| if (customTheme.text) theme.text = '#' + customTheme.text; | |
| if (customTheme.background) theme.background = '#' + customTheme.background; | |
| if (customTheme.stroke) { | |
| theme.stroke = '#' + customTheme.stroke; | |
| theme.strokeOpacity = 1; | |
| } | |
| if (customTheme.icon) theme.icon = '#' + customTheme.icon; | |
| if (customTheme.chart) theme.chart = '#' + customTheme.chart; | |
| } | |
| const svgString = productiveTimeCard(productiveTimeData, theme, utcOffset); |
🤖 Prompt for AI Agents
In src/cards/productive-time-card.ts around lines 15 to 44, the top-level export
made customTheme mandatory which breaks backward compatibility; change the
parameter to be optional and a Partial<Theme> so existing callers that omit
overrides still work, and update the internal getProductiveTimeSVG signature to
accept customTheme?: Partial<Theme>; keep the existing undefined checks and
property concatenation logic as-is, and adjust any local typing references to
treat customTheme properties as possibly undefined.
| if (customTheme !== undefined) { | ||
| if (customTheme.title) theme.title = '#' + customTheme.title; | ||
| if (customTheme.text) theme.text = '#' + customTheme.text; | ||
| if (customTheme.background) theme.background = '#' + customTheme.background; | ||
| if (customTheme.stroke) { | ||
| theme.stroke = '#' + customTheme.stroke; | ||
| theme.strokeOpacity = 1; | ||
| } | ||
| if (customTheme.icon) theme.icon = '#' + customTheme.icon; | ||
| if (customTheme.chart) theme.chart = '#' + customTheme.chart; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify color format validation at the API layer.
The code doesn't validate whether custom color values are valid hexadecimal colors. Invalid values will be prepended with '#' and passed through, potentially causing rendering issues or unexpected visual output.
Ensure that the API layer (where customTheme is constructed from query parameters) validates the color format before creating the Theme object.
Run the following script to check if color validation exists in the API layer:
🏁 Script executed:
#!/bin/bash
# Description: Search for color validation logic in API route handlers
# Look for query parameter parsing and Theme construction
rg -n -C 5 'title_color|text_color|bg_color|border_color|icon_color|chart_color'
# Look for hex color validation patterns
rg -n -C 3 '[0-9a-fA-F]{6}|[0-9a-fA-F]{8}|hex.*valid'Length of output: 294646
API color validation is missing at the route handler layer—review concern is confirmed.
The script output confirms the review comment's observation. The API route handlers in api/cards/ accept color parameters (title_color, text_color, bg_color, border_color, icon_color, chart_color) with only type validation (string check) but no format validation (hexadecimal check).
Key findings:
- All five API handlers perform
typeof !== 'string'checks but do not validate that these strings are valid hex colors. - No regex patterns matching hexadecimal format (
[0-9a-fA-F]{6}or similar) exist in any API route handler. - The code in
src/cards/profile-details-card.tslines 50–60 confirms colors are prepended with'#'unconditionally:theme.title = '#' + customTheme.title(and similar for other colors). - Hex validation patterns exist only in theme definitions (
src/const/theme.ts) and tests, not in the request validation layer.
Why this matters:
Invalid color strings will be passed downstream as malformed CSS/SVG attributes (e.g., fill="#invalid_color"), which can cause rendering failures or injection vulnerabilities if not sanitized.
🤖 Prompt for AI Agents
In src/cards/profile-details-card.ts around lines 50-60, the code
unconditionally prepends '#' to incoming color strings and relies on upstream
handlers for validity; update the API route handlers in api/cards/ to validate
and sanitize color parameters using a hex regex (accept 3- or 6-digit hex,
case-insensitive), strip any leading '#', normalize (e.g., to lowercase), and
return a 400 error for invalid colors instead of allowing them through; then
make profile-details-card.ts prepend '#' only to already-validated/sanitized
values (or check and add if missing) to avoid malformed values or double-#.
| export const getReposPerLanguageSVGWithThemeName = async function ( | ||
| username: string, | ||
| themeName: string, | ||
| customTheme: Theme, | ||
| exclude: Array<string> | ||
| ) { | ||
| if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist'); | ||
| const langData = await getRepoLanguageData(username, exclude); | ||
| return getReposPerLanguageSVG(langData, themeName); | ||
| return getReposPerLanguageSVG(langData, themeName, customTheme); | ||
| }; | ||
|
|
||
| const getReposPerLanguageSVG = function (langData: {name: string; value: number; color: string}[], themeName: string) { | ||
| const svgString = createDonutChartCard('Top Languages by Repo', langData, ThemeMap.get(themeName)!); | ||
| const getReposPerLanguageSVG = function ( | ||
| langData: {name: string; value: number; color: string}[], | ||
| themeName: string, | ||
| customTheme: Theme | undefined | ||
| ) { | ||
| const theme = {...ThemeMap.get(themeName)!}; | ||
| if (customTheme !== undefined) { | ||
| if (customTheme.title) theme.title = '#' + customTheme.title; | ||
| if (customTheme.text) theme.text = '#' + customTheme.text; | ||
| if (customTheme.background) theme.background = '#' + customTheme.background; | ||
| if (customTheme.stroke) { | ||
| theme.stroke = '#' + customTheme.stroke; | ||
| theme.strokeOpacity = 1; | ||
| } | ||
| if (customTheme.icon) theme.icon = '#' + customTheme.icon; | ||
| if (customTheme.chart) theme.chart = '#' + customTheme.chart; | ||
| } | ||
| const svgString = createDonutChartCard('Top Languages by Repo', langData, theme); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve previous signature by making customTheme optional
This export also used to accept only (username, themeName, exclude). Requiring a full Theme object now breaks any caller that doesn’t supply overrides. Please make the parameter optional (and partial) to retain the prior contract.
export const getReposPerLanguageSVGWithThemeName = async function (
username: string,
themeName: string,
- customTheme: Theme,
+ customTheme?: Partial<Theme>,
exclude: Array<string>
) {
@@
-const getReposPerLanguageSVG = function (
- langData: {name: string; value: number; color: string}[],
- themeName: string,
- customTheme: Theme | undefined
+const getReposPerLanguageSVG = function (
+ langData: {name: string; value: number; color: string}[],
+ themeName: string,
+ customTheme?: Partial<Theme>
) {
const theme = {...ThemeMap.get(themeName)!};
- if (customTheme !== undefined) {
+ if (customTheme) {🤖 Prompt for AI Agents
In src/cards/repos-per-language-card.ts around lines 15 to 43, the exported
function now requires a full Theme which breaks callers that relied on the
previous (username, themeName, exclude) signature; change the customTheme
parameter to be optional and allow partial overrides by using Partial<Theme>
(e.g., customTheme?: Partial<Theme>) for both
getReposPerLanguageSVGWithThemeName and the internal getReposPerLanguageSVG,
leaving the existing property checks as-is so callers can pass nothing or only
the specific theme fields they want to override.
| export const getStatsSVGWithThemeName = async function (username: string, themeName: string, customTheme: Theme) { | ||
| if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist'); | ||
| const statsData = await getStatsData(username); | ||
| return getStatsSVG(statsData, themeName); | ||
| return getStatsSVG(statsData, themeName, customTheme); | ||
| }; | ||
|
|
||
| const getStatsSVG = function ( | ||
| StatsData: {index: number; icon: string; name: string; value: string}[], | ||
| themeName: string | ||
| themeName: string, | ||
| customTheme: Theme | undefined | ||
| ) { | ||
| const title = 'Stats'; | ||
| const svgString = statsCard(`${title}`, StatsData, ThemeMap.get(themeName)!); | ||
| const theme = {...ThemeMap.get(themeName)!}; | ||
| if (customTheme !== undefined) { | ||
| if (customTheme.title) theme.title = '#' + customTheme.title; | ||
| if (customTheme.text) theme.text = '#' + customTheme.text; | ||
| if (customTheme.background) theme.background = '#' + customTheme.background; | ||
| if (customTheme.stroke) { | ||
| theme.stroke = '#' + customTheme.stroke; | ||
| theme.strokeOpacity = 1; | ||
| } | ||
| if (customTheme.icon) theme.icon = '#' + customTheme.icon; | ||
| if (customTheme.chart) theme.chart = '#' + customTheme.chart; | ||
| } | ||
| const svgString = statsCard(`${title}`, StatsData, theme); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Retain the old API contract; customTheme should be optional
getStatsSVGWithThemeName used to take three arguments. Forcing callers to always pass a full Theme now breaks existing usage and contradicts the downstream undefined guard. Please leave the override optional (and partial).
-export const getStatsSVGWithThemeName = async function (username: string, themeName: string, customTheme: Theme) {
+export const getStatsSVGWithThemeName = async function (
+ username: string,
+ themeName: string,
+ customTheme?: Partial<Theme>
+) {
@@
-const getStatsSVG = function (
- StatsData: {index: number; icon: string; name: string; value: string}[],
- themeName: string,
- customTheme: Theme | undefined
+const getStatsSVG = function (
+ StatsData: {index: number; icon: string; name: string; value: string}[],
+ themeName: string,
+ customTheme?: Partial<Theme>
) {
@@
- const theme = {...ThemeMap.get(themeName)!};
- if (customTheme !== undefined) {
+ const theme = {...ThemeMap.get(themeName)!};
+ if (customTheme) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const getStatsSVGWithThemeName = async function (username: string, themeName: string, customTheme: Theme) { | |
| if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist'); | |
| const statsData = await getStatsData(username); | |
| return getStatsSVG(statsData, themeName); | |
| return getStatsSVG(statsData, themeName, customTheme); | |
| }; | |
| const getStatsSVG = function ( | |
| StatsData: {index: number; icon: string; name: string; value: string}[], | |
| themeName: string | |
| themeName: string, | |
| customTheme: Theme | undefined | |
| ) { | |
| const title = 'Stats'; | |
| const svgString = statsCard(`${title}`, StatsData, ThemeMap.get(themeName)!); | |
| const theme = {...ThemeMap.get(themeName)!}; | |
| if (customTheme !== undefined) { | |
| if (customTheme.title) theme.title = '#' + customTheme.title; | |
| if (customTheme.text) theme.text = '#' + customTheme.text; | |
| if (customTheme.background) theme.background = '#' + customTheme.background; | |
| if (customTheme.stroke) { | |
| theme.stroke = '#' + customTheme.stroke; | |
| theme.strokeOpacity = 1; | |
| } | |
| if (customTheme.icon) theme.icon = '#' + customTheme.icon; | |
| if (customTheme.chart) theme.chart = '#' + customTheme.chart; | |
| } | |
| const svgString = statsCard(`${title}`, StatsData, theme); | |
| export const getStatsSVGWithThemeName = async function ( | |
| username: string, | |
| themeName: string, | |
| customTheme?: Partial<Theme> | |
| ) { | |
| if (!ThemeMap.has(themeName)) throw new Error('Theme does not exist'); | |
| const statsData = await getStatsData(username); | |
| return getStatsSVG(statsData, themeName, customTheme); | |
| }; | |
| const getStatsSVG = function ( | |
| StatsData: {index: number; icon: string; name: string; value: string}[], | |
| themeName: string, | |
| customTheme?: Partial<Theme> | |
| ) { | |
| const title = 'Stats'; | |
| const theme = {...ThemeMap.get(themeName)!}; | |
| if (customTheme) { | |
| if (customTheme.title) theme.title = '#' + customTheme.title; | |
| if (customTheme.text) theme.text = '#' + customTheme.text; | |
| if (customTheme.background) theme.background = '#' + customTheme.background; | |
| if (customTheme.stroke) { | |
| theme.stroke = '#' + customTheme.stroke; | |
| theme.strokeOpacity = 1; | |
| } | |
| if (customTheme.icon) theme.icon = '#' + customTheme.icon; | |
| if (customTheme.chart) theme.chart = '#' + customTheme.chart; | |
| } | |
| const svgString = statsCard(`${title}`, StatsData, theme); |
Add custom theme support to the cards.
Here are the new query parameters that can be used to customize the theme of the cards:
title_color,text_color,bg_color,border_color,icon_color,chart_colorThe custom color will override the color of the selected theme.
For example, use
theme=dark&bg_color=00000000as dark theme with a transparent background.Resolve:
#110 - Custom theme
#152 - Custom background color
Example:
https://github-profile-summary-cards.vercel.app/api/cards/productive-time?username=vn7n24fzkq&title_color=ff0&text_color=0ff&bg_color=665544&border_color=f0f&icon_color=1234ff&chart_color=ff4321a0
Summary by CodeRabbit
New Features
Chores