Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions apps/frontend/src/content/docs/docs/customization/common-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ title: Common Options

Every card accepts the options below, on top of the exclusive options listed on its own page.

| Name | Description | Type | Default value |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ------------- |
| `title_color`<sup>1</sup> | Card's title color. | string (hex color) | `2f80ed` |
| `text_color`<sup>1</sup> | Body text color. | string (hex color) | `434d58` |
| `icon_color`<sup>1</sup> | Icons color if available. | string (hex color) | `4c71f2` |
| `border_color`<sup>1</sup> | Card's border color. Does not apply when `hide_border` is enabled. | string (hex color) | `e4e2e2` |
| `bg_color`<sup>1</sup> | Card's background color. | string (hex color or a gradient in the form of _angle,start,end_) | `fffefe` |
| `hide_border` | Hides the card's border. | boolean | `false` |
| `theme`<sup>1</sup> | Name of the theme, choose from [all available themes](/frontend/docs/customization/themes/). | enum | `default` |
| `cache_seconds` | Sets the cache header manually (min: 21600, max: 86400). | integer | `21600` |
| `locale` | Sets the language in the card, you can check full list of available locales [here](/frontend/docs/customization/locales/). | enum | `en` |
| `border_radius` | Corner rounding on the card. | number | `4.5` |
| Name | Description | Type | Default value |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ------------- |
| `title_color`<sup>1</sup> | Card's title color. | string (hex color) | `2f80ed` |
| `text_color`<sup>1</sup> | Body text color. | string (hex color) | `434d58` |
| `icon_color`<sup>1</sup> | Icons color if available. | string (hex color) | `4c71f2` |
| `border_color`<sup>1</sup> | Card's border color. Does not apply when `hide_border` is enabled. | string (hex color) | `e4e2e2` |
| `bg_color`<sup>1</sup> | Card's background color. | string (hex color or a gradient in the form of _angle,start,end_) | `fffefe` |
| `hide_border` | Hides the card's border. | boolean | `false` |
| `theme`<sup>1</sup> | Name of the theme, choose from [all available themes](/frontend/docs/customization/themes/). | enum | `default` |
| `cache_seconds` | Sets the cache header manually (min: 21600, max: 86400). | integer | `21600` |
| `locale` | Sets the language in the card, you can check full list of available locales [here](/frontend/docs/customization/locales/). The [gist card](/frontend/docs/cards/gist-pin/) has no translated text, so it ignores this option. | enum | `en` |
| `border_radius` | Corner rounding on the card. | number | `4.5` |

