@stats-forge/github-stats-forge-core 0.2.0 → 0.4.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.js +1 -1
- package/build/api/gist.js +1 -1
- 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/organization.d.ts +29 -0
- package/build/api/organization.d.ts.map +1 -0
- package/build/api/organization.js +53 -0
- package/build/api/params.js +1 -1
- package/build/api/pin.js +1 -1
- package/build/api/stats.js +1 -1
- package/build/api/top-langs.js +1 -1
- package/build/api/wakatime.js +1 -1
- package/build/cards/{contributed-to.d.ts → contributed-to/index.d.ts} +3 -3
- package/build/cards/contributed-to/index.d.ts.map +1 -0
- package/build/cards/{contributed-to.js → contributed-to/index.js} +31 -30
- package/build/cards/contributed-to/locales.d.ts +38 -0
- package/build/cards/contributed-to/locales.d.ts.map +1 -0
- package/build/cards/contributed-to/locales.js +39 -0
- package/build/cards/{gist.d.ts → gist/index.d.ts} +3 -3
- package/build/cards/gist/index.d.ts.map +1 -0
- package/build/cards/{gist.js → gist/index.js} +20 -15
- package/build/cards/gist/locales.d.ts +14 -0
- package/build/cards/gist/locales.d.ts.map +1 -0
- package/build/cards/gist/locales.js +15 -0
- package/build/cards/organization/index.d.ts +36 -0
- package/build/cards/organization/index.d.ts.map +1 -0
- package/build/cards/organization/index.js +225 -0
- package/build/cards/organization/locales.d.ts +47 -0
- package/build/cards/organization/locales.d.ts.map +1 -0
- package/build/cards/organization/locales.js +47 -0
- package/build/cards/{repo.d.ts → repo/index.d.ts} +3 -3
- package/build/cards/repo/index.d.ts.map +1 -0
- package/build/cards/{repo.js → repo/index.js} +20 -23
- package/build/cards/repo/locales.d.ts +129 -0
- package/build/cards/repo/locales.d.ts.map +1 -0
- package/build/cards/repo/locales.js +129 -0
- package/build/cards/{stats.d.ts → stats/index.d.ts} +3 -3
- package/build/cards/stats/index.d.ts.map +1 -0
- package/build/cards/{stats.js → stats/index.js} +38 -42
- package/build/cards/stats/locales.d.ts +621 -0
- package/build/cards/stats/locales.d.ts.map +1 -0
- package/build/cards/stats/locales.js +623 -0
- package/build/cards/{top-languages.d.ts → top-languages/index.d.ts} +3 -3
- package/build/cards/top-languages/index.d.ts.map +1 -0
- package/build/cards/{top-languages.js → top-languages/index.js} +13 -16
- package/build/cards/top-languages/locales.d.ts +103 -0
- package/build/cards/top-languages/locales.d.ts.map +1 -0
- package/build/cards/top-languages/locales.js +103 -0
- package/build/cards/{wakatime.d.ts → wakatime/index.d.ts} +3 -3
- package/build/cards/wakatime/index.d.ts.map +1 -0
- package/build/cards/{wakatime.js → wakatime/index.js} +22 -23
- package/build/cards/wakatime/locales.d.ts +250 -0
- package/build/cards/wakatime/locales.d.ts.map +1 -0
- package/build/cards/wakatime/locales.js +250 -0
- package/build/common/brand.d.ts +1 -0
- package/build/common/brand.d.ts.map +1 -1
- package/build/common/brand.js +1 -0
- package/build/common/error.d.ts +3 -1
- package/build/common/error.d.ts.map +1 -1
- package/build/common/error.js +3 -1
- package/build/common/icons.d.ts +5 -0
- package/build/common/icons.d.ts.map +1 -1
- package/build/common/icons.js +19 -4
- package/build/common/locales.d.ts +59 -0
- package/build/common/locales.d.ts.map +1 -0
- package/build/common/locales.js +59 -0
- package/build/common/localize.d.ts +60 -0
- package/build/common/localize.d.ts.map +1 -0
- package/build/common/localize.js +163 -0
- package/build/common/render.d.ts.map +1 -1
- package/build/common/render.js +9 -3
- 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/organization.d.ts +15 -0
- package/build/fetchers/organization.d.ts.map +1 -0
- package/build/fetchers/organization.js +115 -0
- package/build/fetchers/types.d.ts +38 -0
- package/build/fetchers/types.d.ts.map +1 -1
- package/build/graphql/generated/organization.d.ts +88 -0
- package/build/graphql/generated/organization.d.ts.map +1 -0
- package/build/graphql/generated/organization.js +59 -0
- package/package.json +1 -1
- package/src/api/contributed-to.ts +1 -1
- package/src/api/gist.ts +1 -1
- package/src/api/index.ts +1 -0
- package/src/api/organization.ts +90 -0
- package/src/api/params.ts +1 -1
- package/src/api/pin.ts +1 -1
- package/src/api/stats.ts +1 -1
- package/src/api/top-langs.ts +1 -1
- package/src/api/wakatime.ts +1 -1
- package/src/cards/{contributed-to.ts → contributed-to/index.ts} +42 -39
- package/src/cards/contributed-to/locales.ts +41 -0
- package/src/cards/{gist.ts → gist/index.ts} +23 -20
- package/src/cards/gist/locales.ts +17 -0
- package/src/cards/organization/index.ts +358 -0
- package/src/cards/organization/locales.ts +49 -0
- package/src/cards/{repo.ts → repo/index.ts} +23 -26
- package/src/cards/repo/locales.ts +131 -0
- package/src/cards/{stats.ts → stats/index.ts} +48 -56
- package/src/cards/stats/locales.ts +625 -0
- package/src/cards/{top-languages.ts → top-languages/index.ts} +17 -20
- package/src/cards/top-languages/locales.ts +105 -0
- package/src/cards/{wakatime.ts → wakatime/index.ts} +26 -27
- package/src/cards/wakatime/locales.ts +252 -0
- package/src/common/brand.ts +1 -0
- package/src/common/error.ts +4 -0
- package/src/common/icons.ts +20 -4
- package/src/common/locales.ts +61 -0
- package/src/common/localize.ts +251 -0
- package/src/common/render.ts +10 -3
- package/src/fetchers/index.ts +2 -0
- package/src/fetchers/organization.ts +153 -0
- package/src/fetchers/types.ts +36 -0
- package/src/graphql/generated/organization.ts +109 -0
- package/src/graphql/queries/organization.graphql +57 -0
- package/build/cards/contributed-to.d.ts.map +0 -1
- package/build/cards/gist.d.ts.map +0 -1
- package/build/cards/repo.d.ts.map +0 -1
- package/build/cards/stats.d.ts.map +0 -1
- package/build/cards/top-languages.d.ts.map +0 -1
- package/build/cards/wakatime.d.ts.map +0 -1
- package/build/common/I18n.d.ts +0 -21
- package/build/common/I18n.d.ts.map +0 -1
- package/build/common/I18n.js +0 -34
- package/build/translations.d.ts +0 -1212
- package/build/translations.d.ts.map +0 -1
- package/build/translations.js +0 -1213
- package/src/common/I18n.ts +0 -50
- package/src/translations.ts +0 -1239
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { CARD_ICON, CARD_WIDTH, FONT_SIZE, FONT_WEIGHT, font } from '../../common/brand.ts';
|
|
2
|
+
import { Card } from '../../common/Card.ts';
|
|
3
|
+
import { getLightDarkColors } from '../../common/color.ts';
|
|
4
|
+
import type { CardColors } from '../../common/color.ts';
|
|
5
|
+
import { CardError } from '../../common/error.ts';
|
|
6
|
+
import { kFormatter } from '../../common/fmt.ts';
|
|
7
|
+
import { icons } from '../../common/icons.ts';
|
|
8
|
+
import { localize } from '../../common/localize.ts';
|
|
9
|
+
import { parseEmojis } from '../../common/ops.ts';
|
|
10
|
+
import {
|
|
11
|
+
NUMBER_FORMATS,
|
|
12
|
+
createTextNode,
|
|
13
|
+
flexLayout,
|
|
14
|
+
measureText,
|
|
15
|
+
wrapTextMultiline,
|
|
16
|
+
} from '../../common/render.ts';
|
|
17
|
+
import type { OrganizationData } from '../../fetchers/types.ts';
|
|
18
|
+
import type { Child, CssChild } from '../../markup/index.ts';
|
|
19
|
+
import { el, rule } from '../../markup/index.ts';
|
|
20
|
+
import type { CardOptions, CommonCardOptions } from '../options.ts';
|
|
21
|
+
|
|
22
|
+
import { orgCardLocales } from './locales.ts';
|
|
23
|
+
|
|
24
|
+
const CARD_DEFAULT_WIDTH = CARD_WIDTH.standard;
|
|
25
|
+
/** Padding the card keeps at its edges; matches `Card`'s own `paddingX`. */
|
|
26
|
+
const CARD_PADDING_X = 25;
|
|
27
|
+
/** How far a stat row is translated into the card; see `createTextNode`. */
|
|
28
|
+
const STAT_ROW_X = 25;
|
|
29
|
+
/** `createTextNode`'s own label offset, which it applies only when icons are shown. */
|
|
30
|
+
const LABEL_X_OFFSET = 25;
|
|
31
|
+
/** Smallest gap kept between the longest label and its value. */
|
|
32
|
+
const LABEL_VALUE_GAP = 16;
|
|
33
|
+
const DESCRIPTION_FONT_SIZE = FONT_SIZE.meta;
|
|
34
|
+
const DESCRIPTION_LINE_HEIGHT_PX = 16;
|
|
35
|
+
const DESCRIPTION_MAX_LINES = 2;
|
|
36
|
+
/** Air between the description and the first stat row. */
|
|
37
|
+
const DESCRIPTION_GAP = 8;
|
|
38
|
+
|
|
39
|
+
/** Stats the card draws only when `show` names them. */
|
|
40
|
+
const SHOW_STATS = ['releases', 'commits', 'members', 'top_language', 'created'] as const;
|
|
41
|
+
type ShowStat = (typeof SHOW_STATS)[number];
|
|
42
|
+
|
|
43
|
+
/** Stats the card always draws, and so the ones `hide` has anything to remove. */
|
|
44
|
+
const HIDE_STATS = ['repos', 'stars', 'forks', 'watchers', 'open_issues', 'open_prs'] as const;
|
|
45
|
+
type HideStat = (typeof HIDE_STATS)[number];
|
|
46
|
+
|
|
47
|
+
type StatId = HideStat | ShowStat;
|
|
48
|
+
|
|
49
|
+
interface OrgCardOptions extends CommonCardOptions {
|
|
50
|
+
locale: string;
|
|
51
|
+
hide: Array<string>;
|
|
52
|
+
show: Array<string>;
|
|
53
|
+
show_icons: boolean;
|
|
54
|
+
hide_title: boolean;
|
|
55
|
+
hide_description: boolean;
|
|
56
|
+
card_width: number;
|
|
57
|
+
line_height: number | string;
|
|
58
|
+
custom_title: string;
|
|
59
|
+
disable_animations: boolean;
|
|
60
|
+
number_format: string;
|
|
61
|
+
text_bold: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Meta data for a stat, used to build its text node and accessibility label. */
|
|
65
|
+
interface StatItem {
|
|
66
|
+
icon: Child;
|
|
67
|
+
label: string;
|
|
68
|
+
value: number | string;
|
|
69
|
+
id: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @returns Card CSS styles.
|
|
74
|
+
*/
|
|
75
|
+
const getStyles = ({
|
|
76
|
+
textColor,
|
|
77
|
+
iconColor,
|
|
78
|
+
show_icons,
|
|
79
|
+
}: {
|
|
80
|
+
textColor: string;
|
|
81
|
+
iconColor: string;
|
|
82
|
+
show_icons: boolean;
|
|
83
|
+
}): Array<CssChild> => [
|
|
84
|
+
rule('.stat', {
|
|
85
|
+
font: font('regular', 'body'),
|
|
86
|
+
fill: textColor,
|
|
87
|
+
'font-variant-numeric': 'tabular-nums',
|
|
88
|
+
}),
|
|
89
|
+
rule('.description', { font: font('regular', 'meta'), fill: textColor, opacity: 0.9 }),
|
|
90
|
+
rule('.stagger', { opacity: 0, animation: 'fadeInAnimation 0.3s ease-in-out forwards' }),
|
|
91
|
+
rule('.not_bold', { 'font-weight': FONT_WEIGHT.regular, opacity: 0.75 }),
|
|
92
|
+
rule('.bold', { 'font-weight': FONT_WEIGHT.semibold }),
|
|
93
|
+
rule('.icon', { fill: iconColor, opacity: 0.75, display: show_icons ? 'block' : 'none' }),
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Renders the organization card.
|
|
98
|
+
*
|
|
99
|
+
* @returns The organization card SVG object.
|
|
100
|
+
*/
|
|
101
|
+
const renderCard = (
|
|
102
|
+
organization: OrganizationData,
|
|
103
|
+
options: CardOptions<OrgCardOptions> = {},
|
|
104
|
+
): string => {
|
|
105
|
+
const {
|
|
106
|
+
name,
|
|
107
|
+
description,
|
|
108
|
+
createdAt,
|
|
109
|
+
publicRepos,
|
|
110
|
+
totalStars,
|
|
111
|
+
totalForks,
|
|
112
|
+
totalWatchers,
|
|
113
|
+
openIssues,
|
|
114
|
+
openPRs,
|
|
115
|
+
totalReleases,
|
|
116
|
+
totalCommits,
|
|
117
|
+
publicMembers,
|
|
118
|
+
topLanguage,
|
|
119
|
+
truncated,
|
|
120
|
+
} = organization;
|
|
121
|
+
const {
|
|
122
|
+
hide = [],
|
|
123
|
+
show = [],
|
|
124
|
+
show_icons = true,
|
|
125
|
+
hide_title = false,
|
|
126
|
+
hide_border = false,
|
|
127
|
+
hide_description = false,
|
|
128
|
+
card_width,
|
|
129
|
+
line_height = 25,
|
|
130
|
+
text_bold = true,
|
|
131
|
+
custom_title,
|
|
132
|
+
border_radius,
|
|
133
|
+
number_format = 'short',
|
|
134
|
+
locale,
|
|
135
|
+
disable_animations = false,
|
|
136
|
+
} = options;
|
|
137
|
+
|
|
138
|
+
const lheight = Number.parseInt(String(line_height), 10);
|
|
139
|
+
|
|
140
|
+
const { lightColors, darkColors } = getLightDarkColors(options);
|
|
141
|
+
|
|
142
|
+
// Typed against the exported list, so a stat drawn here cannot be missing from it.
|
|
143
|
+
const shows = (stat: ShowStat): boolean => show.includes(stat);
|
|
144
|
+
|
|
145
|
+
const apostrophe = /s$/i.test(name.trim()) ? '' : 's';
|
|
146
|
+
const t = localize(orgCardLocales, locale);
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* A total the walk may have stopped short of reads as a lower bound.
|
|
150
|
+
*
|
|
151
|
+
* @returns The value, as the row draws it.
|
|
152
|
+
*/
|
|
153
|
+
const total = (value: number): number | string =>
|
|
154
|
+
truncated
|
|
155
|
+
? `${number_format.toLowerCase() === 'long' ? String(value) : kFormatter(value)}+`
|
|
156
|
+
: value;
|
|
157
|
+
|
|
158
|
+
const STATS: Partial<Record<StatId, StatItem>> = {
|
|
159
|
+
repos: {
|
|
160
|
+
icon: icons.repo,
|
|
161
|
+
label: t.repos(),
|
|
162
|
+
value: publicRepos,
|
|
163
|
+
id: 'repos',
|
|
164
|
+
},
|
|
165
|
+
stars: {
|
|
166
|
+
icon: icons.star,
|
|
167
|
+
label: t.stars(),
|
|
168
|
+
value: total(totalStars),
|
|
169
|
+
id: 'stars',
|
|
170
|
+
},
|
|
171
|
+
forks: {
|
|
172
|
+
icon: icons.fork,
|
|
173
|
+
label: t.forks(),
|
|
174
|
+
value: total(totalForks),
|
|
175
|
+
id: 'forks',
|
|
176
|
+
},
|
|
177
|
+
watchers: {
|
|
178
|
+
icon: icons.watchers,
|
|
179
|
+
label: t.watchers(),
|
|
180
|
+
value: total(totalWatchers),
|
|
181
|
+
id: 'watchers',
|
|
182
|
+
},
|
|
183
|
+
open_issues: {
|
|
184
|
+
icon: icons.issues,
|
|
185
|
+
label: t.openIssues(),
|
|
186
|
+
value: total(openIssues),
|
|
187
|
+
id: 'open_issues',
|
|
188
|
+
},
|
|
189
|
+
open_prs: {
|
|
190
|
+
icon: icons.prs,
|
|
191
|
+
label: t.openPrs(),
|
|
192
|
+
value: total(openPRs),
|
|
193
|
+
id: 'open_prs',
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
if (shows('releases')) {
|
|
198
|
+
STATS['releases'] = {
|
|
199
|
+
icon: icons.tag,
|
|
200
|
+
label: t.releases(),
|
|
201
|
+
value: total(totalReleases),
|
|
202
|
+
id: 'releases',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (shows('commits')) {
|
|
206
|
+
STATS['commits'] = {
|
|
207
|
+
icon: icons.commits,
|
|
208
|
+
label: t.commits(),
|
|
209
|
+
value: total(totalCommits),
|
|
210
|
+
id: 'commits',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (shows('members')) {
|
|
215
|
+
STATS['members'] = {
|
|
216
|
+
icon: icons.people,
|
|
217
|
+
label: t.members(),
|
|
218
|
+
value: publicMembers,
|
|
219
|
+
id: 'members',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (shows('top_language')) {
|
|
223
|
+
STATS['top_language'] = {
|
|
224
|
+
icon: icons.code,
|
|
225
|
+
label: t.topLanguage(),
|
|
226
|
+
value: topLanguage?.name ?? t.unspecifiedLanguage(),
|
|
227
|
+
id: 'top_language',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
if (shows('created')) {
|
|
231
|
+
const year = new Date(createdAt).getUTCFullYear();
|
|
232
|
+
STATS['created'] = {
|
|
233
|
+
icon: icons.calendar,
|
|
234
|
+
label: t.created(),
|
|
235
|
+
value: Number.isNaN(year) ? '—' : String(year),
|
|
236
|
+
id: 'created',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const visibleStats = Object.entries(STATS).filter(([key]) => !hide.includes(key));
|
|
241
|
+
|
|
242
|
+
if (visibleStats.length === 0) {
|
|
243
|
+
throw new CardError('Could not render organization card.', {
|
|
244
|
+
code: 'invalid_param',
|
|
245
|
+
secondaryMessage: 'At least one stat is required.',
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const width = card_width && !Number.isNaN(card_width) ? card_width : CARD_DEFAULT_WIDTH;
|
|
250
|
+
|
|
251
|
+
const desc = hide_description ? '' : parseEmojis(description || t.noDescription());
|
|
252
|
+
const descriptionLines = desc
|
|
253
|
+
? wrapTextMultiline(
|
|
254
|
+
desc,
|
|
255
|
+
width - 2 * CARD_PADDING_X,
|
|
256
|
+
DESCRIPTION_FONT_SIZE,
|
|
257
|
+
DESCRIPTION_MAX_LINES,
|
|
258
|
+
)
|
|
259
|
+
: [];
|
|
260
|
+
const statsTop =
|
|
261
|
+
descriptionLines.length > 0
|
|
262
|
+
? descriptionLines.length * DESCRIPTION_LINE_HEIGHT_PX + DESCRIPTION_GAP
|
|
263
|
+
: 0;
|
|
264
|
+
|
|
265
|
+
const height = 45 + (visibleStats.length + 1) * lheight + statsTop;
|
|
266
|
+
|
|
267
|
+
// A value ends at the card's inner edge — or right after the longest label,
|
|
268
|
+
// on a card too narrow for that.
|
|
269
|
+
const widestLabel = Math.max(
|
|
270
|
+
...visibleStats.map(([, stat]) => measureText(`${stat.label}:`, FONT_SIZE.body)),
|
|
271
|
+
);
|
|
272
|
+
const valueAnchorX = Math.round(
|
|
273
|
+
Math.max(
|
|
274
|
+
width - CARD_PADDING_X - STAT_ROW_X,
|
|
275
|
+
(show_icons ? LABEL_X_OFFSET : 0) + widestLabel + LABEL_VALUE_GAP,
|
|
276
|
+
),
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const statItems = visibleStats.map(([, stat], index) =>
|
|
280
|
+
createTextNode({
|
|
281
|
+
icon: stat.icon,
|
|
282
|
+
label: stat.label,
|
|
283
|
+
value: stat.value,
|
|
284
|
+
id: stat.id,
|
|
285
|
+
index,
|
|
286
|
+
showIcons: show_icons,
|
|
287
|
+
shiftValuePos: 0,
|
|
288
|
+
valueAnchorX,
|
|
289
|
+
bold: text_bold,
|
|
290
|
+
labelBold: false,
|
|
291
|
+
numberFormat: number_format,
|
|
292
|
+
}),
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const card = new Card({
|
|
296
|
+
customTitle: custom_title,
|
|
297
|
+
defaultTitle: t.title({ name, apostrophe }),
|
|
298
|
+
titlePrefixIcon: CARD_ICON.organization,
|
|
299
|
+
width,
|
|
300
|
+
height,
|
|
301
|
+
border_radius,
|
|
302
|
+
colors: { light: lightColors, dark: darkColors },
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
card.setHideBorder(hide_border);
|
|
306
|
+
card.setHideTitle(hide_title);
|
|
307
|
+
const cardStyles = ({ textColor, iconColor }: CardColors): Array<CssChild> =>
|
|
308
|
+
getStyles({ textColor, iconColor, show_icons });
|
|
309
|
+
card.setCSS({ light: cardStyles, dark: cardStyles });
|
|
310
|
+
|
|
311
|
+
if (disable_animations) {
|
|
312
|
+
card.disableAnimations();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// `role="img"` hides the inner text from assistive tech, so everything the card
|
|
316
|
+
// shows has to be repeated here.
|
|
317
|
+
card.setAccessibilityLabel({
|
|
318
|
+
title: card.title,
|
|
319
|
+
desc: [
|
|
320
|
+
desc && `${desc}.`,
|
|
321
|
+
visibleStats.map(([, stat]) => `${stat.label}: ${String(stat.value)}`).join(', '),
|
|
322
|
+
]
|
|
323
|
+
.filter(Boolean)
|
|
324
|
+
.join(' '),
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const descriptionSvg =
|
|
328
|
+
descriptionLines.length > 0 &&
|
|
329
|
+
el(
|
|
330
|
+
'text',
|
|
331
|
+
{ class: 'description', 'data-testid': 'description', x: CARD_PADDING_X, y: -5 },
|
|
332
|
+
descriptionLines.map((line) => el('tspan', { dy: '1.2em', x: CARD_PADDING_X }, line)),
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
return card.render([
|
|
336
|
+
descriptionSvg,
|
|
337
|
+
el(
|
|
338
|
+
'g',
|
|
339
|
+
{ transform: `translate(0, ${statsTop})` },
|
|
340
|
+
flexLayout({ items: statItems, gap: lheight, direction: 'column' }),
|
|
341
|
+
),
|
|
342
|
+
]);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* The card, and the values each of its options accepts, keyed by the option's own name.
|
|
347
|
+
* They ride on the renderer so a list cannot be found without what draws it;
|
|
348
|
+
* the api handler forwards them onto its own export, which is what a UI reads.
|
|
349
|
+
*/
|
|
350
|
+
const renderOrganizationCard = Object.assign(renderCard, {
|
|
351
|
+
OPTIONS: {
|
|
352
|
+
show: SHOW_STATS,
|
|
353
|
+
hide: HIDE_STATS,
|
|
354
|
+
number_format: NUMBER_FORMATS,
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
export { renderOrganizationCard };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { defineLocales } from '../../common/localize.ts';
|
|
2
|
+
|
|
3
|
+
/** The organization card's labels in the locales they have been translated into. */
|
|
4
|
+
const orgCardLocales = defineLocales({
|
|
5
|
+
title: {
|
|
6
|
+
en: `{name}'{apostrophe} GitHub Organization`,
|
|
7
|
+
},
|
|
8
|
+
noDescription: {
|
|
9
|
+
en: 'No description provided',
|
|
10
|
+
},
|
|
11
|
+
repos: {
|
|
12
|
+
en: 'Public repositories',
|
|
13
|
+
},
|
|
14
|
+
stars: {
|
|
15
|
+
en: 'Total stars',
|
|
16
|
+
},
|
|
17
|
+
forks: {
|
|
18
|
+
en: 'Total forks',
|
|
19
|
+
},
|
|
20
|
+
watchers: {
|
|
21
|
+
en: 'Total watchers',
|
|
22
|
+
},
|
|
23
|
+
openIssues: {
|
|
24
|
+
en: 'Open issues',
|
|
25
|
+
},
|
|
26
|
+
openPrs: {
|
|
27
|
+
en: 'Open PRs',
|
|
28
|
+
},
|
|
29
|
+
releases: {
|
|
30
|
+
en: 'Releases',
|
|
31
|
+
},
|
|
32
|
+
commits: {
|
|
33
|
+
en: 'Commits',
|
|
34
|
+
},
|
|
35
|
+
members: {
|
|
36
|
+
en: 'Public members',
|
|
37
|
+
},
|
|
38
|
+
topLanguage: {
|
|
39
|
+
en: 'Top language',
|
|
40
|
+
},
|
|
41
|
+
created: {
|
|
42
|
+
en: 'Created',
|
|
43
|
+
},
|
|
44
|
+
unspecifiedLanguage: {
|
|
45
|
+
en: 'Unspecified',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export { orgCardLocales };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { CARD_ICON, CARD_WIDTH, FONT_SIZE, FONT_WEIGHT, font } from '
|
|
2
|
-
import { Card } from '
|
|
3
|
-
import { getLightDarkColors } from '
|
|
4
|
-
import { kFormatter } from '
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { buildSearchFilter, clampValue, parseEmojis } from '
|
|
1
|
+
import { CARD_ICON, CARD_WIDTH, FONT_SIZE, FONT_WEIGHT, font } from '../../common/brand.ts';
|
|
2
|
+
import { Card } from '../../common/Card.ts';
|
|
3
|
+
import { getLightDarkColors } from '../../common/color.ts';
|
|
4
|
+
import { kFormatter } from '../../common/fmt.ts';
|
|
5
|
+
import { icons } from '../../common/icons.ts';
|
|
6
|
+
import { localize } from '../../common/localize.ts';
|
|
7
|
+
import { buildSearchFilter, clampValue, parseEmojis } from '../../common/ops.ts';
|
|
8
8
|
import {
|
|
9
9
|
NUMBER_FORMATS,
|
|
10
10
|
countWrappedLines,
|
|
@@ -16,13 +16,13 @@ import {
|
|
|
16
16
|
measureText,
|
|
17
17
|
wrappedTextNode,
|
|
18
18
|
wrappedTextStyles,
|
|
19
|
-
} from '
|
|
20
|
-
import type { RepositoryData } from '
|
|
21
|
-
import type { Child, MarkupElement } from '
|
|
22
|
-
import { el, rule } from '
|
|
23
|
-
import {
|
|
19
|
+
} from '../../common/render.ts';
|
|
20
|
+
import type { RepositoryData } from '../../fetchers/types.ts';
|
|
21
|
+
import type { Child, MarkupElement } from '../../markup/index.ts';
|
|
22
|
+
import { el, rule } from '../../markup/index.ts';
|
|
23
|
+
import type { CardOptions, CommonCardOptions } from '../options.ts';
|
|
24
24
|
|
|
25
|
-
import
|
|
25
|
+
import { repoCardLocales } from './locales.ts';
|
|
26
26
|
|
|
27
27
|
const ICON_SIZE = 16;
|
|
28
28
|
const CARD_DEFAULT_WIDTH = CARD_WIDTH.standard;
|
|
@@ -137,10 +137,7 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
|
|
|
137
137
|
? CARD_DEFAULT_WIDTH + 30
|
|
138
138
|
: CARD_DEFAULT_WIDTH;
|
|
139
139
|
|
|
140
|
-
const
|
|
141
|
-
locale,
|
|
142
|
-
translations: repoCardLocales,
|
|
143
|
-
});
|
|
140
|
+
const t = localize(repoCardLocales, locale);
|
|
144
141
|
|
|
145
142
|
// Typed against the exported list, so a stat drawn here cannot be missing from it.
|
|
146
143
|
const shows = (stat: RepoShowStat): boolean => show.includes(stat);
|
|
@@ -151,7 +148,7 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
|
|
|
151
148
|
if (shows('prs_authored')) {
|
|
152
149
|
STATS['prs_authored'] = {
|
|
153
150
|
icon: icons.prs,
|
|
154
|
-
label:
|
|
151
|
+
label: t.prsAuthored(),
|
|
155
152
|
value: totalPRsAuthored,
|
|
156
153
|
id: 'prs_authored',
|
|
157
154
|
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=pullrequests`,
|
|
@@ -160,7 +157,7 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
|
|
|
160
157
|
if (shows('prs_commented')) {
|
|
161
158
|
STATS['prs_commented'] = {
|
|
162
159
|
icon: icons.comments,
|
|
163
|
-
label:
|
|
160
|
+
label: t.prsCommented(),
|
|
164
161
|
value: totalPRsCommented,
|
|
165
162
|
id: 'prs_commented',
|
|
166
163
|
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
@@ -169,7 +166,7 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
|
|
|
169
166
|
if (shows('prs_reviewed')) {
|
|
170
167
|
STATS['prs_reviewed'] = {
|
|
171
168
|
icon: icons.reviews,
|
|
172
|
-
label:
|
|
169
|
+
label: t.prsReviewed(),
|
|
173
170
|
value: totalPRsReviewed,
|
|
174
171
|
id: 'prs_reviewed',
|
|
175
172
|
link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
@@ -178,7 +175,7 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
|
|
|
178
175
|
if (shows('issues_authored')) {
|
|
179
176
|
STATS['issues_authored'] = {
|
|
180
177
|
icon: icons.issues,
|
|
181
|
-
label:
|
|
178
|
+
label: t.issuesAuthored(),
|
|
182
179
|
value: totalIssuesAuthored,
|
|
183
180
|
id: 'issues_authored',
|
|
184
181
|
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=issues`,
|
|
@@ -187,7 +184,7 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
|
|
|
187
184
|
if (shows('issues_commented')) {
|
|
188
185
|
STATS['issues_commented'] = {
|
|
189
186
|
icon: icons.discussions_started,
|
|
190
|
-
label:
|
|
187
|
+
label: t.issuesCommented(),
|
|
191
188
|
value: totalIssuesCommented,
|
|
192
189
|
id: 'issues_commented',
|
|
193
190
|
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&type=issues`,
|
|
@@ -215,9 +212,9 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
|
|
|
215
212
|
const extraLHeight = Number.parseInt(String(line_height), 10);
|
|
216
213
|
const lineHeight = 10;
|
|
217
214
|
const header = show_owner ? nameWithOwner : name;
|
|
218
|
-
const langName = primaryLanguage?.name ||
|
|
215
|
+
const langName = primaryLanguage?.name || t.unspecifiedLanguage();
|
|
219
216
|
const langColor = primaryLanguage?.color || '#333';
|
|
220
|
-
const desc = parseEmojis(description ||
|
|
217
|
+
const desc = parseEmojis(description || t.noDescription());
|
|
221
218
|
const descriptionBoxWidth = card_width - 2 * X_OFFSET;
|
|
222
219
|
|
|
223
220
|
let descriptionLinesCount: number;
|
|
@@ -365,9 +362,9 @@ const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions>
|
|
|
365
362
|
|
|
366
363
|
return card.render([
|
|
367
364
|
isTemplate
|
|
368
|
-
? getBadgeSVG(
|
|
365
|
+
? getBadgeSVG(t.template(), card_width - CARD_DEFAULT_WIDTH)
|
|
369
366
|
: isArchived
|
|
370
|
-
? getBadgeSVG(
|
|
367
|
+
? getBadgeSVG(t.archived(), card_width - CARD_DEFAULT_WIDTH)
|
|
371
368
|
: undefined,
|
|
372
369
|
descriptionSvg,
|
|
373
370
|
el('g', { transform: `translate(30, ${height - 75 - extraHeight})` }, starAndForkCount),
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { defineLocales } from '../../common/localize.ts';
|
|
2
|
+
|
|
3
|
+
/** The repo card's labels in the locales they have been translated into. */
|
|
4
|
+
const repoCardLocales = defineLocales({
|
|
5
|
+
noDescription: {
|
|
6
|
+
en: 'No description provided',
|
|
7
|
+
},
|
|
8
|
+
unspecifiedLanguage: {
|
|
9
|
+
en: 'Unspecified',
|
|
10
|
+
},
|
|
11
|
+
template: {
|
|
12
|
+
en: 'Template',
|
|
13
|
+
ar: 'قالب',
|
|
14
|
+
az: 'Şablon',
|
|
15
|
+
bg: 'Шаблон',
|
|
16
|
+
bn: 'টেমপ্লেট',
|
|
17
|
+
ca: 'Plantilla',
|
|
18
|
+
cn: '模板',
|
|
19
|
+
'zh-tw': '模板',
|
|
20
|
+
cs: 'Šablona',
|
|
21
|
+
de: 'Vorlage',
|
|
22
|
+
sw: 'Kigezo',
|
|
23
|
+
ur: 'سانچہ',
|
|
24
|
+
es: 'Plantilla',
|
|
25
|
+
fa: 'الگو',
|
|
26
|
+
fi: 'Malli',
|
|
27
|
+
fr: 'Modèle',
|
|
28
|
+
hi: 'खाका',
|
|
29
|
+
sa: 'प्रारूपम्',
|
|
30
|
+
hu: 'Sablon',
|
|
31
|
+
it: 'Template',
|
|
32
|
+
ja: 'テンプレート',
|
|
33
|
+
kr: '템플릿',
|
|
34
|
+
nl: 'Sjabloon',
|
|
35
|
+
'pt-pt': 'Modelo',
|
|
36
|
+
'pt-br': 'Modelo',
|
|
37
|
+
np: 'टेम्पलेट',
|
|
38
|
+
el: 'Πρότυπο',
|
|
39
|
+
ro: 'Șablon',
|
|
40
|
+
ru: 'Шаблон',
|
|
41
|
+
'uk-ua': 'Шаблон',
|
|
42
|
+
id: 'Pola',
|
|
43
|
+
ml: 'ടെംപ്ലേറ്റ്',
|
|
44
|
+
my: 'ပုံစံ',
|
|
45
|
+
ta: `டெம்ப்ளேட்`,
|
|
46
|
+
sk: 'Šablóna',
|
|
47
|
+
tr: 'Şablon',
|
|
48
|
+
pl: 'Szablony',
|
|
49
|
+
uz: 'Shablon',
|
|
50
|
+
vi: 'Mẫu',
|
|
51
|
+
se: 'Mall',
|
|
52
|
+
he: 'תבנית',
|
|
53
|
+
fil: 'Suleras',
|
|
54
|
+
th: 'เทมเพลต',
|
|
55
|
+
sr: 'Шаблон',
|
|
56
|
+
'sr-latn': 'Šablon',
|
|
57
|
+
no: 'Mal',
|
|
58
|
+
be: 'Шаблон',
|
|
59
|
+
},
|
|
60
|
+
archived: {
|
|
61
|
+
en: 'Archived',
|
|
62
|
+
ar: 'مُؤرشف',
|
|
63
|
+
az: 'Arxiv',
|
|
64
|
+
bg: 'Архивирани',
|
|
65
|
+
bn: 'আর্কাইভড',
|
|
66
|
+
ca: 'Arxivats',
|
|
67
|
+
cn: '已归档',
|
|
68
|
+
'zh-tw': '已封存',
|
|
69
|
+
cs: 'Archivováno',
|
|
70
|
+
de: 'Archiviert',
|
|
71
|
+
sw: 'Hifadhiwa kwenye kumbukumbu',
|
|
72
|
+
ur: 'محفوظ شدہ',
|
|
73
|
+
es: 'Archivados',
|
|
74
|
+
fa: 'بایگانیشده',
|
|
75
|
+
fi: 'Arkistoitu',
|
|
76
|
+
fr: 'Archivé',
|
|
77
|
+
hi: 'संग्रहीत',
|
|
78
|
+
sa: 'संगृहीतम्',
|
|
79
|
+
hu: 'Archivált',
|
|
80
|
+
it: 'Archiviata',
|
|
81
|
+
ja: 'アーカイブ済み',
|
|
82
|
+
kr: '보관됨',
|
|
83
|
+
nl: 'Gearchiveerd',
|
|
84
|
+
'pt-pt': 'Arquivados',
|
|
85
|
+
'pt-br': 'Arquivados',
|
|
86
|
+
np: 'अभिलेख राखियो',
|
|
87
|
+
el: 'Αρχειοθετημένα',
|
|
88
|
+
ro: 'Arhivat',
|
|
89
|
+
ru: 'Архивирован',
|
|
90
|
+
'uk-ua': 'Архивований',
|
|
91
|
+
id: 'Arsip',
|
|
92
|
+
ml: 'ശേഖരിച്ചത്',
|
|
93
|
+
my: 'သိုလှောင်ပြီး',
|
|
94
|
+
ta: `காப்பகப்படுத்தப்பட்டது`,
|
|
95
|
+
sk: 'Archivované',
|
|
96
|
+
tr: 'Arşiv',
|
|
97
|
+
pl: 'Zarchiwizowano',
|
|
98
|
+
uz: 'Arxivlangan',
|
|
99
|
+
vi: 'Đã Lưu Trữ',
|
|
100
|
+
se: 'Arkiverade',
|
|
101
|
+
he: 'גנוז',
|
|
102
|
+
fil: 'Naka-arkibo',
|
|
103
|
+
th: 'เก็บถาวร',
|
|
104
|
+
sr: 'Архивирано',
|
|
105
|
+
'sr-latn': 'Arhivirano',
|
|
106
|
+
no: 'Arkivert',
|
|
107
|
+
be: 'Архіваваны',
|
|
108
|
+
},
|
|
109
|
+
prsAuthored: {
|
|
110
|
+
en: 'my created PRs',
|
|
111
|
+
'zh-tw': '我所創建的拉取請求',
|
|
112
|
+
},
|
|
113
|
+
prsCommented: {
|
|
114
|
+
en: 'my commented PRs',
|
|
115
|
+
'zh-tw': '我所參與的拉取請求',
|
|
116
|
+
},
|
|
117
|
+
prsReviewed: {
|
|
118
|
+
en: 'my reviewed PRs',
|
|
119
|
+
'zh-tw': '我所審核的拉取請求',
|
|
120
|
+
},
|
|
121
|
+
issuesAuthored: {
|
|
122
|
+
en: 'my created issues',
|
|
123
|
+
'zh-tw': '我所創建的議題',
|
|
124
|
+
},
|
|
125
|
+
issuesCommented: {
|
|
126
|
+
en: 'my commented issues',
|
|
127
|
+
'zh-tw': '我所參與的議題',
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export { repoCardLocales };
|