@stats-forge/github-stats-forge-core 0.1.0 → 0.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/contributed-to.d.ts +23 -0
- package/build/api/contributed-to.d.ts.map +1 -0
- package/build/api/contributed-to.js +43 -0
- package/build/api/gist.d.ts +8 -15
- package/build/api/gist.d.ts.map +1 -1
- package/build/api/gist.js +14 -29
- package/build/api/handler.d.ts +16 -0
- package/build/api/handler.d.ts.map +1 -0
- package/build/api/handler.js +29 -0
- package/build/api/index.d.ts +1 -0
- package/build/api/index.d.ts.map +1 -1
- package/build/api/index.js +1 -0
- package/build/api/params.d.ts +11 -5
- package/build/api/params.d.ts.map +1 -1
- package/build/api/params.js +26 -6
- package/build/api/pin.d.ts +11 -15
- package/build/api/pin.d.ts.map +1 -1
- package/build/api/pin.js +34 -46
- package/build/api/stats.d.ts +15 -22
- package/build/api/stats.d.ts.map +1 -1
- package/build/api/stats.js +59 -72
- package/build/api/top-langs.d.ts +11 -22
- package/build/api/top-langs.d.ts.map +1 -1
- package/build/api/top-langs.js +34 -51
- package/build/api/wakatime.d.ts +10 -24
- package/build/api/wakatime.d.ts.map +1 -1
- package/build/api/wakatime.js +27 -47
- package/build/cards/contributed-to.d.ts +19 -0
- package/build/cards/contributed-to.d.ts.map +1 -0
- package/build/cards/contributed-to.js +242 -0
- package/build/cards/gist.d.ts +1 -0
- package/build/cards/gist.d.ts.map +1 -1
- package/build/cards/gist.js +13 -9
- package/build/cards/repo.d.ts +12 -1
- package/build/cards/repo.d.ts.map +1 -1
- package/build/cards/repo.js +40 -18
- package/build/cards/stats.d.ts +15 -3
- package/build/cards/stats.d.ts.map +1 -1
- package/build/cards/stats.js +91 -76
- package/build/cards/top-languages.d.ts +13 -2
- package/build/cards/top-languages.d.ts.map +1 -1
- package/build/cards/top-languages.js +26 -9
- package/build/cards/wakatime.d.ts +13 -2
- package/build/cards/wakatime.d.ts.map +1 -1
- package/build/cards/wakatime.js +22 -9
- package/build/common/Card.d.ts +9 -0
- package/build/common/Card.d.ts.map +1 -1
- package/build/common/Card.js +44 -9
- package/build/common/I18n.d.ts +1 -1
- package/build/common/I18n.d.ts.map +1 -1
- package/build/common/I18n.js +4 -2
- package/build/common/brand.d.ts +76 -0
- package/build/common/brand.d.ts.map +1 -0
- package/build/common/brand.js +73 -0
- package/build/common/constants.d.ts +3 -1
- package/build/common/constants.d.ts.map +1 -1
- package/build/common/constants.js +3 -1
- package/build/common/date.d.ts +50 -3
- package/build/common/date.d.ts.map +1 -1
- package/build/common/date.js +104 -2
- package/build/common/icons.d.ts +4 -2
- package/build/common/icons.d.ts.map +1 -1
- package/build/common/icons.js +10 -5
- package/build/common/ops.d.ts +8 -3
- package/build/common/ops.d.ts.map +1 -1
- package/build/common/ops.js +8 -14
- package/build/common/render.d.ts +7 -1
- package/build/common/render.d.ts.map +1 -1
- package/build/common/render.js +60 -58
- package/build/fetchers/contributed-to.d.ts +62 -0
- package/build/fetchers/contributed-to.d.ts.map +1 -0
- package/build/fetchers/contributed-to.js +184 -0
- package/build/fetchers/graphql-error.d.ts +13 -0
- package/build/fetchers/graphql-error.d.ts.map +1 -0
- package/build/fetchers/graphql-error.js +29 -0
- package/build/fetchers/index.d.ts +2 -1
- package/build/fetchers/index.d.ts.map +1 -1
- package/build/fetchers/index.js +1 -0
- package/build/fetchers/stats.d.ts +14 -8
- package/build/fetchers/stats.d.ts.map +1 -1
- package/build/fetchers/stats.js +72 -195
- package/build/fetchers/top-languages.d.ts.map +1 -1
- package/build/fetchers/top-languages.js +3 -18
- package/build/fetchers/types.d.ts +24 -0
- package/build/fetchers/types.d.ts.map +1 -1
- package/build/graphql/contributionsCollection.d.ts +9 -0
- package/build/graphql/contributionsCollection.d.ts.map +1 -0
- package/build/graphql/contributionsCollection.js +12 -0
- package/build/graphql/contributionsDocument.d.ts +5 -5
- package/build/graphql/contributionsDocument.d.ts.map +1 -1
- package/build/graphql/contributionsDocument.js +8 -15
- package/build/graphql/generated/contributed-to.d.ts +22 -0
- package/build/graphql/generated/contributed-to.d.ts.map +1 -0
- package/build/graphql/generated/contributed-to.js +11 -0
- package/build/graphql/generated/stats.d.ts +13 -1
- package/build/graphql/generated/stats.d.ts.map +1 -1
- package/build/graphql/generated/stats.js +2 -2
- package/build/graphql/graphqlDocument.d.ts +1 -1
- package/build/graphql/reposContributedToDocument.d.ts.map +1 -1
- package/build/graphql/reposContributedToDocument.js +11 -5
- package/build/translations.d.ts +64 -1
- package/build/translations.d.ts.map +1 -1
- package/build/translations.js +65 -1
- package/package.json +1 -1
- package/src/api/contributed-to.ts +84 -0
- package/src/api/gist.ts +20 -37
- package/src/api/handler.ts +42 -0
- package/src/api/index.ts +1 -0
- package/src/api/params.ts +48 -9
- package/src/api/pin.ts +33 -45
- package/src/api/stats.ts +78 -89
- package/src/api/top-langs.ts +34 -56
- package/src/api/wakatime.ts +33 -54
- package/src/cards/contributed-to.ts +373 -0
- package/src/cards/gist.ts +17 -8
- package/src/cards/repo.ts +44 -20
- package/src/cards/stats.ts +119 -90
- package/src/cards/top-languages.ts +27 -18
- package/src/cards/wakatime.ts +23 -13
- package/src/common/Card.ts +56 -11
- package/src/common/I18n.ts +4 -2
- package/src/common/brand.ts +109 -0
- package/src/common/constants.ts +4 -1
- package/src/common/date.ts +135 -3
- package/src/common/icons.ts +10 -5
- package/src/common/ops.ts +10 -19
- package/src/common/render.ts +65 -62
- package/src/fetchers/contributed-to.ts +271 -0
- package/src/fetchers/graphql-error.ts +36 -0
- package/src/fetchers/index.ts +3 -0
- package/src/fetchers/stats.ts +123 -274
- package/src/fetchers/top-languages.ts +5 -19
- package/src/fetchers/types.ts +26 -0
- package/src/graphql/contributionsCollection.ts +18 -0
- package/src/graphql/contributionsDocument.ts +11 -18
- package/src/graphql/generated/contributed-to.ts +25 -0
- package/src/graphql/generated/stats.ts +19 -6
- package/src/graphql/graphqlDocument.ts +1 -1
- package/src/graphql/queries/contributed-to.graphql +8 -0
- package/src/graphql/queries/stats.graphql +14 -2
- package/src/graphql/reposContributedToDocument.ts +11 -9
- package/src/translations.ts +77 -0
package/src/cards/repo.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CARD_ICON, CARD_WIDTH, FONT_SIZE, FONT_WEIGHT, font } from '../common/brand.ts';
|
|
1
2
|
import { Card } from '../common/Card.ts';
|
|
2
3
|
import { getLightDarkColors } from '../common/color.ts';
|
|
3
4
|
import { kFormatter } from '../common/fmt.ts';
|
|
@@ -5,6 +6,7 @@ import { I18n } from '../common/I18n.ts';
|
|
|
5
6
|
import { icons } from '../common/icons.ts';
|
|
6
7
|
import { buildSearchFilter, clampValue, parseEmojis } from '../common/ops.ts';
|
|
7
8
|
import {
|
|
9
|
+
NUMBER_FORMATS,
|
|
8
10
|
countWrappedLines,
|
|
9
11
|
wrapTextMultiline,
|
|
10
12
|
createLanguageNode,
|
|
@@ -23,12 +25,22 @@ import { repoCardLocales } from '../translations.ts';
|
|
|
23
25
|
import type { CardOptions, CommonCardOptions } from './options.ts';
|
|
24
26
|
|
|
25
27
|
const ICON_SIZE = 16;
|
|
26
|
-
const CARD_DEFAULT_WIDTH =
|
|
28
|
+
const CARD_DEFAULT_WIDTH = CARD_WIDTH.standard;
|
|
27
29
|
const X_OFFSET = 25;
|
|
28
|
-
const DESCRIPTION_FONT_SIZE =
|
|
30
|
+
const DESCRIPTION_FONT_SIZE = FONT_SIZE.meta;
|
|
29
31
|
const DESCRIPTION_LINE_HEIGHT_PX = 16;
|
|
30
32
|
const DESCRIPTION_MAX_LINES = 3;
|
|
31
33
|
|
|
34
|
+
/** Stats the card draws only when `show` names them. */
|
|
35
|
+
const REPO_SHOW_STATS = [
|
|
36
|
+
'prs_authored',
|
|
37
|
+
'prs_commented',
|
|
38
|
+
'prs_reviewed',
|
|
39
|
+
'issues_authored',
|
|
40
|
+
'issues_commented',
|
|
41
|
+
] as const;
|
|
42
|
+
type RepoShowStat = (typeof REPO_SHOW_STATS)[number];
|
|
43
|
+
|
|
32
44
|
interface RepoCardOptions extends CommonCardOptions {
|
|
33
45
|
locale: string;
|
|
34
46
|
show_owner: boolean;
|
|
@@ -85,10 +97,7 @@ const getBadgeSVG = (label: string, xOffset = 0): MarkupElement => {
|
|
|
85
97
|
*
|
|
86
98
|
* @returns Repository card SVG object.
|
|
87
99
|
*/
|
|
88
|
-
const
|
|
89
|
-
repo: RepositoryData,
|
|
90
|
-
options: CardOptions<RepoCardOptions> = {},
|
|
91
|
-
): string => {
|
|
100
|
+
const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions> = {}): string => {
|
|
92
101
|
const {
|
|
93
102
|
name,
|
|
94
103
|
nameWithOwner,
|
|
@@ -133,10 +142,13 @@ const renderRepoCard = (
|
|
|
133
142
|
translations: repoCardLocales,
|
|
134
143
|
});
|
|
135
144
|
|
|
145
|
+
// Typed against the exported list, so a stat drawn here cannot be missing from it.
|
|
146
|
+
const shows = (stat: RepoShowStat): boolean => show.includes(stat);
|
|
147
|
+
|
|
136
148
|
const repoFilter = encodeURIComponent(buildSearchFilter([nameWithOwner], []));
|
|
137
149
|
const encodedUsername = encodeURIComponent(username ?? '');
|
|
138
150
|
const STATS: Record<string, RepoStatItem> = {};
|
|
139
|
-
if (
|
|
151
|
+
if (shows('prs_authored')) {
|
|
140
152
|
STATS['prs_authored'] = {
|
|
141
153
|
icon: icons.prs,
|
|
142
154
|
label: i18n.t('repocard.prs-authored'),
|
|
@@ -145,7 +157,7 @@ const renderRepoCard = (
|
|
|
145
157
|
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=pullrequests`,
|
|
146
158
|
};
|
|
147
159
|
}
|
|
148
|
-
if (
|
|
160
|
+
if (shows('prs_commented')) {
|
|
149
161
|
STATS['prs_commented'] = {
|
|
150
162
|
icon: icons.comments,
|
|
151
163
|
label: i18n.t('repocard.prs-commented'),
|
|
@@ -154,7 +166,7 @@ const renderRepoCard = (
|
|
|
154
166
|
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
155
167
|
};
|
|
156
168
|
}
|
|
157
|
-
if (
|
|
169
|
+
if (shows('prs_reviewed')) {
|
|
158
170
|
STATS['prs_reviewed'] = {
|
|
159
171
|
icon: icons.reviews,
|
|
160
172
|
label: i18n.t('repocard.prs-reviewed'),
|
|
@@ -163,7 +175,7 @@ const renderRepoCard = (
|
|
|
163
175
|
link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
164
176
|
};
|
|
165
177
|
}
|
|
166
|
-
if (
|
|
178
|
+
if (shows('issues_authored')) {
|
|
167
179
|
STATS['issues_authored'] = {
|
|
168
180
|
icon: icons.issues,
|
|
169
181
|
label: i18n.t('repocard.issues-authored'),
|
|
@@ -172,7 +184,7 @@ const renderRepoCard = (
|
|
|
172
184
|
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=issues`,
|
|
173
185
|
};
|
|
174
186
|
}
|
|
175
|
-
if (
|
|
187
|
+
if (shows('issues_commented')) {
|
|
176
188
|
STATS['issues_commented'] = {
|
|
177
189
|
icon: icons.discussions_started,
|
|
178
190
|
label: i18n.t('repocard.issues-commented'),
|
|
@@ -203,9 +215,9 @@ const renderRepoCard = (
|
|
|
203
215
|
const extraLHeight = Number.parseInt(String(line_height), 10);
|
|
204
216
|
const lineHeight = 10;
|
|
205
217
|
const header = show_owner ? nameWithOwner : name;
|
|
206
|
-
const langName = primaryLanguage?.name || '
|
|
218
|
+
const langName = primaryLanguage?.name || i18n.t('repocard.unspecified-language');
|
|
207
219
|
const langColor = primaryLanguage?.color || '#333';
|
|
208
|
-
const desc = parseEmojis(description || '
|
|
220
|
+
const desc = parseEmojis(description || i18n.t('repocard.no-description'));
|
|
209
221
|
const descriptionBoxWidth = card_width - 2 * X_OFFSET;
|
|
210
222
|
|
|
211
223
|
let descriptionLinesCount: number;
|
|
@@ -294,7 +306,7 @@ const renderRepoCard = (
|
|
|
294
306
|
|
|
295
307
|
const card = new Card({
|
|
296
308
|
defaultTitle: header.length > 35 ? `${header.slice(0, 35)}...` : header,
|
|
297
|
-
titlePrefixIcon:
|
|
309
|
+
titlePrefixIcon: CARD_ICON.repo,
|
|
298
310
|
width: card_width,
|
|
299
311
|
height,
|
|
300
312
|
border_radius,
|
|
@@ -307,21 +319,21 @@ const renderRepoCard = (
|
|
|
307
319
|
card.setCSS({
|
|
308
320
|
light: ({ textColor, iconColor }) => [
|
|
309
321
|
rule('.description', {
|
|
310
|
-
font:
|
|
322
|
+
font: font('regular', 'meta'),
|
|
311
323
|
fill: textColor,
|
|
312
324
|
...(browser_rendering ? wrappedTextStyles(textColor) : {}),
|
|
313
325
|
}),
|
|
314
|
-
rule('.gray', { font:
|
|
315
|
-
rule('.badge', { font:
|
|
326
|
+
rule('.gray', { font: font('regular', 'small'), fill: textColor }),
|
|
327
|
+
rule('.badge', { font: font('semibold', 'micro') }),
|
|
316
328
|
rule('.badge rect', { opacity: 0.2, stroke: textColor }),
|
|
317
329
|
rule('.badge text', { fill: textColor }),
|
|
318
|
-
rule('.stat', { font:
|
|
330
|
+
rule('.stat', { font: font('regular', 'small'), fill: textColor }),
|
|
319
331
|
rule('.stagger', {
|
|
320
332
|
opacity: 0,
|
|
321
333
|
animation: 'fadeInAnimation 0.3s ease-in-out forwards',
|
|
322
334
|
}),
|
|
323
|
-
rule('.not_bold', { 'font-weight':
|
|
324
|
-
rule('.bold', { 'font-weight':
|
|
335
|
+
rule('.not_bold', { 'font-weight': FONT_WEIGHT.regular }),
|
|
336
|
+
rule('.bold', { 'font-weight': FONT_WEIGHT.semibold }),
|
|
325
337
|
rule('.icon', { fill: iconColor, display: 'block' }),
|
|
326
338
|
],
|
|
327
339
|
dark: ({ textColor, iconColor }) => [
|
|
@@ -363,4 +375,16 @@ const renderRepoCard = (
|
|
|
363
375
|
]);
|
|
364
376
|
};
|
|
365
377
|
|
|
378
|
+
/**
|
|
379
|
+
* The card, and the values each of its options accepts, keyed by the option's own name.
|
|
380
|
+
* They ride on the renderer so a list cannot be found without what draws it;
|
|
381
|
+
* the api handler forwards them onto its own export, which is what a UI reads.
|
|
382
|
+
*/
|
|
383
|
+
const renderRepoCard = Object.assign(renderCard, {
|
|
384
|
+
OPTIONS: {
|
|
385
|
+
show: REPO_SHOW_STATS,
|
|
386
|
+
number_format: NUMBER_FORMATS,
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
|
|
366
390
|
export { renderRepoCard };
|
package/src/cards/stats.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CARD_ICON,
|
|
3
|
+
CARD_WIDTH,
|
|
4
|
+
FONT_SIZE,
|
|
5
|
+
FONT_WEIGHT,
|
|
6
|
+
firefoxFontSize,
|
|
7
|
+
font,
|
|
8
|
+
} from '../common/brand.ts';
|
|
1
9
|
import { Card } from '../common/Card.ts';
|
|
2
10
|
import { getLightDarkColors } from '../common/color.ts';
|
|
3
11
|
import type { CardColors } from '../common/color.ts';
|
|
12
|
+
import type { GitHubDateRange } from '../common/date.ts';
|
|
13
|
+
import { formatRange } from '../common/date.ts';
|
|
4
14
|
import { CardError } from '../common/error.ts';
|
|
5
15
|
import { I18n } from '../common/I18n.ts';
|
|
6
16
|
import { icons, rankIcon } from '../common/icons.ts';
|
|
7
17
|
import { buildSearchFilter, clampValue } from '../common/ops.ts';
|
|
8
|
-
import { createTextNode, flexLayout, measureText } from '../common/render.ts';
|
|
18
|
+
import { NUMBER_FORMATS, createTextNode, flexLayout, measureText } from '../common/render.ts';
|
|
9
19
|
import type { StatsData } from '../fetchers/types.ts';
|
|
10
20
|
import type { Child, CssChild } from '../markup/index.ts';
|
|
11
21
|
import { atRule, cssComment, el, rule } from '../markup/index.ts';
|
|
@@ -14,19 +24,19 @@ import { statCardLocales, wakatimeCardLocales } from '../translations.ts';
|
|
|
14
24
|
import type { CardOptions, CommonCardOptions } from './options.ts';
|
|
15
25
|
|
|
16
26
|
const CARD_MIN_WIDTH = 287;
|
|
17
|
-
const CARD_DEFAULT_WIDTH =
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const RANK_ONLY_CARD_MIN_WIDTH = 290;
|
|
21
|
-
const RANK_ONLY_CARD_DEFAULT_WIDTH = 290;
|
|
27
|
+
const CARD_DEFAULT_WIDTH = CARD_WIDTH.compact;
|
|
28
|
+
const RANK_CARD_DEFAULT_WIDTH = CARD_WIDTH.wide;
|
|
29
|
+
const RANK_ONLY_CARD_DEFAULT_WIDTH = CARD_WIDTH.compact;
|
|
22
30
|
|
|
23
|
-
const STAT_FONT_SIZE =
|
|
31
|
+
const STAT_FONT_SIZE = FONT_SIZE.body;
|
|
24
32
|
/** Padding the card keeps at its edges; matches `Card`'s own `paddingX`. */
|
|
25
33
|
const CARD_PADDING_X = 25;
|
|
26
34
|
/** How far a stat row is translated into the card; see `createTextNode`. */
|
|
27
35
|
const STAT_ROW_X = 25;
|
|
28
36
|
/** Room the rank ring needs at the right edge, so a value never runs into it. */
|
|
29
37
|
const RANK_GUTTER = 120;
|
|
38
|
+
/** The ring's own `cx`, which its group's translation has to make up for. */
|
|
39
|
+
const RANK_CIRCLE_CX_OFFSET = 10;
|
|
30
40
|
/** `createTextNode`'s own label offset, which it applies only when icons are shown. */
|
|
31
41
|
const LABEL_X_OFFSET = 25;
|
|
32
42
|
/** Smallest gap kept between the longest label and its value. */
|
|
@@ -36,6 +46,30 @@ const LABEL_VALUE_GAP = 16;
|
|
|
36
46
|
const RANK_ICONS = ['default', 'github', 'percentile'] as const;
|
|
37
47
|
type RankIcon = (typeof RANK_ICONS)[number];
|
|
38
48
|
|
|
49
|
+
/** Stats the card draws only when `show` names them. */
|
|
50
|
+
const SHOW_STATS = [
|
|
51
|
+
'contributions',
|
|
52
|
+
'prs_merged',
|
|
53
|
+
'prs_merged_percentage',
|
|
54
|
+
'reviews',
|
|
55
|
+
'discussions_started',
|
|
56
|
+
'discussions_answered',
|
|
57
|
+
'prs_authored',
|
|
58
|
+
'prs_commented',
|
|
59
|
+
'prs_reviewed',
|
|
60
|
+
'issues_authored',
|
|
61
|
+
'issues_commented',
|
|
62
|
+
'all_time_contribs',
|
|
63
|
+
] as const;
|
|
64
|
+
type ShowStat = (typeof SHOW_STATS)[number];
|
|
65
|
+
|
|
66
|
+
/** Stats the card always draws, and so the ones `hide` has anything to remove. */
|
|
67
|
+
const HIDE_STATS = ['stars', 'commits', 'prs', 'issues', 'contribs'] as const;
|
|
68
|
+
type HideStat = (typeof HIDE_STATS)[number];
|
|
69
|
+
|
|
70
|
+
/** Every stat the card can draw: the ones it always draws, plus the ones `show` adds. */
|
|
71
|
+
type StatId = HideStat | ShowStat;
|
|
72
|
+
|
|
39
73
|
interface StatCardOptions extends CommonCardOptions {
|
|
40
74
|
locale: string;
|
|
41
75
|
hide: Array<string>;
|
|
@@ -44,7 +78,6 @@ interface StatCardOptions extends CommonCardOptions {
|
|
|
44
78
|
card_width: number;
|
|
45
79
|
hide_rank: boolean;
|
|
46
80
|
include_all_commits: boolean;
|
|
47
|
-
commits_year: number;
|
|
48
81
|
line_height: number | string;
|
|
49
82
|
custom_title: string;
|
|
50
83
|
disable_animations: boolean;
|
|
@@ -141,26 +174,22 @@ const getStyles = ({
|
|
|
141
174
|
progress: number;
|
|
142
175
|
}): Array<CssChild> => [
|
|
143
176
|
rule('.stat', {
|
|
144
|
-
font:
|
|
177
|
+
font: font('regular', 'body'),
|
|
145
178
|
fill: textColor,
|
|
146
179
|
'font-variant-numeric': 'tabular-nums',
|
|
147
180
|
}),
|
|
148
|
-
|
|
149
|
-
'@supports(-moz-appearance: auto)',
|
|
150
|
-
cssComment('Selector detects Firefox'),
|
|
151
|
-
rule('.stat', { 'font-size': '12px' }),
|
|
152
|
-
),
|
|
181
|
+
firefoxFontSize(['.stat'], 'small'),
|
|
153
182
|
rule('.stagger', { opacity: 0, animation: 'fadeInAnimation 0.3s ease-in-out forwards' }),
|
|
154
183
|
rule('.rank-text', {
|
|
155
|
-
font:
|
|
184
|
+
font: font('bold', 'display'),
|
|
156
185
|
fill: textColor,
|
|
157
186
|
animation: 'scaleInAnimation 0.3s ease-in-out forwards',
|
|
158
187
|
}),
|
|
159
|
-
rule('.rank-percentile-header', { 'font-size':
|
|
160
|
-
rule('.rank-percentile-text', { 'font-size':
|
|
188
|
+
rule('.rank-percentile-header', { 'font-size': `${String(FONT_SIZE.body)}px` }),
|
|
189
|
+
rule('.rank-percentile-text', { 'font-size': `${String(FONT_SIZE.lead)}px` }),
|
|
161
190
|
cssComment('Labels recede so that the values read first.'),
|
|
162
|
-
rule('.not_bold', { 'font-weight':
|
|
163
|
-
rule('.bold', { 'font-weight':
|
|
191
|
+
rule('.not_bold', { 'font-weight': FONT_WEIGHT.regular, opacity: 0.75 }),
|
|
192
|
+
rule('.bold', { 'font-weight': FONT_WEIGHT.semibold }),
|
|
164
193
|
rule('.icon', {
|
|
165
194
|
fill: iconColor,
|
|
166
195
|
opacity: 0.75,
|
|
@@ -187,19 +216,17 @@ const getStyles = ({
|
|
|
187
216
|
];
|
|
188
217
|
|
|
189
218
|
/**
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
* @returns The label corresponding to the options.
|
|
219
|
+
* @returns What the commit count covers, for the stat's label.
|
|
193
220
|
*/
|
|
194
|
-
const
|
|
221
|
+
const getTotalCommitsRangeLabel = (
|
|
195
222
|
include_all_commits: boolean,
|
|
196
|
-
|
|
223
|
+
commitsRange: GitHubDateRange | undefined,
|
|
197
224
|
i18n: I18n,
|
|
198
225
|
): string =>
|
|
199
226
|
include_all_commits
|
|
200
227
|
? ''
|
|
201
|
-
:
|
|
202
|
-
? ` (${
|
|
228
|
+
: commitsRange
|
|
229
|
+
? ` (${formatRange(commitsRange)})`
|
|
203
230
|
: ` (${i18n.t('wakatimecard.lastyear')})`;
|
|
204
231
|
|
|
205
232
|
/**
|
|
@@ -207,7 +234,7 @@ const getTotalCommitsYearLabel = (
|
|
|
207
234
|
*
|
|
208
235
|
* @returns The stats card SVG object.
|
|
209
236
|
*/
|
|
210
|
-
const
|
|
237
|
+
const renderCard = (
|
|
211
238
|
stats: StatsData,
|
|
212
239
|
options: CardOptions<StatCardOptions> = {},
|
|
213
240
|
username?: string,
|
|
@@ -218,6 +245,7 @@ const renderStatsCard = (
|
|
|
218
245
|
name,
|
|
219
246
|
totalStars,
|
|
220
247
|
totalCommits,
|
|
248
|
+
commitsRange,
|
|
221
249
|
totalIssues,
|
|
222
250
|
totalPRs,
|
|
223
251
|
totalPRsMerged,
|
|
@@ -243,7 +271,6 @@ const renderStatsCard = (
|
|
|
243
271
|
card_width,
|
|
244
272
|
hide_rank = false,
|
|
245
273
|
include_all_commits = false,
|
|
246
|
-
commits_year,
|
|
247
274
|
line_height = 25,
|
|
248
275
|
text_bold = true,
|
|
249
276
|
custom_title,
|
|
@@ -260,6 +287,9 @@ const renderStatsCard = (
|
|
|
260
287
|
|
|
261
288
|
const { lightColors, darkColors } = getLightDarkColors(options);
|
|
262
289
|
|
|
290
|
+
// Typed against the exported list, so a stat drawn here cannot be missing from it.
|
|
291
|
+
const shows = (stat: ShowStat): boolean => show.includes(stat);
|
|
292
|
+
|
|
263
293
|
const apostrophe = /s$/i.test(name.trim()) ? '' : 's';
|
|
264
294
|
const i18n = new I18n({
|
|
265
295
|
locale,
|
|
@@ -270,7 +300,7 @@ const renderStatsCard = (
|
|
|
270
300
|
});
|
|
271
301
|
|
|
272
302
|
// Meta data for creating text nodes with createTextNode function
|
|
273
|
-
const STATS: Record<
|
|
303
|
+
const STATS: Partial<Record<StatId, StatItem>> = {
|
|
274
304
|
stars: {
|
|
275
305
|
icon: icons.star,
|
|
276
306
|
label: i18n.t('statcard.totalstars'),
|
|
@@ -279,7 +309,7 @@ const renderStatsCard = (
|
|
|
279
309
|
},
|
|
280
310
|
};
|
|
281
311
|
|
|
282
|
-
if (
|
|
312
|
+
if (shows('contributions')) {
|
|
283
313
|
STATS['contributions'] = {
|
|
284
314
|
icon: icons.contributions,
|
|
285
315
|
label: i18n.t('statcard.contributions'),
|
|
@@ -290,9 +320,9 @@ const renderStatsCard = (
|
|
|
290
320
|
|
|
291
321
|
STATS['commits'] = {
|
|
292
322
|
icon: icons.commits,
|
|
293
|
-
label: `${i18n.t('statcard.commits')}${
|
|
323
|
+
label: `${i18n.t('statcard.commits')}${getTotalCommitsRangeLabel(
|
|
294
324
|
include_all_commits,
|
|
295
|
-
|
|
325
|
+
commitsRange,
|
|
296
326
|
i18n,
|
|
297
327
|
)}`,
|
|
298
328
|
value: totalCommits,
|
|
@@ -305,7 +335,7 @@ const renderStatsCard = (
|
|
|
305
335
|
id: 'prs',
|
|
306
336
|
};
|
|
307
337
|
|
|
308
|
-
if (
|
|
338
|
+
if (shows('prs_merged')) {
|
|
309
339
|
STATS['prs_merged'] = {
|
|
310
340
|
icon: icons.prs_merged,
|
|
311
341
|
label: i18n.t('statcard.prs-merged'),
|
|
@@ -314,7 +344,7 @@ const renderStatsCard = (
|
|
|
314
344
|
};
|
|
315
345
|
}
|
|
316
346
|
|
|
317
|
-
if (
|
|
347
|
+
if (shows('prs_merged_percentage')) {
|
|
318
348
|
STATS['prs_merged_percentage'] = {
|
|
319
349
|
icon: icons.prs_merged_percentage,
|
|
320
350
|
label: i18n.t('statcard.prs-merged-percentage'),
|
|
@@ -328,7 +358,7 @@ const renderStatsCard = (
|
|
|
328
358
|
};
|
|
329
359
|
}
|
|
330
360
|
|
|
331
|
-
if (
|
|
361
|
+
if (shows('reviews')) {
|
|
332
362
|
STATS['reviews'] = {
|
|
333
363
|
icon: icons.reviews,
|
|
334
364
|
label: i18n.t('statcard.reviews'),
|
|
@@ -344,7 +374,7 @@ const renderStatsCard = (
|
|
|
344
374
|
id: 'issues',
|
|
345
375
|
};
|
|
346
376
|
|
|
347
|
-
if (
|
|
377
|
+
if (shows('discussions_started')) {
|
|
348
378
|
STATS['discussions_started'] = {
|
|
349
379
|
icon: icons.discussions_started,
|
|
350
380
|
label: i18n.t('statcard.discussions-started'),
|
|
@@ -352,7 +382,7 @@ const renderStatsCard = (
|
|
|
352
382
|
id: 'discussions_started',
|
|
353
383
|
};
|
|
354
384
|
}
|
|
355
|
-
if (
|
|
385
|
+
if (shows('discussions_answered')) {
|
|
356
386
|
STATS['discussions_answered'] = {
|
|
357
387
|
icon: icons.discussions_answered,
|
|
358
388
|
label: i18n.t('statcard.discussions-answered'),
|
|
@@ -363,7 +393,7 @@ const renderStatsCard = (
|
|
|
363
393
|
|
|
364
394
|
const repoFilter = encodeURIComponent(buildSearchFilter(repo, owner));
|
|
365
395
|
const encodedUsername = encodeURIComponent(username ?? '');
|
|
366
|
-
if (
|
|
396
|
+
if (shows('prs_authored')) {
|
|
367
397
|
STATS['prs_authored'] = {
|
|
368
398
|
icon: icons.prs,
|
|
369
399
|
label: i18n.t('statcard.prs-authored'),
|
|
@@ -372,7 +402,7 @@ const renderStatsCard = (
|
|
|
372
402
|
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=pullrequests`,
|
|
373
403
|
};
|
|
374
404
|
}
|
|
375
|
-
if (
|
|
405
|
+
if (shows('prs_commented')) {
|
|
376
406
|
STATS['prs_commented'] = {
|
|
377
407
|
icon: icons.comments,
|
|
378
408
|
label: i18n.t('statcard.prs-commented'),
|
|
@@ -381,7 +411,7 @@ const renderStatsCard = (
|
|
|
381
411
|
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
382
412
|
};
|
|
383
413
|
}
|
|
384
|
-
if (
|
|
414
|
+
if (shows('prs_reviewed')) {
|
|
385
415
|
STATS['prs_reviewed'] = {
|
|
386
416
|
icon: icons.reviews,
|
|
387
417
|
label: i18n.t('statcard.prs-reviewed'),
|
|
@@ -390,7 +420,7 @@ const renderStatsCard = (
|
|
|
390
420
|
link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
391
421
|
};
|
|
392
422
|
}
|
|
393
|
-
if (
|
|
423
|
+
if (shows('issues_authored')) {
|
|
394
424
|
STATS['issues_authored'] = {
|
|
395
425
|
icon: icons.issues,
|
|
396
426
|
label: i18n.t('statcard.issues-authored'),
|
|
@@ -399,7 +429,7 @@ const renderStatsCard = (
|
|
|
399
429
|
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=issues`,
|
|
400
430
|
};
|
|
401
431
|
}
|
|
402
|
-
if (
|
|
432
|
+
if (shows('issues_commented')) {
|
|
403
433
|
STATS['issues_commented'] = {
|
|
404
434
|
icon: icons.discussions_started,
|
|
405
435
|
label: i18n.t('statcard.issues-commented'),
|
|
@@ -410,15 +440,15 @@ const renderStatsCard = (
|
|
|
410
440
|
}
|
|
411
441
|
|
|
412
442
|
STATS['contribs'] = {
|
|
413
|
-
icon: icons.
|
|
443
|
+
icon: icons.repo,
|
|
414
444
|
label: i18n.t('statcard.contribs'),
|
|
415
445
|
value: contributedTo,
|
|
416
446
|
id: 'contribs',
|
|
417
447
|
};
|
|
418
448
|
|
|
419
|
-
if (
|
|
449
|
+
if (shows('all_time_contribs')) {
|
|
420
450
|
STATS['all_time_contribs'] = {
|
|
421
|
-
icon: icons.
|
|
451
|
+
icon: icons.repo,
|
|
422
452
|
label: i18n.t('statcard.all-time-contribs'),
|
|
423
453
|
value: allTimeContributedTo,
|
|
424
454
|
id: 'all_time_contribs',
|
|
@@ -455,29 +485,24 @@ const renderStatsCard = (
|
|
|
455
485
|
(visibleStats.length > 0 ? i18n.t('statcard.title') : i18n.t('statcard.ranktitle')),
|
|
456
486
|
);
|
|
457
487
|
|
|
458
|
-
/*
|
|
459
|
-
When hide_rank=true, the minimum card width is 270 px + the title length and padding.
|
|
460
|
-
When hide_rank=false, the minimum card_width is 340 px + the icon width (if show_icons=true).
|
|
461
|
-
Numbers are picked by looking at existing dimensions on production.
|
|
462
|
-
*/
|
|
463
488
|
const iconWidth = show_icons && visibleStats.length > 0 ? 16 + /* padding */ 1 : 0;
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
489
|
+
// The icons ride inside the step's own slack, so a default card lands on the width grid;
|
|
490
|
+
// only the minimum, which has no slack, still has to make room for them.
|
|
491
|
+
const defaultCardWidth = hide_rank
|
|
492
|
+
? CARD_DEFAULT_WIDTH
|
|
493
|
+
: visibleStats.length > 0
|
|
494
|
+
? RANK_CARD_DEFAULT_WIDTH
|
|
495
|
+
: RANK_ONLY_CARD_DEFAULT_WIDTH;
|
|
496
|
+
// Only a card sized by its own title can outgrow its step: the ranked layouts are fixed,
|
|
497
|
+
// so their step is also their floor.
|
|
498
|
+
const fittedWidth = hide_rank
|
|
499
|
+
? Math.max(
|
|
500
|
+
defaultCardWidth,
|
|
501
|
+
clampValue(50 /* padding */ + calculateTextWidth() * 2, CARD_MIN_WIDTH, Infinity) +
|
|
502
|
+
iconWidth,
|
|
503
|
+
)
|
|
504
|
+
: defaultCardWidth;
|
|
505
|
+
const width = card_width ? (Number.isNaN(card_width) ? fittedWidth : card_width) : fittedWidth;
|
|
481
506
|
|
|
482
507
|
// A value ends at the card's inner edge, clear of the rank ring — or right after
|
|
483
508
|
// the longest label, on a card too narrow for that.
|
|
@@ -515,6 +540,7 @@ const renderStatsCard = (
|
|
|
515
540
|
const card = new Card({
|
|
516
541
|
customTitle: custom_title,
|
|
517
542
|
defaultTitle: visibleStats.length > 0 ? i18n.t('statcard.title') : i18n.t('statcard.ranktitle'),
|
|
543
|
+
titlePrefixIcon: CARD_ICON.stats,
|
|
518
544
|
width,
|
|
519
545
|
height,
|
|
520
546
|
border_radius,
|
|
@@ -533,26 +559,15 @@ const renderStatsCard = (
|
|
|
533
559
|
}
|
|
534
560
|
|
|
535
561
|
/**
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
* width > RANK_CARD_DEFAULT_WIDTH: The default right padding of 70 px will be used.
|
|
540
|
-
* width < RANK_CARD_DEFAULT_WIDTH: The left and right padding will be enlarged
|
|
541
|
-
* equally from a certain minimum at RANK_CARD_MIN_WIDTH.
|
|
562
|
+
* The ring sits centred in the same gutter the values are kept clear of, so the gap
|
|
563
|
+
* between them holds at every width; a rank-only card has no values, so it centres.
|
|
542
564
|
*
|
|
543
565
|
* @returns Rank circle translation value.
|
|
544
566
|
*/
|
|
545
|
-
const calculateRankXTranslation = (): number =>
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const xMaxExpansion = minXTranslation + (450 - minCardWidth) / 2;
|
|
550
|
-
return xMaxExpansion + width - RANK_CARD_DEFAULT_WIDTH;
|
|
551
|
-
}
|
|
552
|
-
return minXTranslation + (width - minCardWidth) / 2;
|
|
553
|
-
}
|
|
554
|
-
return width / 2 + 20 - 10;
|
|
555
|
-
};
|
|
567
|
+
const calculateRankXTranslation = (): number =>
|
|
568
|
+
visibleStats.length > 0
|
|
569
|
+
? width - CARD_PADDING_X - RANK_GUTTER / 2 + RANK_CIRCLE_CX_OFFSET
|
|
570
|
+
: width / 2 + 20 - 10;
|
|
556
571
|
|
|
557
572
|
// Conditionally rendered elements
|
|
558
573
|
const rankCircle =
|
|
@@ -563,8 +578,8 @@ const renderStatsCard = (
|
|
|
563
578
|
'data-testid': 'rank-circle',
|
|
564
579
|
transform: `translate(${calculateRankXTranslation()}, ${height / 2 - 50})`,
|
|
565
580
|
},
|
|
566
|
-
el('circle', { class: 'rank-circle-rim', cx: -
|
|
567
|
-
el('circle', { class: 'rank-circle', cx: -
|
|
581
|
+
el('circle', { class: 'rank-circle-rim', cx: -RANK_CIRCLE_CX_OFFSET, cy: 8, r: 40 }),
|
|
582
|
+
el('circle', { class: 'rank-circle', cx: -RANK_CIRCLE_CX_OFFSET, cy: 8, r: 40 }),
|
|
568
583
|
el('g', { class: 'rank-text' }, rankIcon(rank_icon, rank.level, rank.percentile)),
|
|
569
584
|
);
|
|
570
585
|
|
|
@@ -572,9 +587,9 @@ const renderStatsCard = (
|
|
|
572
587
|
const labels = visibleStats
|
|
573
588
|
.map(([key, stat]) => {
|
|
574
589
|
if (key === 'commits') {
|
|
575
|
-
return `${i18n.t('statcard.commits')} ${
|
|
590
|
+
return `${i18n.t('statcard.commits')} ${getTotalCommitsRangeLabel(
|
|
576
591
|
include_all_commits,
|
|
577
|
-
|
|
592
|
+
commitsRange,
|
|
578
593
|
i18n,
|
|
579
594
|
)} : ${stat.value}`;
|
|
580
595
|
}
|
|
@@ -583,7 +598,7 @@ const renderStatsCard = (
|
|
|
583
598
|
.join(', ');
|
|
584
599
|
|
|
585
600
|
card.setAccessibilityLabel({
|
|
586
|
-
title: `${card.title},
|
|
601
|
+
title: `${card.title}, ${i18n.t('statcard.rank')}: ${rank.level}`,
|
|
587
602
|
desc: labels,
|
|
588
603
|
});
|
|
589
604
|
|
|
@@ -593,4 +608,18 @@ const renderStatsCard = (
|
|
|
593
608
|
]);
|
|
594
609
|
};
|
|
595
610
|
|
|
596
|
-
|
|
611
|
+
/**
|
|
612
|
+
* The card, and the values each of its options accepts, keyed by the option's own name.
|
|
613
|
+
* They ride on the renderer so a list cannot be found without what draws it;
|
|
614
|
+
* the api handler forwards them onto its own export, which is what a UI reads.
|
|
615
|
+
*/
|
|
616
|
+
const renderStatsCard = Object.assign(renderCard, {
|
|
617
|
+
OPTIONS: {
|
|
618
|
+
rank_icon: RANK_ICONS,
|
|
619
|
+
show: SHOW_STATS,
|
|
620
|
+
hide: HIDE_STATS,
|
|
621
|
+
number_format: NUMBER_FORMATS,
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
export { renderStatsCard };
|