<sup>1</sup>: Supports light and dark mode via `*_light` / `*_dark` variants (e.g. `title_color_light`). See [Light & Dark Mode Parameters](/frontend/docs/customization/theming/#light--dark-mode-parameters) for details.

Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/api/api-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** What every api handler returns: a rendered card, or a rendered error. */
export interface ApiResult {
status: "success" | "error - permanent" | "error - temporary";
content: string;
}
48 changes: 36 additions & 12 deletions packages/core/src/api/gist.js → packages/core/src/api/gist.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { renderGistCard } from "../cards/gist.js";
import type { ColorParams } from "../common/color.js";
import { findInvalidColorParam, pickColorParams } from "../common/color.js";
import {
MissingParamError,
Expand All @@ -7,21 +8,44 @@ import {
import { parseBoolean } from "../common/ops.js";
import { renderError } from "../common/render.js";
import { fetchGist } from "../fetchers/gist.js";
import { isLocaleAvailable } from "../translations.js";

// @ts-ignore
import type { ApiResult } from "./api-result.js";

/** Query params the gist endpoint accepts, on top of the shared color params. */
interface GistApiQuery extends ColorParams {
id?: string;
border_radius?: string;
show_owner?: string;
browser_rendering?: string;
hide_border?: string;
}

/** Characters a gist ID may contain. */
const SAFE_PATTERN = /^[-\w/.,]+$/;

/**
* Render the gist card for a set of query params.
*
* @param query Raw query params.
* @param query.id GitHub gist ID.
* @param query.border_radius Card border radius.
* @param query.show_owner Whether to show the gist owner.
* @param query.browser_rendering Whether the browser wraps the description text.
* @param query.hide_border Whether to hide the card border.
* @param pat Optional PAT override.
* @returns The rendered card, or a rendered error.
*/
export default async (
{
id,
locale,
border_radius,
show_owner,
browser_rendering,
hide_border,
...remainingParams
},
pat = null,
) => {
}: GistApiQuery,
pat: string | null = null,
): Promise<ApiResult> => {
const colorParams = pickColorParams(remainingParams);

const invalidColorInput = findInvalidColorParam(colorParams);
Expand All @@ -35,19 +59,20 @@ export default async (
};
}

if (locale && !isLocaleAvailable(locale)) {
const borderRadius =
border_radius === undefined ? undefined : parseFloat(border_radius);
if (borderRadius !== undefined && !Number.isFinite(borderRadius)) {
return {
status: "error - permanent",
content: renderError({
message: "Something went wrong",
secondaryMessage: "Language not found",
secondaryMessage: 'Invalid number input for parameter "border_radius"',
renderOptions: colorParams,
}),
};
}

const safePattern = /^[-\w/.,]+$/;
if (id && !safePattern.test(id)) {
if (id && !SAFE_PATTERN.test(id)) {
return {
status: "error - permanent",
content: renderError({
Expand All @@ -65,8 +90,7 @@ export default async (
status: "success",
content: renderGistCard(gistData, {
...colorParams,
border_radius,
locale: locale ? locale.toLowerCase() : null,
border_radius: borderRadius,
show_owner: parseBoolean(show_owner),
browser_rendering: parseBoolean(browser_rendering),
hide_border: parseBoolean(hide_border),
Expand Down
13 changes: 0 additions & 13 deletions packages/core/src/cards/common-options.ts

This file was deleted.

6 changes: 3 additions & 3 deletions packages/core/src/cards/gist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from "../common/render.js";
import type { GistData } from "../fetchers/types.js";

import type { CommonOptions } from "./common-options.js";
import type { CardOptions, CommonCardOptions } from "./options.js";

const ICON_SIZE = 16;
const CARD_DEFAULT_WIDTH = 400;
Expand All @@ -27,7 +27,7 @@ const DESCRIPTION_FONT_SIZE = 13;
const DESCRIPTION_LINE_HEIGHT_PX = 16;
const DESCRIPTION_MAX_LINES = 10;

interface GistCardOptions extends CommonOptions {
interface GistCardOptions extends CommonCardOptions {
show_owner: boolean;
browser_rendering: boolean;
}
Expand All @@ -41,7 +41,7 @@ interface GistCardOptions extends CommonOptions {
*/
const renderGistCard = (
gistData: GistData,
options: Partial<GistCardOptions> = {},
options: CardOptions<GistCardOptions> = {},
): string => {
const { name, nameWithOwner, description, language, starsCount, forksCount } =
gistData;
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/cards/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { ColorParams } from "../common/color.js";

/**
* Options every card accepts.
* Cards spread these into `getLightDarkColors`, hence {@link ColorParams}.
*/
export interface CommonCardOptions extends ColorParams {
title_color: string;
icon_color: string;
text_color: string;
bg_color: string;
border_radius: number;
border_color: string;
hide_border: boolean;
}

/** `Partial<T>` that also accepts an explicit `undefined`, as api handlers forward. */
export type CardOptions<T> = { [K in keyof T]?: T[K] | undefined };
5 changes: 3 additions & 2 deletions packages/core/src/cards/repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import type { RepositoryData } from "../fetchers/types.js";
import { repoCardLocales } from "../translations.js";

import type { CommonOptions } from "./common-options.js";
import type { CommonCardOptions } from "./options.js";

const ICON_SIZE = 16;
const CARD_DEFAULT_WIDTH = 400;
Expand All @@ -27,7 +27,8 @@ const DESCRIPTION_FONT_SIZE = 13;
const DESCRIPTION_LINE_HEIGHT_PX = 16;
const DESCRIPTION_MAX_LINES = 3;

interface RepoCardOptions extends CommonOptions {
interface RepoCardOptions extends CommonCardOptions {
locale: string;
show_owner: boolean;
browser_rendering: boolean;
description_lines_count: number;
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/cards/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { createTextNode, flexLayout, measureText } from "../common/render.js";
import type { StatsData } from "../fetchers/types.js";
import { statCardLocales, wakatimeCardLocales } from "../translations.js";

import type { CommonOptions } from "./common-options.js";
import type { CommonCardOptions } from "./options.js";

const CARD_MIN_WIDTH = 287;
const CARD_DEFAULT_WIDTH = 287;
Expand All @@ -19,7 +19,8 @@ const RANK_ONLY_CARD_DEFAULT_WIDTH = 290;

type RankIcon = "default" | "github" | "percentile";

interface StatCardOptions extends CommonOptions {
interface StatCardOptions extends CommonCardOptions {
locale: string;
hide: Array<string>;
show_icons: boolean;
hide_title: boolean;
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/cards/top-languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import type { Lang, TopLangData } from "../fetchers/types.js";
import { langCardLocales } from "../translations.js";

import type { CommonOptions } from "./common-options.js";
import type { CommonCardOptions } from "./options.js";

const DEFAULT_CARD_WIDTH = 300;
const MIN_CARD_WIDTH = 280;
Expand All @@ -29,7 +29,8 @@ const DONUT_VERTICAL_LAYOUT_DEFAULT_LANGS_COUNT = 6;

type TopLangLayout = "compact" | "normal" | "donut" | "donut-vertical" | "pie";

interface TopLangOptions extends CommonOptions {
interface TopLangOptions extends CommonCardOptions {
locale: string;
hide_title: boolean;
card_width: number;
hide: Array<string>;
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/cards/wakatime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { createProgressNode, flexLayout } from "../common/render.js";
import type { WakaTimeData, WakaTimeLang } from "../fetchers/types.js";
import { wakatimeCardLocales } from "../translations.js";

import type { CommonOptions } from "./common-options.js";
import type { CommonCardOptions } from "./options.js";

const DEFAULT_CARD_WIDTH = 495;
const MIN_CARD_WIDTH = 250;
Expand All @@ -22,7 +22,8 @@ const TOTAL_TEXT_WIDTH = 275;
type WakaTimeLayout = "compact" | "normal";
type DisplayFormat = "time" | "percent";

interface WakaTimeOptions extends CommonOptions {
interface WakaTimeOptions extends CommonCardOptions {
locale: string;
hide_title: boolean;
hide: Array<string>;
card_width: number;
Expand Down
11 changes: 4 additions & 7 deletions packages/core/src/common/color.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { themes } from "../themes/index.js";
import type { ThemeName } from "../themes/index.js";
import { isThemeName, themes } from "../themes/index.js";

/** Matches a 3-, 4-, 6-, or 8-digit hex color with no leading `#`. */
const HEX_COLOR =
Expand Down Expand Up @@ -172,11 +171,7 @@ const getCardColors = ({
theme,
}: ColorInput): CardColors => {
const defaultTheme = themes.default;
const isThemeProvided = theme !== undefined && theme in themes;

const selectedTheme = isThemeProvided
? themes[theme as ThemeName]
: defaultTheme;
const selectedTheme = isThemeName(theme) ? themes[theme] : defaultTheme;

const defaultBorderColor =
"border_color" in selectedTheme
Expand Down Expand Up @@ -343,6 +338,8 @@ const findInvalidColorParam = (params: ColorParams): string | null =>
),
);

export type { ColorParams };

export {
getCardColors,
getLightDarkColors,
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/common/ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import { CustomError } from "./error.js";
/**
* Returns boolean if value is either "true" or "false" else the value as it is.
*
* @param value The value to parse.
* @param value The value to parse; `undefined` when the param was not sent.
* @returns The parsed value.
*/
const parseBoolean = (value: string | boolean): boolean | undefined => {
const parseBoolean = (
value: string | boolean | undefined,
): boolean | undefined => {
if (typeof value === "boolean") {
return value;
}
Expand Down
15 changes: 8 additions & 7 deletions packages/core/src/common/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,14 +394,15 @@ const renderError = ({
renderOptions = {},
}: {
message: string;
secondaryMessage?: string;
secondaryMessage?: string | undefined;
// `| undefined`: api handlers forward absent query params
renderOptions?: {
title_color?: string;
text_color?: string;
bg_color?: string;
border_color?: string;
theme?: string;
show_repo_link?: boolean;
title_color?: string | undefined;
text_color?: string | undefined;
bg_color?: string | undefined;
border_color?: string | undefined;
theme?: string | undefined;
show_repo_link?: boolean | undefined;
};
}): string => {
const {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/fetchers/gist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const calculatePrimaryLanguage = (
* @returns Gist data.
*/
const fetchGist = async (
id: string,
id: string | undefined,
pat: string | null = null,
): Promise<GistData> => {
if (!id) {
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/themes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,3 +507,13 @@ export const themes = {
* Name of one of the {@link themes}.
*/
export type ThemeName = keyof typeof themes;

/**
* Checks whether a value is one of the {@link themes}
*/
export const isThemeName = (value: unknown): value is ThemeName => {
if (typeof value !== "string") {
return false;
}
return value in themes;
};
1 change: 0 additions & 1 deletion packages/core/tests/ops.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ describe("Test ops.js", () => {
expect(parseBoolean("1")).toBe(undefined);
expect(parseBoolean("0")).toBe(undefined);
expect(parseBoolean("")).toBe(undefined);
// @ts-expect-error testing invalid input
expect(parseBoolean(undefined)).toBe(undefined);
});

Expand Down
Loading