@stats-organization/github-readme-stats-core 2.1.5 → 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 +9 -41
- package/build/api/index.d.ts +3 -8
- package/build/api/index.d.ts.map +1 -1
- package/build/api/index.js +23 -45
- package/build/api/pin.d.ts +2 -7
- package/build/api/pin.d.ts.map +1 -1
- package/build/api/pin.js +9 -41
- 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 +11 -55
- package/build/api/wakatime.d.ts +2 -7
- package/build/api/wakatime.d.ts.map +1 -1
- package/build/api/wakatime.js +9 -41
- 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 +58 -60
- package/build/cards/stats.d.ts +29 -46
- package/build/cards/stats.d.ts.map +1 -1
- package/build/cards/stats.js +113 -181
- 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 +174 -200
- package/build/cards/wakatime.d.ts +21 -11
- package/build/cards/wakatime.d.ts.map +1 -1
- package/build/cards/wakatime.js +73 -111
- package/build/common/Card.d.ts +34 -13
- package/build/common/Card.d.ts.map +1 -1
- package/build/common/Card.js +73 -40
- package/build/common/I18n.d.ts +4 -2
- package/build/common/I18n.d.ts.map +1 -1
- package/build/common/color.d.ts +60 -18
- package/build/common/color.d.ts.map +1 -1
- package/build/common/color.js +102 -5
- 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/http.d.ts +25 -2
- package/build/common/http.d.ts.map +1 -1
- package/build/common/http.js +26 -3
- 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 +1 -0
- package/build/common/ops.d.ts +3 -10
- package/build/common/ops.d.ts.map +1 -1
- package/build/common/ops.js +3 -15
- package/build/common/render.d.ts +43 -6
- package/build/common/render.d.ts.map +1 -1
- package/build/common/render.js +77 -11
- package/build/common/retryer.d.ts +3 -2
- package/build/common/retryer.d.ts.map +1 -1
- package/build/common/retryer.js +1 -0
- package/build/fetchers/gist.d.ts.map +1 -1
- package/build/fetchers/gist.js +8 -39
- package/build/fetchers/repo.d.ts +12 -11
- package/build/fetchers/repo.d.ts.map +1 -1
- package/build/fetchers/repo.js +26 -67
- package/build/fetchers/stats.d.ts +24 -17
- package/build/fetchers/stats.d.ts.map +1 -1
- package/build/fetchers/stats.js +302 -183
- 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 +24 -20
- package/build/fetchers/types.d.ts.map +1 -1
- 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 +13 -7
- package/src/_github-username-regex.d.ts +4 -0
- package/src/api/gist.js +10 -46
- package/src/api/index.js +30 -51
- package/src/api/pin.js +10 -46
- package/src/api/top-langs.js +13 -60
- package/src/api/wakatime.js +10 -46
- package/src/cards/common-options.ts +13 -0
- package/src/cards/{gist.js → gist.ts} +37 -37
- package/src/cards/{repo.js → repo.ts} +86 -68
- package/src/cards/{stats.js → stats.ts} +168 -219
- package/src/cards/{top-languages.js → top-languages.ts} +309 -265
- package/src/cards/{wakatime.js → wakatime.ts} +136 -130
- package/src/common/Card.ts +94 -49
- package/src/common/I18n.ts +6 -2
- package/src/common/color.ts +163 -14
- package/src/common/config.ts +22 -29
- package/src/common/constants.ts +3 -1
- package/src/common/date.ts +45 -0
- package/src/common/http.ts +43 -3
- package/src/common/icons.ts +1 -0
- package/src/common/languageColors.json +1 -0
- package/src/common/languageColors.ts +19 -0
- package/src/common/ops.ts +8 -18
- package/src/common/render.ts +114 -14
- package/src/common/retryer.ts +6 -5
- package/src/fetchers/gist.ts +15 -72
- 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.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/fetchers/repo.js +0 -161
- package/src/fetchers/stats.js +0 -473
- package/src/fetchers/top-languages.js +0 -175
- package/src/themes/README.md +0 -229
package/src/common/Card.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { isPrefixedHexColor, isValidGradient } from "./color.js";
|
|
1
|
+
import { getCardColors, isPrefixedHexColor, isValidGradient } from "./color.js";
|
|
2
2
|
import { encodeHTML } from "./html.js";
|
|
3
3
|
import { flexLayout } from "./render.js";
|
|
4
4
|
|
|
5
5
|
interface CardColors {
|
|
6
6
|
/** Card title color. */
|
|
7
|
-
titleColor
|
|
7
|
+
titleColor: string;
|
|
8
8
|
/** Card text color. */
|
|
9
|
-
textColor
|
|
9
|
+
textColor: string;
|
|
10
10
|
/** Card icon color. */
|
|
11
|
-
iconColor
|
|
11
|
+
iconColor: string;
|
|
12
12
|
/** Card background color. */
|
|
13
|
-
bgColor
|
|
13
|
+
bgColor: string | Array<string>;
|
|
14
14
|
/** Card border color. */
|
|
15
|
-
borderColor
|
|
15
|
+
borderColor: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
class Card {
|
|
@@ -21,9 +21,10 @@ class Card {
|
|
|
21
21
|
hideBorder: boolean;
|
|
22
22
|
hideTitle: boolean;
|
|
23
23
|
border_radius: number;
|
|
24
|
-
colors: CardColors;
|
|
24
|
+
colors: { light: CardColors; dark: CardColors | null };
|
|
25
25
|
title: string;
|
|
26
26
|
css: string;
|
|
27
|
+
darkCss: string;
|
|
27
28
|
paddingX: number;
|
|
28
29
|
paddingY: number;
|
|
29
30
|
titlePrefixIcon: string | undefined;
|
|
@@ -40,7 +41,9 @@ class Card {
|
|
|
40
41
|
* @param props.width Card width.
|
|
41
42
|
* @param props.height Card height.
|
|
42
43
|
* @param props.border_radius Card border radius.
|
|
43
|
-
* @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.
|
|
44
47
|
* @param props.customTitle Card custom title.
|
|
45
48
|
* @param props.defaultTitle Card default title.
|
|
46
49
|
* @param props.titlePrefixIcon Sanitized card title prefix icon.
|
|
@@ -49,16 +52,17 @@ class Card {
|
|
|
49
52
|
width = 100,
|
|
50
53
|
height = 100,
|
|
51
54
|
border_radius = 4.5,
|
|
52
|
-
colors = {},
|
|
55
|
+
colors = { light: getCardColors({}), dark: null },
|
|
53
56
|
customTitle,
|
|
54
57
|
defaultTitle = "",
|
|
55
58
|
titlePrefixIcon,
|
|
56
59
|
}: {
|
|
57
60
|
width?: number;
|
|
58
61
|
height?: number;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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;
|
|
62
66
|
defaultTitle?: string;
|
|
63
67
|
titlePrefixIcon?: string;
|
|
64
68
|
}) {
|
|
@@ -70,11 +74,11 @@ class Card {
|
|
|
70
74
|
|
|
71
75
|
this.border_radius = parseFloat(String(border_radius));
|
|
72
76
|
|
|
73
|
-
// returns theme based colors with proper overrides and defaults
|
|
74
77
|
this.colors = colors;
|
|
75
78
|
this.title = customTitle === undefined ? defaultTitle : customTitle;
|
|
76
79
|
|
|
77
80
|
this.css = "";
|
|
81
|
+
this.darkCss = "";
|
|
78
82
|
|
|
79
83
|
this.paddingX = 25;
|
|
80
84
|
this.paddingY = 35;
|
|
@@ -105,12 +109,17 @@ class Card {
|
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
/**
|
|
108
|
-
*
|
|
112
|
+
* Sets the card CSS for light and dark mode.
|
|
109
113
|
*
|
|
110
|
-
*
|
|
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.
|
|
111
119
|
*/
|
|
112
|
-
setCSS(
|
|
113
|
-
this.css =
|
|
120
|
+
setCSS({ light, dark }: { light: string; dark?: string | null }): void {
|
|
121
|
+
this.css = light;
|
|
122
|
+
this.darkCss = dark ?? "";
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
/**
|
|
@@ -183,19 +192,12 @@ class Card {
|
|
|
183
192
|
* @returns The rendered card gradient.
|
|
184
193
|
*/
|
|
185
194
|
renderGradient(): string {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (!isValidGradient(this.colors.bgColor)) {
|
|
190
|
-
throw new Error(`Invalid gradient: ${this.colors.bgColor.join(",")}`);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const gradients = this.colors.bgColor.slice(1);
|
|
194
|
-
return `
|
|
195
|
-
<defs>
|
|
195
|
+
const buildGradientDef = (id: string, bgColor: Array<string>): string => {
|
|
196
|
+
const gradients = bgColor.slice(1);
|
|
197
|
+
return `
|
|
196
198
|
<linearGradient
|
|
197
|
-
id="
|
|
198
|
-
gradientTransform="rotate(${String(
|
|
199
|
+
id="${id}"
|
|
200
|
+
gradientTransform="rotate(${String(bgColor[0])})"
|
|
199
201
|
gradientUnits="userSpaceOnUse"
|
|
200
202
|
>
|
|
201
203
|
${gradients
|
|
@@ -204,7 +206,31 @@ class Card {
|
|
|
204
206
|
return `<stop offset="${offset}%" stop-color="#${grad}" />`;
|
|
205
207
|
})
|
|
206
208
|
.join(",")}
|
|
207
|
-
</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) : ""}
|
|
208
234
|
</defs>
|
|
209
235
|
`;
|
|
210
236
|
}
|
|
@@ -236,6 +262,28 @@ class Card {
|
|
|
236
262
|
`;
|
|
237
263
|
};
|
|
238
264
|
|
|
265
|
+
/**
|
|
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
|
+
|
|
239
287
|
/**
|
|
240
288
|
* The caller must ensure that the passed `body` string is properly sanitized!
|
|
241
289
|
*
|
|
@@ -246,26 +294,21 @@ class Card {
|
|
|
246
294
|
if (!Number.isFinite(this.border_radius)) {
|
|
247
295
|
throw new Error(`Invalid border radius: "${this.border_radius}"`);
|
|
248
296
|
}
|
|
249
|
-
if (
|
|
250
|
-
this.colors.titleColor
|
|
251
|
-
!isPrefixedHexColor(this.colors.titleColor)
|
|
252
|
-
) {
|
|
253
|
-
throw new Error(`Invalid title color: "${this.colors.titleColor}"`);
|
|
297
|
+
if (!isPrefixedHexColor(this.colors.light.titleColor)) {
|
|
298
|
+
throw new Error(`Invalid title color: "${this.colors.light.titleColor}"`);
|
|
254
299
|
}
|
|
255
|
-
if (
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
throw new Error(`Invalid border color: "${this.colors.borderColor}"`);
|
|
300
|
+
if (!isPrefixedHexColor(this.colors.light.borderColor)) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`Invalid border color: "${this.colors.light.borderColor}"`,
|
|
303
|
+
);
|
|
260
304
|
}
|
|
261
305
|
if (
|
|
262
|
-
this.colors.bgColor
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
: isPrefixedHexColor(this.colors.bgColor))
|
|
306
|
+
!(typeof this.colors.light.bgColor === "object"
|
|
307
|
+
? isValidGradient(this.colors.light.bgColor)
|
|
308
|
+
: isPrefixedHexColor(this.colors.light.bgColor))
|
|
266
309
|
) {
|
|
267
310
|
throw new Error(
|
|
268
|
-
`Invalid background color: ${String(this.colors.bgColor)}`,
|
|
311
|
+
`Invalid background color: ${String(this.colors.light.bgColor)}`,
|
|
269
312
|
);
|
|
270
313
|
}
|
|
271
314
|
|
|
@@ -284,7 +327,7 @@ class Card {
|
|
|
284
327
|
<style>
|
|
285
328
|
.header {
|
|
286
329
|
font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif;
|
|
287
|
-
fill: ${
|
|
330
|
+
fill: ${this.colors.light.titleColor};
|
|
288
331
|
animation: fadeInAnimation 0.8s ease-in-out forwards;
|
|
289
332
|
}
|
|
290
333
|
@supports(-moz-appearance: auto) {
|
|
@@ -292,6 +335,7 @@ class Card {
|
|
|
292
335
|
.header { font-size: 15.5px; }
|
|
293
336
|
}
|
|
294
337
|
${this.css}
|
|
338
|
+
${this.renderDarkMediaBlock()}
|
|
295
339
|
|
|
296
340
|
${this.getAnimations()}
|
|
297
341
|
${
|
|
@@ -305,16 +349,17 @@ class Card {
|
|
|
305
349
|
|
|
306
350
|
<rect
|
|
307
351
|
data-testid="card-bg"
|
|
352
|
+
class="card-bg"
|
|
308
353
|
x="0.5"
|
|
309
354
|
y="0.5"
|
|
310
355
|
rx="${this.border_radius}"
|
|
311
356
|
height="99%"
|
|
312
|
-
stroke="${
|
|
357
|
+
stroke="${this.colors.light.borderColor}"
|
|
313
358
|
width="${this.width - 1}"
|
|
314
359
|
fill="${
|
|
315
|
-
typeof this.colors.bgColor === "object"
|
|
360
|
+
typeof this.colors.light.bgColor === "object"
|
|
316
361
|
? "url(#gradient)"
|
|
317
|
-
:
|
|
362
|
+
: this.colors.light.bgColor
|
|
318
363
|
}"
|
|
319
364
|
stroke-opacity="${this.hideBorder ? 0 : 1}"
|
|
320
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,4 +1,5 @@
|
|
|
1
1
|
import { themes } from "../themes/index.js";
|
|
2
|
+
import type { ThemeName } from "../themes/index.js";
|
|
2
3
|
|
|
3
4
|
/** Matches a 3-, 4-, 6-, or 8-digit hex color with no leading `#`. */
|
|
4
5
|
const HEX_COLOR =
|
|
@@ -114,8 +115,38 @@ interface CardColors {
|
|
|
114
115
|
bgColor: string | Array<string>;
|
|
115
116
|
borderColor: string;
|
|
116
117
|
ringColor: string;
|
|
118
|
+
progBarBgColor: string;
|
|
117
119
|
}
|
|
118
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
|
+
|
|
119
150
|
/**
|
|
120
151
|
* Returns theme based colors with proper overrides and defaults.
|
|
121
152
|
*
|
|
@@ -126,6 +157,7 @@ interface CardColors {
|
|
|
126
157
|
* @param props.bg_color Card background color.
|
|
127
158
|
* @param props.border_color Card border color.
|
|
128
159
|
* @param props.ring_color Card ring color.
|
|
160
|
+
* @param props.prog_bar_bg_color Progress bar background color.
|
|
129
161
|
* @param props.theme Card theme.
|
|
130
162
|
* @returns Card colors.
|
|
131
163
|
*/
|
|
@@ -136,21 +168,14 @@ const getCardColors = ({
|
|
|
136
168
|
bg_color,
|
|
137
169
|
border_color,
|
|
138
170
|
ring_color,
|
|
171
|
+
prog_bar_bg_color,
|
|
139
172
|
theme,
|
|
140
|
-
}: {
|
|
141
|
-
title_color?: string | undefined;
|
|
142
|
-
text_color?: string | undefined;
|
|
143
|
-
icon_color?: string | undefined;
|
|
144
|
-
bg_color?: string | undefined;
|
|
145
|
-
border_color?: string | undefined;
|
|
146
|
-
ring_color?: string | undefined;
|
|
147
|
-
theme?: string | undefined;
|
|
148
|
-
}): CardColors => {
|
|
173
|
+
}: ColorInput): CardColors => {
|
|
149
174
|
const defaultTheme = themes.default;
|
|
150
175
|
const isThemeProvided = theme !== undefined && theme in themes;
|
|
151
176
|
|
|
152
177
|
const selectedTheme = isThemeProvided
|
|
153
|
-
? themes[theme as
|
|
178
|
+
? themes[theme as ThemeName]
|
|
154
179
|
: defaultTheme;
|
|
155
180
|
|
|
156
181
|
const defaultBorderColor =
|
|
@@ -167,8 +192,6 @@ const getCardColors = ({
|
|
|
167
192
|
|
|
168
193
|
// get the color provided by the user else the theme color
|
|
169
194
|
// finally if both colors are invalid we use the titleColor
|
|
170
|
-
// NOTE: no built-in theme defines `ring_color`, so it falls back to the title color.
|
|
171
|
-
const ringColor = fallbackColor(ring_color, titleColor);
|
|
172
195
|
const iconColor = fallbackColor(
|
|
173
196
|
icon_color || selectedTheme.icon_color,
|
|
174
197
|
"#" + defaultTheme.icon_color,
|
|
@@ -186,11 +209,16 @@ const getCardColors = ({
|
|
|
186
209
|
border_color || defaultBorderColor,
|
|
187
210
|
"#" + defaultBorderColor,
|
|
188
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");
|
|
189
216
|
|
|
190
217
|
if (
|
|
191
218
|
typeof titleColor !== "string" ||
|
|
192
219
|
typeof textColor !== "string" ||
|
|
193
220
|
typeof ringColor !== "string" ||
|
|
221
|
+
typeof progBarBgColor !== "string" ||
|
|
194
222
|
typeof iconColor !== "string" ||
|
|
195
223
|
typeof borderColor !== "string"
|
|
196
224
|
) {
|
|
@@ -199,14 +227,135 @@ const getCardColors = ({
|
|
|
199
227
|
);
|
|
200
228
|
}
|
|
201
229
|
|
|
202
|
-
return {
|
|
230
|
+
return {
|
|
231
|
+
titleColor,
|
|
232
|
+
iconColor,
|
|
233
|
+
textColor,
|
|
234
|
+
bgColor,
|
|
235
|
+
borderColor,
|
|
236
|
+
ringColor,
|
|
237
|
+
progBarBgColor,
|
|
238
|
+
};
|
|
203
239
|
};
|
|
204
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
|
+
};
|
|
298
|
+
};
|
|
299
|
+
|
|
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
|
+
|
|
205
346
|
export {
|
|
206
|
-
fallbackColor,
|
|
207
347
|
getCardColors,
|
|
348
|
+
getLightDarkColors,
|
|
208
349
|
findInvalidColor,
|
|
350
|
+
findInvalidColorParam,
|
|
351
|
+
pickColorParams,
|
|
209
352
|
isValidGradient,
|
|
210
353
|
isBareHexColor,
|
|
211
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,
|
|
212
361
|
};
|
package/src/common/config.ts
CHANGED
|
@@ -9,7 +9,8 @@ interface Config {
|
|
|
9
9
|
whitelist: Array<string> | undefined;
|
|
10
10
|
gistWhitelist: Array<string> | undefined;
|
|
11
11
|
excludeRepositories: Array<string>;
|
|
12
|
-
|
|
12
|
+
/** Max pages of starred repos to fetch; `Infinity` means every page, `1` only the first. */
|
|
13
|
+
fetchMultiPageStars: number;
|
|
13
14
|
pats: Array<PersonalAccessToken>;
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -17,11 +18,19 @@ interface Config {
|
|
|
17
18
|
* @param value Comma-separated string.
|
|
18
19
|
* @returns Parsed string values.
|
|
19
20
|
*/
|
|
20
|
-
const parseCsv = (value: string | undefined): Array<string> | undefined =>
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const parseCsv = (value: string | undefined): Array<string> | undefined =>
|
|
22
|
+
value ? value.split(",") : undefined;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param value Raw `FETCH_MULTI_PAGE_STARS` value.
|
|
26
|
+
* @returns Page limit: `"true"` means every page, a positive number caps the pages, anything else means one.
|
|
27
|
+
*/
|
|
28
|
+
const parseFetchMultiPageStars = (value: string | undefined): number => {
|
|
29
|
+
if (value === "true") {
|
|
30
|
+
return Infinity;
|
|
23
31
|
}
|
|
24
|
-
|
|
32
|
+
const limit = Number(value);
|
|
33
|
+
return limit > 0 ? limit : 1;
|
|
25
34
|
};
|
|
26
35
|
|
|
27
36
|
/**
|
|
@@ -45,37 +54,21 @@ const getDefaultEnv = (): Env => {
|
|
|
45
54
|
return processEnv ?? {};
|
|
46
55
|
};
|
|
47
56
|
|
|
48
|
-
/**
|
|
49
|
-
* @param config (Partial) config values to normalize.
|
|
50
|
-
* @returns Normalized config object with defaults applied.
|
|
51
|
-
*/
|
|
52
|
-
const normalizeConfig = (config: Partial<Config> = {}): Config => {
|
|
53
|
-
return {
|
|
54
|
-
whitelist: config.whitelist,
|
|
55
|
-
gistWhitelist: config.gistWhitelist,
|
|
56
|
-
excludeRepositories: config.excludeRepositories ?? [],
|
|
57
|
-
fetchMultiPageStars: config.fetchMultiPageStars,
|
|
58
|
-
pats: config.pats ?? [],
|
|
59
|
-
};
|
|
60
|
-
};
|
|
61
|
-
|
|
62
57
|
let currentConfig: Config;
|
|
63
58
|
|
|
64
59
|
/**
|
|
65
60
|
* @param env Environment variables used to build the runtime config.
|
|
66
61
|
*/
|
|
67
62
|
export const loadConfigFromEnv = (env: Env = getDefaultEnv()): void => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
excludeRepositories,
|
|
76
|
-
fetchMultiPageStars: env["FETCH_MULTI_PAGE_STARS"],
|
|
63
|
+
currentConfig = {
|
|
64
|
+
whitelist: parseCsv(env["WHITELIST"]),
|
|
65
|
+
gistWhitelist: parseCsv(env["GIST_WHITELIST"]),
|
|
66
|
+
excludeRepositories: parseCsv(env["EXCLUDE_REPO"]) ?? [],
|
|
67
|
+
fetchMultiPageStars: parseFetchMultiPageStars(
|
|
68
|
+
env["FETCH_MULTI_PAGE_STARS"],
|
|
69
|
+
),
|
|
77
70
|
pats: parsePATsFromEnv(env),
|
|
78
|
-
}
|
|
71
|
+
};
|
|
79
72
|
};
|
|
80
73
|
|
|
81
74
|
loadConfigFromEnv();
|
package/src/common/constants.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { RepositoryAffiliation } from "../graphql/generated/common.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Valid owner affiliations for GitHub API queries.
|
|
3
5
|
*/
|
|
4
|
-
const OWNER_AFFILIATIONS: Array<
|
|
6
|
+
const OWNER_AFFILIATIONS: Array<RepositoryAffiliation> = [
|
|
5
7
|
"OWNER",
|
|
6
8
|
"COLLABORATOR",
|
|
7
9
|
"ORGANIZATION_MEMBER",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** A span between two dates, as GitHub's range arguments take it. */
|
|
2
|
+
interface GitHubDateRange {
|
|
3
|
+
/** Start of the range, inclusive. */
|
|
4
|
+
from: Date;
|
|
5
|
+
/** End of the range, inclusive. */
|
|
6
|
+
to: Date;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Format a date as a GitHub `DateTime` scalar.
|
|
11
|
+
* Seconds precision, no milliseconds.
|
|
12
|
+
*
|
|
13
|
+
* @param date Date to format.
|
|
14
|
+
* @returns e.g. `2024-01-01T00:00:00Z`.
|
|
15
|
+
*/
|
|
16
|
+
const toGitHubDateTime = (date: Date): string =>
|
|
17
|
+
`${date.toISOString().slice(0, 19)}Z`;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The full UTC span of a calendar year, both ends inclusive.
|
|
21
|
+
*
|
|
22
|
+
* The end matters to callers that would otherwise leave a range open:
|
|
23
|
+
* GitHub's `contributionsCollection` defaults `to` to one year after `from`,
|
|
24
|
+
* which pulls the next 1st of January into the year.
|
|
25
|
+
*
|
|
26
|
+
* @param year Four-digit year.
|
|
27
|
+
* @returns The range covering it.
|
|
28
|
+
*/
|
|
29
|
+
const getGitHubYearRange = (year: number): GitHubDateRange => ({
|
|
30
|
+
from: new Date(Date.UTC(year, 0, 1)),
|
|
31
|
+
to: new Date(Date.UTC(year, 11, 31, 23, 59, 59)),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get diff in minutes between two dates.
|
|
36
|
+
*
|
|
37
|
+
* @param d1 First date.
|
|
38
|
+
* @param d2 Second date.
|
|
39
|
+
* @returns Number of minutes between the two dates.
|
|
40
|
+
*/
|
|
41
|
+
const dateDiff = (d1: Date, d2: Date): number =>
|
|
42
|
+
Math.round((d1.getTime() - d2.getTime()) / (1000 * 60));
|
|
43
|
+
|
|
44
|
+
export { dateDiff, getGitHubYearRange, toGitHubDateTime };
|
|
45
|
+
export type { GitHubDateRange };
|