@stats-organization/github-readme-stats-core 2.1.4 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/api/gist.d.ts +2 -7
- package/build/api/gist.d.ts.map +1 -1
- package/build/api/gist.js +28 -26
- package/build/api/index.d.ts +3 -8
- package/build/api/index.d.ts.map +1 -1
- package/build/api/index.js +32 -36
- package/build/api/pin.d.ts +2 -7
- package/build/api/pin.d.ts.map +1 -1
- package/build/api/pin.js +18 -33
- package/build/api/top-langs.d.ts +2 -7
- package/build/api/top-langs.d.ts.map +1 -1
- package/build/api/top-langs.js +32 -44
- package/build/api/wakatime.d.ts +2 -7
- package/build/api/wakatime.d.ts.map +1 -1
- package/build/api/wakatime.js +28 -26
- package/build/calculateRank.d.ts +12 -11
- package/build/calculateRank.d.ts.map +1 -1
- package/build/calculateRank.js +18 -18
- package/build/cards/common-options.d.ts +13 -0
- package/build/cards/common-options.d.ts.map +1 -0
- package/build/cards/common-options.js +1 -0
- package/build/cards/gist.d.ts +11 -16
- package/build/cards/gist.d.ts.map +1 -1
- package/build/cards/gist.js +26 -31
- package/build/cards/repo.d.ts +19 -16
- package/build/cards/repo.d.ts.map +1 -1
- package/build/cards/repo.js +79 -72
- package/build/cards/stats.d.ts +29 -44
- package/build/cards/stats.d.ts.map +1 -1
- package/build/cards/stats.js +118 -173
- package/build/cards/top-languages.d.ts +81 -77
- package/build/cards/top-languages.d.ts.map +1 -1
- package/build/cards/top-languages.js +186 -184
- package/build/cards/wakatime.d.ts +21 -11
- package/build/cards/wakatime.d.ts.map +1 -1
- package/build/cards/wakatime.js +92 -109
- package/build/common/Card.d.ts +41 -14
- package/build/common/Card.d.ts.map +1 -1
- package/build/common/Card.js +92 -31
- package/build/common/I18n.d.ts +4 -2
- package/build/common/I18n.d.ts.map +1 -1
- package/build/common/color.d.ts +91 -15
- package/build/common/color.d.ts.map +1 -1
- package/build/common/color.js +163 -17
- package/build/common/config.d.ts +2 -1
- package/build/common/config.d.ts.map +1 -1
- package/build/common/config.js +16 -26
- package/build/common/constants.d.ts +2 -1
- package/build/common/constants.d.ts.map +1 -1
- package/build/common/date.d.ts +37 -0
- package/build/common/date.d.ts.map +1 -0
- package/build/common/date.js +31 -0
- package/build/common/html.js +1 -1
- package/build/common/http.d.ts +37 -5
- package/build/common/http.d.ts.map +1 -1
- package/build/common/http.js +29 -6
- package/build/common/icons.d.ts +1 -0
- package/build/common/icons.d.ts.map +1 -1
- package/build/common/icons.js +1 -0
- package/build/common/languageColors.d.ts +11 -0
- package/build/common/languageColors.d.ts.map +1 -0
- package/build/common/languageColors.js +15 -0
- package/build/common/languageColors.json +10 -1
- package/build/common/ops.d.ts +27 -33
- package/build/common/ops.d.ts.map +1 -1
- package/build/common/ops.js +31 -48
- package/build/common/render.d.ts +154 -113
- package/build/common/render.d.ts.map +1 -1
- package/build/common/render.js +209 -92
- package/build/common/retryer.d.ts +15 -6
- package/build/common/retryer.d.ts.map +1 -1
- package/build/common/retryer.js +2 -0
- package/build/fetchers/gist.d.ts +6 -21
- package/build/fetchers/gist.d.ts.map +1 -1
- package/build/fetchers/gist.js +28 -64
- package/build/fetchers/repo.d.ts +12 -11
- package/build/fetchers/repo.d.ts.map +1 -1
- package/build/fetchers/repo.js +27 -68
- package/build/fetchers/stats.d.ts +24 -17
- package/build/fetchers/stats.d.ts.map +1 -1
- package/build/fetchers/stats.js +302 -184
- package/build/fetchers/top-languages.d.ts +10 -15
- package/build/fetchers/top-languages.d.ts.map +1 -1
- package/build/fetchers/top-languages.js +52 -103
- package/build/fetchers/types.d.ts +130 -0
- package/build/fetchers/types.d.ts.map +1 -0
- package/build/fetchers/types.js +1 -0
- package/build/fetchers/wakatime.d.ts +9 -5
- package/build/fetchers/wakatime.d.ts.map +1 -1
- package/build/fetchers/wakatime.js +8 -4
- package/build/graphql/contributionsDocument.d.ts +18 -0
- package/build/graphql/contributionsDocument.d.ts.map +1 -0
- package/build/graphql/contributionsDocument.js +30 -0
- package/build/graphql/generated/common.d.ts +42 -0
- package/build/graphql/generated/common.d.ts.map +1 -0
- package/build/graphql/generated/common.js +2 -0
- package/build/graphql/generated/gist.d.ts +44 -0
- package/build/graphql/generated/gist.d.ts.map +1 -0
- package/build/graphql/generated/gist.js +27 -0
- package/build/graphql/generated/repo.d.ts +67 -0
- package/build/graphql/generated/repo.d.ts.map +1 -0
- package/build/graphql/generated/repo.js +30 -0
- package/build/graphql/generated/stats.d.ts +150 -0
- package/build/graphql/generated/stats.d.ts.map +1 -0
- package/build/graphql/generated/stats.js +95 -0
- package/build/graphql/generated/top-languages.d.ts +54 -0
- package/build/graphql/generated/top-languages.d.ts.map +1 -0
- package/build/graphql/generated/top-languages.js +27 -0
- package/build/graphql/graphqlDocument.d.ts +22 -0
- package/build/graphql/graphqlDocument.d.ts.map +1 -0
- package/build/graphql/graphqlDocument.js +8 -0
- package/build/graphql/reposContributedToDocument.d.ts +27 -0
- package/build/graphql/reposContributedToDocument.d.ts.map +1 -0
- package/build/graphql/reposContributedToDocument.js +59 -0
- package/build/index.d.ts +3 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -1
- package/build/themes/index.d.ts +32 -0
- package/build/themes/index.d.ts.map +1 -1
- package/build/themes/index.js +28 -0
- package/build/translations.d.ts +17 -0
- package/build/translations.d.ts.map +1 -1
- package/build/translations.js +17 -0
- package/package.json +14 -8
- package/src/_emoji-name-map.d.ts +10 -0
- package/src/_github-username-regex.d.ts +4 -0
- package/src/api/gist.js +31 -31
- package/src/api/index.js +40 -42
- package/src/api/pin.js +20 -38
- package/src/api/top-langs.js +36 -49
- package/src/api/wakatime.js +31 -31
- package/src/{calculateRank.js → calculateRank.ts} +29 -19
- package/src/cards/common-options.ts +13 -0
- package/src/cards/{gist.js → gist.ts} +37 -37
- package/src/cards/{repo.js → repo.ts} +107 -79
- package/src/cards/{stats.js → stats.ts} +173 -210
- package/src/cards/{top-languages.js → top-languages.ts} +325 -243
- package/src/cards/{wakatime.js → wakatime.ts} +158 -127
- package/src/common/Card.ts +123 -43
- package/src/common/I18n.ts +6 -2
- package/src/common/color.ts +240 -28
- package/src/common/config.ts +22 -29
- package/src/common/constants.ts +3 -1
- package/src/common/date.ts +45 -0
- package/src/common/html.ts +1 -1
- package/src/common/http.ts +71 -0
- package/src/common/icons.ts +1 -0
- package/src/common/languageColors.json +10 -1
- package/src/common/languageColors.ts +19 -0
- package/src/common/ops.ts +158 -0
- package/src/common/render.ts +652 -0
- package/src/common/retryer.ts +21 -12
- package/src/fetchers/gist.ts +79 -0
- package/src/fetchers/repo.ts +114 -0
- package/src/fetchers/stats.ts +743 -0
- package/src/fetchers/top-languages.ts +135 -0
- package/src/fetchers/{types.d.ts → types.ts} +28 -20
- package/src/fetchers/{wakatime.js → wakatime.ts} +19 -5
- package/src/graphql/contributionsDocument.ts +44 -0
- package/src/graphql/generated/common.ts +26 -0
- package/src/graphql/generated/gist.ts +59 -0
- package/src/graphql/generated/repo.ts +90 -0
- package/src/graphql/generated/stats.ts +194 -0
- package/src/graphql/generated/top-languages.ts +75 -0
- package/src/graphql/graphqlDocument.ts +25 -0
- package/src/graphql/queries/gist.graphql +25 -0
- package/src/graphql/queries/repo.graphql +28 -0
- package/src/graphql/queries/stats.graphql +120 -0
- package/src/graphql/queries/top-languages.graphql +34 -0
- package/src/graphql/reposContributedToDocument.ts +86 -0
- package/src/index.ts +3 -1
- package/src/themes/index.ts +33 -0
- package/src/translations.ts +17 -0
- package/src/cards/types.d.ts +0 -78
- package/src/common/http.js +0 -19
- package/src/common/ops.js +0 -170
- package/src/common/render.js +0 -440
- package/src/fetchers/gist.js +0 -112
- package/src/fetchers/repo.js +0 -161
- package/src/fetchers/stats.js +0 -474
- package/src/fetchers/top-languages.js +0 -175
- package/src/themes/README.md +0 -229
package/src/common/Card.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
+
import { getCardColors, isPrefixedHexColor, isValidGradient } from "./color.js";
|
|
1
2
|
import { encodeHTML } from "./html.js";
|
|
2
3
|
import { flexLayout } from "./render.js";
|
|
3
4
|
|
|
4
5
|
interface CardColors {
|
|
5
6
|
/** Card title color. */
|
|
6
|
-
titleColor
|
|
7
|
+
titleColor: string;
|
|
7
8
|
/** Card text color. */
|
|
8
|
-
textColor
|
|
9
|
+
textColor: string;
|
|
9
10
|
/** Card icon color. */
|
|
10
|
-
iconColor
|
|
11
|
+
iconColor: string;
|
|
11
12
|
/** Card background color. */
|
|
12
|
-
bgColor
|
|
13
|
+
bgColor: string | Array<string>;
|
|
13
14
|
/** Card border color. */
|
|
14
|
-
borderColor
|
|
15
|
+
borderColor: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
class Card {
|
|
@@ -20,9 +21,10 @@ class Card {
|
|
|
20
21
|
hideBorder: boolean;
|
|
21
22
|
hideTitle: boolean;
|
|
22
23
|
border_radius: number;
|
|
23
|
-
colors: CardColors;
|
|
24
|
+
colors: { light: CardColors; dark: CardColors | null };
|
|
24
25
|
title: string;
|
|
25
26
|
css: string;
|
|
27
|
+
darkCss: string;
|
|
26
28
|
paddingX: number;
|
|
27
29
|
paddingY: number;
|
|
28
30
|
titlePrefixIcon: string | undefined;
|
|
@@ -33,29 +35,34 @@ class Card {
|
|
|
33
35
|
/**
|
|
34
36
|
* Creates a new card instance.
|
|
35
37
|
*
|
|
38
|
+
* The caller must ensure that the passed `titlePrefixIcon` is properly sanitized!
|
|
39
|
+
*
|
|
36
40
|
* @param props Card arguments.
|
|
37
41
|
* @param props.width Card width.
|
|
38
42
|
* @param props.height Card height.
|
|
39
43
|
* @param props.border_radius Card border radius.
|
|
40
|
-
* @param props.colors Card colors
|
|
44
|
+
* @param props.colors Card colors for light and dark mode.
|
|
45
|
+
* @param props.colors.light Card colors for light mode.
|
|
46
|
+
* @param props.colors.dark Card colors for dark mode, or `null` when no dark-mode override is needed.
|
|
41
47
|
* @param props.customTitle Card custom title.
|
|
42
48
|
* @param props.defaultTitle Card default title.
|
|
43
|
-
* @param props.titlePrefixIcon
|
|
49
|
+
* @param props.titlePrefixIcon Sanitized card title prefix icon.
|
|
44
50
|
*/
|
|
45
51
|
constructor({
|
|
46
52
|
width = 100,
|
|
47
53
|
height = 100,
|
|
48
54
|
border_radius = 4.5,
|
|
49
|
-
colors = {},
|
|
55
|
+
colors = { light: getCardColors({}), dark: null },
|
|
50
56
|
customTitle,
|
|
51
57
|
defaultTitle = "",
|
|
52
58
|
titlePrefixIcon,
|
|
53
59
|
}: {
|
|
54
60
|
width?: number;
|
|
55
61
|
height?: number;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
// `| undefined`: card callers forward possibly-undefined query options
|
|
63
|
+
border_radius?: number | undefined;
|
|
64
|
+
colors?: { light: CardColors; dark: CardColors | null };
|
|
65
|
+
customTitle?: string | undefined;
|
|
59
66
|
defaultTitle?: string;
|
|
60
67
|
titlePrefixIcon?: string;
|
|
61
68
|
}) {
|
|
@@ -65,15 +72,13 @@ class Card {
|
|
|
65
72
|
this.hideBorder = false;
|
|
66
73
|
this.hideTitle = false;
|
|
67
74
|
|
|
68
|
-
this.border_radius = border_radius;
|
|
75
|
+
this.border_radius = parseFloat(String(border_radius));
|
|
69
76
|
|
|
70
|
-
// returns theme based colors with proper overrides and defaults
|
|
71
77
|
this.colors = colors;
|
|
72
|
-
this.title =
|
|
73
|
-
customTitle === undefined ? defaultTitle : customTitle,
|
|
74
|
-
);
|
|
78
|
+
this.title = customTitle === undefined ? defaultTitle : customTitle;
|
|
75
79
|
|
|
76
80
|
this.css = "";
|
|
81
|
+
this.darkCss = "";
|
|
77
82
|
|
|
78
83
|
this.paddingX = 25;
|
|
79
84
|
this.paddingY = 35;
|
|
@@ -104,10 +109,17 @@ class Card {
|
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
/**
|
|
107
|
-
*
|
|
112
|
+
* Sets the card CSS for light and dark mode.
|
|
113
|
+
*
|
|
114
|
+
* The caller must ensure that the passed CSS strings are properly sanitized!
|
|
115
|
+
*
|
|
116
|
+
* @param props The props object.
|
|
117
|
+
* @param props.light CSS applied unconditionally (light/default mode).
|
|
118
|
+
* @param props.dark CSS placed inside a `@media (prefers-color-scheme: dark)` block. Pass `null` when not needed.
|
|
108
119
|
*/
|
|
109
|
-
setCSS(
|
|
110
|
-
this.css =
|
|
120
|
+
setCSS({ light, dark }: { light: string; dark?: string | null }): void {
|
|
121
|
+
this.css = light;
|
|
122
|
+
this.darkCss = dark ?? "";
|
|
111
123
|
}
|
|
112
124
|
|
|
113
125
|
/**
|
|
@@ -121,10 +133,13 @@ class Card {
|
|
|
121
133
|
* @param value Whether to hide the title or not.
|
|
122
134
|
*/
|
|
123
135
|
setHideTitle(value: boolean): void {
|
|
124
|
-
this.hideTitle
|
|
125
|
-
if (value) {
|
|
136
|
+
if (value && !this.hideTitle) {
|
|
126
137
|
this.height -= 30;
|
|
127
138
|
}
|
|
139
|
+
if (!value && this.hideTitle) {
|
|
140
|
+
this.height += 30;
|
|
141
|
+
}
|
|
142
|
+
this.hideTitle = value;
|
|
128
143
|
}
|
|
129
144
|
|
|
130
145
|
/**
|
|
@@ -144,7 +159,7 @@ class Card {
|
|
|
144
159
|
y="0"
|
|
145
160
|
class="header"
|
|
146
161
|
data-testid="header"
|
|
147
|
-
>${this.title}</text>
|
|
162
|
+
>${encodeHTML(this.title)}</text>
|
|
148
163
|
`;
|
|
149
164
|
|
|
150
165
|
const prefixIcon = `
|
|
@@ -177,17 +192,12 @@ class Card {
|
|
|
177
192
|
* @returns The rendered card gradient.
|
|
178
193
|
*/
|
|
179
194
|
renderGradient(): string {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const gradients = this.colors.bgColor.slice(1);
|
|
185
|
-
return typeof this.colors.bgColor === "object"
|
|
186
|
-
? `
|
|
187
|
-
<defs>
|
|
195
|
+
const buildGradientDef = (id: string, bgColor: Array<string>): string => {
|
|
196
|
+
const gradients = bgColor.slice(1);
|
|
197
|
+
return `
|
|
188
198
|
<linearGradient
|
|
189
|
-
id="
|
|
190
|
-
gradientTransform="rotate(${String(
|
|
199
|
+
id="${id}"
|
|
200
|
+
gradientTransform="rotate(${String(bgColor[0])})"
|
|
191
201
|
gradientUnits="userSpaceOnUse"
|
|
192
202
|
>
|
|
193
203
|
${gradients
|
|
@@ -196,10 +206,33 @@ class Card {
|
|
|
196
206
|
return `<stop offset="${offset}%" stop-color="#${grad}" />`;
|
|
197
207
|
})
|
|
198
208
|
.join(",")}
|
|
199
|
-
</linearGradient
|
|
209
|
+
</linearGradient>`;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if (
|
|
213
|
+
typeof this.colors.light.bgColor === "object" &&
|
|
214
|
+
!isValidGradient(this.colors.light.bgColor)
|
|
215
|
+
) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
`Invalid gradient: ${this.colors.light.bgColor.join(",")}`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
if (
|
|
221
|
+
this.colors.dark &&
|
|
222
|
+
typeof this.colors.dark.bgColor === "object" &&
|
|
223
|
+
!isValidGradient(this.colors.dark.bgColor)
|
|
224
|
+
) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`Invalid dark gradient: ${this.colors.dark.bgColor.join(",")}`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return `
|
|
231
|
+
<defs>
|
|
232
|
+
${typeof this.colors.light.bgColor === "object" ? buildGradientDef("gradient", this.colors.light.bgColor) : ""}
|
|
233
|
+
${this.colors.dark && typeof this.colors.dark.bgColor === "object" ? buildGradientDef("gradient-dark", this.colors.dark.bgColor) : ""}
|
|
200
234
|
</defs>
|
|
201
|
-
|
|
202
|
-
: "";
|
|
235
|
+
`;
|
|
203
236
|
}
|
|
204
237
|
|
|
205
238
|
/**
|
|
@@ -230,10 +263,55 @@ class Card {
|
|
|
230
263
|
};
|
|
231
264
|
|
|
232
265
|
/**
|
|
233
|
-
* @
|
|
266
|
+
* Builds the @media (prefers-color-scheme: dark) CSS block for the card.
|
|
267
|
+
* Returns an empty string when no dark colors are set.
|
|
268
|
+
*/
|
|
269
|
+
private renderDarkMediaBlock(): string {
|
|
270
|
+
if (!this.colors.dark) {
|
|
271
|
+
return "";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const bgFill =
|
|
275
|
+
typeof this.colors.dark.bgColor === "object"
|
|
276
|
+
? "url(#gradient-dark)"
|
|
277
|
+
: this.colors.dark.bgColor;
|
|
278
|
+
|
|
279
|
+
return `
|
|
280
|
+
@media (prefers-color-scheme: dark) {
|
|
281
|
+
.header { fill: ${this.colors.dark.titleColor}; }
|
|
282
|
+
.card-bg { fill: ${bgFill}; stroke: ${this.colors.dark.borderColor}; }
|
|
283
|
+
${this.darkCss}
|
|
284
|
+
}`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The caller must ensure that the passed `body` string is properly sanitized!
|
|
289
|
+
*
|
|
290
|
+
* @param body The sanitized inner body of the card.
|
|
234
291
|
* @returns The rendered card.
|
|
235
292
|
*/
|
|
236
293
|
render(body: string): string {
|
|
294
|
+
if (!Number.isFinite(this.border_radius)) {
|
|
295
|
+
throw new Error(`Invalid border radius: "${this.border_radius}"`);
|
|
296
|
+
}
|
|
297
|
+
if (!isPrefixedHexColor(this.colors.light.titleColor)) {
|
|
298
|
+
throw new Error(`Invalid title color: "${this.colors.light.titleColor}"`);
|
|
299
|
+
}
|
|
300
|
+
if (!isPrefixedHexColor(this.colors.light.borderColor)) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`Invalid border color: "${this.colors.light.borderColor}"`,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
if (
|
|
306
|
+
!(typeof this.colors.light.bgColor === "object"
|
|
307
|
+
? isValidGradient(this.colors.light.bgColor)
|
|
308
|
+
: isPrefixedHexColor(this.colors.light.bgColor))
|
|
309
|
+
) {
|
|
310
|
+
throw new Error(
|
|
311
|
+
`Invalid background color: ${String(this.colors.light.bgColor)}`,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
237
315
|
return `
|
|
238
316
|
<svg
|
|
239
317
|
width="${this.width}"
|
|
@@ -244,12 +322,12 @@ class Card {
|
|
|
244
322
|
role="img"
|
|
245
323
|
aria-labelledby="descId"
|
|
246
324
|
>
|
|
247
|
-
<title id="titleId">${this.a11yTitle}</title>
|
|
248
|
-
<desc id="descId">${this.a11yDesc}</desc>
|
|
325
|
+
<title id="titleId">${encodeHTML(this.a11yTitle)}</title>
|
|
326
|
+
<desc id="descId">${encodeHTML(this.a11yDesc)}</desc>
|
|
249
327
|
<style>
|
|
250
328
|
.header {
|
|
251
329
|
font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif;
|
|
252
|
-
fill: ${
|
|
330
|
+
fill: ${this.colors.light.titleColor};
|
|
253
331
|
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
|
254
332
|
}
|
|
255
333
|
@supports(-moz-appearance: auto) {
|
|
@@ -257,6 +335,7 @@ class Card {
|
|
|
257
335
|
.header { font-size: 15.5px; }
|
|
258
336
|
}
|
|
259
337
|
${this.css}
|
|
338
|
+
${this.renderDarkMediaBlock()}
|
|
260
339
|
|
|
261
340
|
${this.getAnimations()}
|
|
262
341
|
${
|
|
@@ -270,16 +349,17 @@ class Card {
|
|
|
270
349
|
|
|
271
350
|
<rect
|
|
272
351
|
data-testid="card-bg"
|
|
352
|
+
class="card-bg"
|
|
273
353
|
x="0.5"
|
|
274
354
|
y="0.5"
|
|
275
355
|
rx="${this.border_radius}"
|
|
276
356
|
height="99%"
|
|
277
|
-
stroke="${
|
|
357
|
+
stroke="${this.colors.light.borderColor}"
|
|
278
358
|
width="${this.width - 1}"
|
|
279
359
|
fill="${
|
|
280
|
-
typeof this.colors.bgColor === "object"
|
|
360
|
+
typeof this.colors.light.bgColor === "object"
|
|
281
361
|
? "url(#gradient)"
|
|
282
|
-
:
|
|
362
|
+
: this.colors.light.bgColor
|
|
283
363
|
}"
|
|
284
364
|
stroke-opacity="${this.hideBorder ? 0 : 1}"
|
|
285
365
|
/>
|
package/src/common/I18n.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const FALLBACK_LOCALE = "en";
|
|
2
2
|
|
|
3
|
+
/** A map of translation keys to per-locale strings. */
|
|
4
|
+
type TranslationsMap = Record<string, Record<string, string>>;
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
7
|
* I18n translation class.
|
|
5
8
|
*/
|
|
6
|
-
class I18n<Translations extends
|
|
9
|
+
class I18n<Translations extends TranslationsMap = TranslationsMap> {
|
|
7
10
|
locale: string;
|
|
8
11
|
translations: Translations;
|
|
9
12
|
|
|
@@ -16,7 +19,8 @@ class I18n<Translations extends Record<string, Record<string, string>>> {
|
|
|
16
19
|
locale,
|
|
17
20
|
translations,
|
|
18
21
|
}: {
|
|
19
|
-
|
|
22
|
+
// `| undefined`: card callers forward possibly-undefined query options
|
|
23
|
+
locale?: string | undefined;
|
|
20
24
|
translations: Translations;
|
|
21
25
|
}) {
|
|
22
26
|
this.locale = locale || FALLBACK_LOCALE;
|
package/src/common/color.ts
CHANGED
|
@@ -1,30 +1,87 @@
|
|
|
1
1
|
import { themes } from "../themes/index.js";
|
|
2
|
+
import type { ThemeName } from "../themes/index.js";
|
|
3
|
+
|
|
4
|
+
/** Matches a 3-, 4-, 6-, or 8-digit hex color with no leading `#`. */
|
|
5
|
+
const HEX_COLOR =
|
|
6
|
+
/^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{3})$/;
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
|
-
* Checks if a
|
|
9
|
+
* Checks if a value is a bare hex color, i.e. hex digits with no `#` prefix
|
|
10
|
+
* (`"f00"`, `"ffffff"`). This is the form user-supplied color params and
|
|
11
|
+
* gradient stops arrive in.
|
|
5
12
|
*
|
|
6
|
-
* @param
|
|
7
|
-
* @returns True if the
|
|
13
|
+
* @param value Value to check.
|
|
14
|
+
* @returns True if the value is a bare hex color.
|
|
8
15
|
*/
|
|
9
|
-
const
|
|
10
|
-
return
|
|
11
|
-
/^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{4})$/,
|
|
12
|
-
).test(hexColor);
|
|
16
|
+
const isBareHexColor = (value: unknown): boolean => {
|
|
17
|
+
return typeof value === "string" && HEX_COLOR.test(value);
|
|
13
18
|
};
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
|
-
*
|
|
21
|
+
* Checks if a value is a `#`-prefixed hex color (`"#f00"`, `"#ffffff"`). This
|
|
22
|
+
* is the form colors take once resolved by {@link getCardColors}, i.e. right
|
|
23
|
+
* before they are written into the SVG.
|
|
17
24
|
*
|
|
18
|
-
* @param
|
|
19
|
-
* @returns True if the
|
|
25
|
+
* @param value Value to check.
|
|
26
|
+
* @returns True if the value is a `#`-prefixed hex color.
|
|
20
27
|
*/
|
|
21
|
-
const
|
|
28
|
+
const isPrefixedHexColor = (value: unknown): boolean => {
|
|
22
29
|
return (
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
typeof value === "string" &&
|
|
31
|
+
value.startsWith("#") &&
|
|
32
|
+
HEX_COLOR.test(value.slice(1))
|
|
25
33
|
);
|
|
26
34
|
};
|
|
27
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Checks if the given parts form a valid gradient: a finite numeric angle
|
|
38
|
+
* followed by at least two bare-hex color stops, e.g. `["90", "f00", "0f0"]`.
|
|
39
|
+
* The angle is written into the SVG `gradientTransform="rotate(...)"`.
|
|
40
|
+
*
|
|
41
|
+
* @param parts Gradient parts: `[angle, ...stops]`.
|
|
42
|
+
* @returns True if the parts form a valid gradient.
|
|
43
|
+
*/
|
|
44
|
+
const isValidGradient = (parts: Array<string>): boolean => {
|
|
45
|
+
const [angle, ...stops] = parts;
|
|
46
|
+
return (
|
|
47
|
+
stops.length >= 2 &&
|
|
48
|
+
angle !== undefined &&
|
|
49
|
+
angle.trim() !== "" &&
|
|
50
|
+
Number.isFinite(Number(angle)) &&
|
|
51
|
+
stops.every(isBareHexColor)
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Checks if a string is a valid input for a color or gradient.
|
|
57
|
+
*
|
|
58
|
+
* @param color String to check, may be null or undefined.
|
|
59
|
+
* @returns True if the given string is a valid input.
|
|
60
|
+
*/
|
|
61
|
+
const isValidColorInput = (color: string | null | undefined): boolean => {
|
|
62
|
+
if (color === null || color === undefined) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return isValidGradient(color.split(",")) || isBareHexColor(color);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Iterates over a collection of colors inputs and verifies that each is a valid color or gradient.
|
|
70
|
+
*
|
|
71
|
+
* @param colors Object whose values are checked as valid color inputs.
|
|
72
|
+
* @return The first key where the associated input value is not valid. null if all inputs are valid.
|
|
73
|
+
*/
|
|
74
|
+
const findInvalidColor = (
|
|
75
|
+
colors: Record<string, string | null | undefined>,
|
|
76
|
+
): string | null => {
|
|
77
|
+
for (const [key, value] of Object.entries(colors)) {
|
|
78
|
+
if (!isValidColorInput(value)) {
|
|
79
|
+
return key;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
};
|
|
84
|
+
|
|
28
85
|
/**
|
|
29
86
|
* Retrieves a gradient if color has more than one valid hex codes else a single color.
|
|
30
87
|
*
|
|
@@ -41,7 +98,7 @@ const fallbackColor = (
|
|
|
41
98
|
return colors;
|
|
42
99
|
}
|
|
43
100
|
|
|
44
|
-
if (color !== undefined &&
|
|
101
|
+
if (color !== undefined && isBareHexColor(color)) {
|
|
45
102
|
return `#${color}`;
|
|
46
103
|
}
|
|
47
104
|
|
|
@@ -58,8 +115,38 @@ interface CardColors {
|
|
|
58
115
|
bgColor: string | Array<string>;
|
|
59
116
|
borderColor: string;
|
|
60
117
|
ringColor: string;
|
|
118
|
+
progBarBgColor: string;
|
|
61
119
|
}
|
|
62
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Every color param a card accepts, before any `_light` / `_dark` suffix.
|
|
123
|
+
*
|
|
124
|
+
* Single source of truth: the param types and {@link COLOR_PARAM_KEYS} are all
|
|
125
|
+
* derived from this, so adding a param here is enough.
|
|
126
|
+
*/
|
|
127
|
+
const BASE_COLOR_KEYS = [
|
|
128
|
+
"title_color",
|
|
129
|
+
"icon_color",
|
|
130
|
+
"text_color",
|
|
131
|
+
"bg_color",
|
|
132
|
+
"border_color",
|
|
133
|
+
"ring_color",
|
|
134
|
+
"prog_bar_bg_color",
|
|
135
|
+
"theme",
|
|
136
|
+
] as const;
|
|
137
|
+
|
|
138
|
+
const THEME_VARIANTS = ["light", "dark"] as const;
|
|
139
|
+
|
|
140
|
+
type BaseColorKey = (typeof BASE_COLOR_KEYS)[number];
|
|
141
|
+
type ThemeVariant = (typeof THEME_VARIANTS)[number];
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Object with all input color params. Not every field is consumed by every card
|
|
145
|
+
* (e.g. `prog_bar_bg_color` is only used by the top-languages card's `normal`
|
|
146
|
+
* layout).
|
|
147
|
+
*/
|
|
148
|
+
type ColorInput = Partial<Record<BaseColorKey, string | undefined>>;
|
|
149
|
+
|
|
63
150
|
/**
|
|
64
151
|
* Returns theme based colors with proper overrides and defaults.
|
|
65
152
|
*
|
|
@@ -70,6 +157,7 @@ interface CardColors {
|
|
|
70
157
|
* @param props.bg_color Card background color.
|
|
71
158
|
* @param props.border_color Card border color.
|
|
72
159
|
* @param props.ring_color Card ring color.
|
|
160
|
+
* @param props.prog_bar_bg_color Progress bar background color.
|
|
73
161
|
* @param props.theme Card theme.
|
|
74
162
|
* @returns Card colors.
|
|
75
163
|
*/
|
|
@@ -80,21 +168,14 @@ const getCardColors = ({
|
|
|
80
168
|
bg_color,
|
|
81
169
|
border_color,
|
|
82
170
|
ring_color,
|
|
171
|
+
prog_bar_bg_color,
|
|
83
172
|
theme,
|
|
84
|
-
}: {
|
|
85
|
-
title_color?: string;
|
|
86
|
-
text_color?: string;
|
|
87
|
-
icon_color?: string;
|
|
88
|
-
bg_color?: string;
|
|
89
|
-
border_color?: string;
|
|
90
|
-
ring_color?: string;
|
|
91
|
-
theme?: string;
|
|
92
|
-
}): CardColors => {
|
|
173
|
+
}: ColorInput): CardColors => {
|
|
93
174
|
const defaultTheme = themes.default;
|
|
94
175
|
const isThemeProvided = theme !== undefined && theme in themes;
|
|
95
176
|
|
|
96
177
|
const selectedTheme = isThemeProvided
|
|
97
|
-
? themes[theme as
|
|
178
|
+
? themes[theme as ThemeName]
|
|
98
179
|
: defaultTheme;
|
|
99
180
|
|
|
100
181
|
const defaultBorderColor =
|
|
@@ -111,8 +192,6 @@ const getCardColors = ({
|
|
|
111
192
|
|
|
112
193
|
// get the color provided by the user else the theme color
|
|
113
194
|
// finally if both colors are invalid we use the titleColor
|
|
114
|
-
// NOTE: no built-in theme defines `ring_color`, so it falls back to the title color.
|
|
115
|
-
const ringColor = fallbackColor(ring_color, titleColor);
|
|
116
195
|
const iconColor = fallbackColor(
|
|
117
196
|
icon_color || selectedTheme.icon_color,
|
|
118
197
|
"#" + defaultTheme.icon_color,
|
|
@@ -130,11 +209,16 @@ const getCardColors = ({
|
|
|
130
209
|
border_color || defaultBorderColor,
|
|
131
210
|
"#" + defaultBorderColor,
|
|
132
211
|
);
|
|
212
|
+
// No theme defines `ring_color`, so it falls back to the title color.
|
|
213
|
+
const ringColor = fallbackColor(ring_color, titleColor);
|
|
214
|
+
// No theme defines `prog_bar_bg_color`, so it falls back to "#ddd".
|
|
215
|
+
const progBarBgColor = fallbackColor(prog_bar_bg_color, "#ddd");
|
|
133
216
|
|
|
134
217
|
if (
|
|
135
218
|
typeof titleColor !== "string" ||
|
|
136
219
|
typeof textColor !== "string" ||
|
|
137
220
|
typeof ringColor !== "string" ||
|
|
221
|
+
typeof progBarBgColor !== "string" ||
|
|
138
222
|
typeof iconColor !== "string" ||
|
|
139
223
|
typeof borderColor !== "string"
|
|
140
224
|
) {
|
|
@@ -143,7 +227,135 @@ const getCardColors = ({
|
|
|
143
227
|
);
|
|
144
228
|
}
|
|
145
229
|
|
|
146
|
-
return {
|
|
230
|
+
return {
|
|
231
|
+
titleColor,
|
|
232
|
+
iconColor,
|
|
233
|
+
textColor,
|
|
234
|
+
bgColor,
|
|
235
|
+
borderColor,
|
|
236
|
+
ringColor,
|
|
237
|
+
progBarBgColor,
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
type LightDarkColorParams = Partial<
|
|
242
|
+
Record<`${BaseColorKey}_${ThemeVariant}`, string | undefined>
|
|
243
|
+
>;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Returns the light- or dark-mode-specific color params, given a set of
|
|
247
|
+
* raw query params. Also removes the "_light" or "_dark" suffixes.
|
|
248
|
+
*
|
|
249
|
+
* @param params Raw query params with optional `_light` / `_dark` suffixes.
|
|
250
|
+
* @param suffix `"_light"` or `"_dark"`.
|
|
251
|
+
* @returns ColorInput with the suffix stripped, ready for `getCardColors`.
|
|
252
|
+
*/
|
|
253
|
+
const extractLightDarkColors = (
|
|
254
|
+
params: LightDarkColorParams,
|
|
255
|
+
suffix: `_${ThemeVariant}`,
|
|
256
|
+
): ColorInput =>
|
|
257
|
+
Object.fromEntries(
|
|
258
|
+
BASE_COLOR_KEYS.map((key) => [key, params[`${key}${suffix}`]]),
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Returns resolved colors for both light and dark mode given all input params.
|
|
263
|
+
*
|
|
264
|
+
* Each mode resolves independently, then runs the normal `getCardColors` precedence
|
|
265
|
+
* (explicit color -> theme color -> default theme):
|
|
266
|
+
* light: `theme_light ?? theme`, with `*_light` params overriding general ones
|
|
267
|
+
* dark: `theme_dark ?? theme`, with `*_dark` params overriding general ones
|
|
268
|
+
*
|
|
269
|
+
* Anything a mode does not override falls back to the general params,
|
|
270
|
+
* so a partial override such as `bg_color_dark` alone keeps every other color from the base theme.
|
|
271
|
+
*
|
|
272
|
+
* When no `_light` / `_dark` param is provided at all, `darkColors` is `null` and the caller emits no dark-mode block.
|
|
273
|
+
*
|
|
274
|
+
* @param params Raw query params, containing both general and `_light`/`_dark` suffixed colors and themes.
|
|
275
|
+
* @returns `{ lightColors, darkColors }`, resolved colors for both light and dark mode
|
|
276
|
+
*/
|
|
277
|
+
const getLightDarkColors = (
|
|
278
|
+
params: ColorInput & LightDarkColorParams,
|
|
279
|
+
): { lightColors: CardColors; darkColors: CardColors | null } => {
|
|
280
|
+
const lightOverrides = extractLightDarkColors(params, "_light");
|
|
281
|
+
const darkOverrides = extractLightDarkColors(params, "_dark");
|
|
282
|
+
|
|
283
|
+
const hasModeOverrides =
|
|
284
|
+
Object.values(lightOverrides).some((v) => v !== undefined) ||
|
|
285
|
+
Object.values(darkOverrides).some((v) => v !== undefined);
|
|
286
|
+
|
|
287
|
+
if (!hasModeOverrides) {
|
|
288
|
+
return { lightColors: getCardColors(params), darkColors: null };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const defined = (obj: ColorInput): ColorInput =>
|
|
292
|
+
Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
lightColors: getCardColors({ ...params, ...defined(lightOverrides) }),
|
|
296
|
+
darkColors: getCardColors({ ...params, ...defined(darkOverrides) }),
|
|
297
|
+
};
|
|
147
298
|
};
|
|
148
299
|
|
|
149
|
-
|
|
300
|
+
type ColorParams = ColorInput & LightDarkColorParams;
|
|
301
|
+
|
|
302
|
+
const COLOR_PARAM_KEYS: ReadonlyArray<keyof ColorParams> = [
|
|
303
|
+
...BASE_COLOR_KEYS,
|
|
304
|
+
...THEME_VARIANTS.flatMap((variant) =>
|
|
305
|
+
BASE_COLOR_KEYS.map((key) => `${key}_${variant}` as const),
|
|
306
|
+
),
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Picks all color-related parameters from a query object.
|
|
311
|
+
*
|
|
312
|
+
* @param query Raw query parameters.
|
|
313
|
+
* @returns All color-related parameters.
|
|
314
|
+
*/
|
|
315
|
+
const pickColorParams = (
|
|
316
|
+
query: Record<string, string | undefined>,
|
|
317
|
+
): ColorParams =>
|
|
318
|
+
Object.fromEntries(
|
|
319
|
+
COLOR_PARAM_KEYS.filter((k) => k in query).map((k) => [k, query[k]]),
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
/** Params naming a theme rather than holding a color value. */
|
|
323
|
+
const THEME_PARAM_KEYS: ReadonlyArray<keyof ColorParams> = [
|
|
324
|
+
"theme",
|
|
325
|
+
...THEME_VARIANTS.map((variant) => `theme_${variant}` as const),
|
|
326
|
+
];
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Finds the first color param holding an invalid color.
|
|
330
|
+
*
|
|
331
|
+
* Theme params are skipped: they name a theme, and an unknown name falls back
|
|
332
|
+
* to the default rather than being an error.
|
|
333
|
+
*
|
|
334
|
+
* @param params Color params, as returned by {@link pickColorParams}.
|
|
335
|
+
* @returns The first invalid param name, or null if all are valid.
|
|
336
|
+
*/
|
|
337
|
+
const findInvalidColorParam = (params: ColorParams): string | null =>
|
|
338
|
+
findInvalidColor(
|
|
339
|
+
Object.fromEntries(
|
|
340
|
+
Object.entries(params).filter(
|
|
341
|
+
([key]) => !THEME_PARAM_KEYS.includes(key as keyof ColorParams),
|
|
342
|
+
),
|
|
343
|
+
),
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
export {
|
|
347
|
+
getCardColors,
|
|
348
|
+
getLightDarkColors,
|
|
349
|
+
findInvalidColor,
|
|
350
|
+
findInvalidColorParam,
|
|
351
|
+
pickColorParams,
|
|
352
|
+
isValidGradient,
|
|
353
|
+
isBareHexColor,
|
|
354
|
+
isPrefixedHexColor,
|
|
355
|
+
|
|
356
|
+
// Not re-exported from the package index: internal,
|
|
357
|
+
// exposed so tests can pin the accepted param list.
|
|
358
|
+
BASE_COLOR_KEYS,
|
|
359
|
+
THEME_VARIANTS,
|
|
360
|
+
COLOR_PARAM_KEYS,
|
|
361
|
+
};
|