@stats-forge/github-stats-forge-core 0.0.2 → 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/README.md +0 -10
- package/build/api/api-result.d.ts +6 -7
- package/build/api/api-result.d.ts.map +1 -1
- package/build/api/api-result.js +4 -5
- 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 -22
- package/build/api/gist.d.ts.map +1 -1
- package/build/api/gist.js +14 -36
- 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 +13 -12
- package/build/api/index.d.ts.map +1 -1
- package/build/api/index.js +1 -0
- package/build/api/params.d.ts +21 -12
- package/build/api/params.d.ts.map +1 -1
- package/build/api/params.js +41 -19
- package/build/api/pin.d.ts +11 -31
- package/build/api/pin.d.ts.map +1 -1
- package/build/api/pin.js +35 -63
- package/build/api/stats.d.ts +15 -16
- package/build/api/stats.d.ts.map +1 -1
- package/build/api/stats.js +60 -99
- package/build/api/top-langs.d.ts +13 -18
- package/build/api/top-langs.d.ts.map +1 -1
- package/build/api/top-langs.js +35 -72
- package/build/api/wakatime.d.ts +11 -17
- package/build/api/wakatime.d.ts.map +1 -1
- package/build/api/wakatime.js +27 -64
- package/build/calculateRank.d.ts +0 -9
- package/build/calculateRank.d.ts.map +1 -1
- package/build/calculateRank.js +12 -17
- 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 +3 -4
- package/build/cards/gist.d.ts.map +1 -1
- package/build/cards/gist.js +38 -43
- package/build/cards/options.d.ts +1 -1
- package/build/cards/repo.d.ts +14 -5
- package/build/cards/repo.d.ts.map +1 -1
- package/build/cards/repo.js +93 -103
- package/build/cards/stats.d.ts +18 -11
- package/build/cards/stats.d.ts.map +1 -1
- package/build/cards/stats.js +191 -222
- package/build/cards/top-languages.d.ts +18 -33
- package/build/cards/top-languages.d.ts.map +1 -1
- package/build/cards/top-languages.js +196 -387
- package/build/cards/wakatime.d.ts +17 -8
- package/build/cards/wakatime.d.ts.map +1 -1
- package/build/cards/wakatime.js +133 -187
- package/build/common/Card.d.ts +20 -59
- package/build/common/Card.d.ts.map +1 -1
- package/build/common/Card.js +131 -198
- package/build/common/I18n.d.ts +1 -7
- package/build/common/I18n.d.ts.map +1 -1
- package/build/common/I18n.js +7 -8
- 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/color.d.ts +4 -17
- package/build/common/color.d.ts.map +1 -1
- package/build/common/color.js +67 -49
- package/build/common/config.d.ts +4 -14
- package/build/common/config.d.ts.map +1 -1
- package/build/common/config.js +3 -16
- package/build/common/constants.d.ts +6 -2
- package/build/common/constants.d.ts.map +1 -1
- package/build/common/constants.js +5 -1
- package/build/common/date.d.ts +50 -5
- package/build/common/date.d.ts.map +1 -1
- package/build/common/date.js +104 -4
- package/build/common/emojiMap.d.ts +8 -0
- package/build/common/emojiMap.d.ts.map +1 -0
- package/build/common/emojiMap.js +10 -0
- package/build/common/emojiMap.json +1915 -0
- package/build/common/error.d.ts +1 -10
- package/build/common/error.d.ts.map +1 -1
- package/build/common/error.js +1 -10
- package/build/common/fmt.d.ts +3 -15
- package/build/common/fmt.d.ts.map +1 -1
- package/build/common/fmt.js +7 -35
- package/build/common/http.d.ts +2 -7
- package/build/common/http.d.ts.map +1 -1
- package/build/common/http.js +8 -17
- package/build/common/icons.d.ts +20 -20
- package/build/common/icons.d.ts.map +1 -1
- package/build/common/icons.js +108 -67
- package/build/common/languageColors.d.ts +0 -1
- package/build/common/languageColors.d.ts.map +1 -1
- package/build/common/languageColors.js +1 -4
- package/build/common/languageColors.json +18 -2
- package/build/common/ops.d.ts +11 -17
- package/build/common/ops.d.ts.map +1 -1
- package/build/common/ops.js +22 -39
- package/build/common/render.d.ts +34 -82
- package/build/common/render.d.ts.map +1 -1
- package/build/common/render.js +225 -259
- package/build/common/retryer.d.ts +2 -7
- package/build/common/retryer.d.ts.map +1 -1
- package/build/common/retryer.js +4 -11
- 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/gist.d.ts +2 -5
- package/build/fetchers/gist.d.ts.map +1 -1
- package/build/fetchers/gist.js +1 -5
- 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 +13 -12
- package/build/fetchers/index.d.ts.map +1 -1
- package/build/fetchers/index.js +1 -0
- package/build/fetchers/repo.d.ts +2 -11
- package/build/fetchers/repo.d.ts.map +1 -1
- package/build/fetchers/repo.js +20 -33
- package/build/fetchers/stats.d.ts +16 -40
- package/build/fetchers/stats.d.ts.map +1 -1
- package/build/fetchers/stats.js +89 -271
- package/build/fetchers/top-languages.d.ts +2 -9
- package/build/fetchers/top-languages.d.ts.map +1 -1
- package/build/fetchers/top-languages.js +16 -47
- package/build/fetchers/types.d.ts +25 -1
- package/build/fetchers/types.d.ts.map +1 -1
- package/build/fetchers/wakatime.d.ts +2 -6
- package/build/fetchers/wakatime.d.ts.map +1 -1
- package/build/fetchers/wakatime.js +1 -5
- 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 +6 -6
- package/build/graphql/contributionsDocument.d.ts.map +1 -1
- package/build/graphql/contributionsDocument.js +8 -16
- 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/gist.d.ts +1 -1
- package/build/graphql/generated/repo.d.ts +1 -1
- package/build/graphql/generated/stats.d.ts +16 -4
- package/build/graphql/generated/stats.d.ts.map +1 -1
- package/build/graphql/generated/stats.js +2 -2
- package/build/graphql/generated/top-languages.d.ts +2 -2
- package/build/graphql/graphqlDocument.d.ts +1 -2
- package/build/graphql/graphqlDocument.d.ts.map +1 -1
- package/build/graphql/graphqlDocument.js +0 -1
- package/build/graphql/reposContributedToDocument.d.ts +4 -5
- package/build/graphql/reposContributedToDocument.d.ts.map +1 -1
- package/build/graphql/reposContributedToDocument.js +11 -7
- package/build/markup/css.d.ts +35 -0
- package/build/markup/css.d.ts.map +1 -0
- package/build/markup/css.js +64 -0
- package/build/markup/element.d.ts +41 -0
- package/build/markup/element.d.ts.map +1 -0
- package/build/markup/element.js +127 -0
- package/build/markup/html.d.ts +13 -0
- package/build/markup/html.d.ts.map +1 -0
- package/build/markup/html.js +27 -0
- package/build/markup/index.d.ts +9 -0
- package/build/markup/index.d.ts.map +1 -0
- package/build/markup/index.js +6 -0
- package/build/markup/serialize.d.ts +16 -0
- package/build/markup/serialize.d.ts.map +1 -0
- package/build/markup/serialize.js +17 -0
- package/build/themes/index.d.ts +332 -333
- package/build/themes/index.d.ts.map +1 -1
- package/build/themes/index.js +1 -1
- package/build/translations.d.ts +66 -7
- package/build/translations.d.ts.map +1 -1
- package/build/translations.js +686 -629
- package/package.json +17 -26
- package/src/api/api-result.ts +10 -9
- package/src/api/contributed-to.ts +84 -0
- package/src/api/gist.ts +22 -46
- package/src/api/handler.ts +42 -0
- package/src/api/index.ts +13 -12
- package/src/api/params.ts +74 -29
- package/src/api/pin.ts +38 -65
- package/src/api/stats.ts +81 -118
- package/src/api/top-langs.ts +38 -80
- package/src/api/wakatime.ts +35 -73
- package/src/calculateRank.ts +12 -23
- package/src/cards/contributed-to.ts +373 -0
- package/src/cards/gist.ts +55 -49
- package/src/cards/options.ts +1 -1
- package/src/cards/repo.ts +134 -120
- package/src/cards/stats.ts +262 -252
- package/src/cards/top-languages.ts +315 -422
- package/src/cards/wakatime.ts +207 -225
- package/src/common/Card.ts +207 -214
- package/src/common/I18n.ts +7 -8
- package/src/common/brand.ts +109 -0
- package/src/common/color.ts +80 -57
- package/src/common/config.ts +5 -18
- package/src/common/constants.ts +8 -2
- package/src/common/date.ts +135 -5
- package/src/common/emojiMap.json +1915 -0
- package/src/common/emojiMap.ts +13 -0
- package/src/common/error.ts +2 -11
- package/src/common/fmt.ts +7 -44
- package/src/common/http.ts +9 -15
- package/src/common/icons.ts +122 -42
- package/src/common/languageColors.json +18 -2
- package/src/common/languageColors.ts +1 -4
- package/src/common/ops.ts +31 -48
- package/src/common/render.ts +334 -284
- package/src/common/retryer.ts +8 -15
- package/src/fetchers/contributed-to.ts +271 -0
- package/src/fetchers/gist.ts +8 -12
- package/src/fetchers/graphql-error.ts +36 -0
- package/src/fetchers/index.ts +15 -12
- package/src/fetchers/repo.ts +27 -40
- package/src/fetchers/stats.ts +157 -366
- package/src/fetchers/top-languages.ts +27 -55
- package/src/fetchers/types.ts +27 -1
- package/src/fetchers/wakatime.ts +5 -9
- package/src/graphql/contributionsCollection.ts +18 -0
- package/src/graphql/contributionsDocument.ts +14 -19
- package/src/graphql/generated/contributed-to.ts +25 -0
- package/src/graphql/generated/gist.ts +1 -1
- package/src/graphql/generated/repo.ts +1 -1
- package/src/graphql/generated/stats.ts +21 -8
- package/src/graphql/generated/top-languages.ts +2 -2
- package/src/graphql/graphqlDocument.ts +1 -2
- package/src/graphql/queries/contributed-to.graphql +8 -0
- package/src/graphql/queries/stats.graphql +14 -2
- package/src/graphql/reposContributedToDocument.ts +16 -15
- package/src/markup/css.ts +118 -0
- package/src/markup/element.ts +246 -0
- package/src/markup/html.ts +34 -0
- package/src/markup/index.ts +9 -0
- package/src/markup/serialize.ts +27 -0
- package/src/themes/index.ts +1 -1
- package/src/translations.ts +699 -629
- package/build/cards/index.d.ts +0 -11
- package/build/cards/index.d.ts.map +0 -1
- package/build/cards/index.js +0 -7
- package/build/common/html.d.ts +0 -8
- package/build/common/html.d.ts.map +0 -1
- package/build/common/html.js +0 -14
- package/build/index.d.ts +0 -15
- package/build/index.d.ts.map +0 -1
- package/build/index.js +0 -10
- package/src/_emoji-name-map.d.ts +0 -10
- package/src/_github-username-regex.d.ts +0 -4
- package/src/cards/index.ts +0 -21
- package/src/common/html.ts +0 -17
- package/src/index.ts +0 -18
package/src/cards/gist.ts
CHANGED
|
@@ -1,33 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { CARD_ICON, CARD_WIDTH, FONT_SIZE, 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 { I18n } from '../common/I18n.ts';
|
|
6
|
+
import { icons } from '../common/icons.ts';
|
|
7
|
+
import { getLanguageColor } from '../common/languageColors.ts';
|
|
8
|
+
import { parseEmojis } from '../common/ops.ts';
|
|
8
9
|
import {
|
|
9
10
|
countWrappedLines,
|
|
11
|
+
wrapTextMultiline,
|
|
10
12
|
createLanguageNode,
|
|
11
13
|
flexLayout,
|
|
12
14
|
iconWithLabel,
|
|
13
15
|
measureText,
|
|
14
16
|
wrappedTextNode,
|
|
15
17
|
wrappedTextStyles,
|
|
16
|
-
} from '../common/render.
|
|
17
|
-
import type { GistData } from '../fetchers/types.
|
|
18
|
+
} from '../common/render.ts';
|
|
19
|
+
import type { GistData } from '../fetchers/types.ts';
|
|
20
|
+
import type { Child } from '../markup/index.ts';
|
|
21
|
+
import { el, rule } from '../markup/index.ts';
|
|
22
|
+
import { gistCardLocales } from '../translations.ts';
|
|
18
23
|
|
|
19
|
-
import type { CardOptions, CommonCardOptions } from './options.
|
|
24
|
+
import type { CardOptions, CommonCardOptions } from './options.ts';
|
|
20
25
|
|
|
21
26
|
const ICON_SIZE = 16;
|
|
22
|
-
const CARD_DEFAULT_WIDTH =
|
|
27
|
+
const CARD_DEFAULT_WIDTH = CARD_WIDTH.standard;
|
|
23
28
|
const X_OFFSET = 25;
|
|
24
29
|
const HEADER_MAX_LENGTH = 35;
|
|
25
30
|
const DESCRIPTION_BOX_WIDTH = CARD_DEFAULT_WIDTH - 2 * X_OFFSET;
|
|
26
|
-
const DESCRIPTION_FONT_SIZE =
|
|
31
|
+
const DESCRIPTION_FONT_SIZE = FONT_SIZE.meta;
|
|
27
32
|
const DESCRIPTION_LINE_HEIGHT_PX = 16;
|
|
28
33
|
const DESCRIPTION_MAX_LINES = 10;
|
|
29
34
|
|
|
30
35
|
interface GistCardOptions extends CommonCardOptions {
|
|
36
|
+
locale: string;
|
|
31
37
|
show_owner: boolean;
|
|
32
38
|
browser_rendering: boolean;
|
|
33
39
|
}
|
|
@@ -35,13 +41,12 @@ interface GistCardOptions extends CommonCardOptions {
|
|
|
35
41
|
/**
|
|
36
42
|
* Render gist card.
|
|
37
43
|
*
|
|
38
|
-
* @param gistData Gist data.
|
|
39
|
-
* @param options Gist card options.
|
|
40
44
|
* @returns Gist card.
|
|
41
45
|
*/
|
|
42
46
|
const renderGistCard = (gistData: GistData, options: CardOptions<GistCardOptions> = {}): string => {
|
|
43
47
|
const { name, nameWithOwner, description, language, starsCount, forksCount } = gistData;
|
|
44
48
|
const {
|
|
49
|
+
locale,
|
|
45
50
|
theme = 'default_repocard',
|
|
46
51
|
border_radius,
|
|
47
52
|
show_owner = false,
|
|
@@ -51,10 +56,12 @@ const renderGistCard = (gistData: GistData, options: CardOptions<GistCardOptions
|
|
|
51
56
|
|
|
52
57
|
const { lightColors, darkColors } = getLightDarkColors({ ...options, theme });
|
|
53
58
|
|
|
54
|
-
const
|
|
59
|
+
const i18n = new I18n({ locale, translations: gistCardLocales });
|
|
60
|
+
|
|
61
|
+
const desc = parseEmojis(description || i18n.t('gistcard.no-description'));
|
|
55
62
|
|
|
56
63
|
let descriptionLines: number;
|
|
57
|
-
let descriptionSvg:
|
|
64
|
+
let descriptionSvg: Child;
|
|
58
65
|
if (browser_rendering) {
|
|
59
66
|
// The browser performs the actual text wrapping inside the foreignObject;
|
|
60
67
|
// we only estimate the line count server-side so the SVG can reserve enough
|
|
@@ -86,12 +93,11 @@ const renderGistCard = (gistData: GistData, options: CardOptions<GistCardOptions
|
|
|
86
93
|
linesLimit,
|
|
87
94
|
);
|
|
88
95
|
descriptionLines = multiLineDescription.length;
|
|
89
|
-
descriptionSvg =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
</text>`;
|
|
96
|
+
descriptionSvg = el(
|
|
97
|
+
'text',
|
|
98
|
+
{ class: 'description', x: X_OFFSET, y: -5 },
|
|
99
|
+
multiLineDescription.map((line) => el('tspan', { dy: '1.2em', x: X_OFFSET }, line)),
|
|
100
|
+
);
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
const lineHeight = descriptionLines > 3 ? 12 : 10;
|
|
@@ -102,7 +108,7 @@ const renderGistCard = (gistData: GistData, options: CardOptions<GistCardOptions
|
|
|
102
108
|
const svgStars = iconWithLabel(icons.star, totalStars, 'starsCount', ICON_SIZE);
|
|
103
109
|
const svgForks = iconWithLabel(icons.fork, totalForks, 'forksCount', ICON_SIZE);
|
|
104
110
|
|
|
105
|
-
const languageName = language || '
|
|
111
|
+
const languageName = language || i18n.t('gistcard.unspecified-language');
|
|
106
112
|
const languageColor = getLanguageColor(languageName);
|
|
107
113
|
|
|
108
114
|
const svgLanguage = createLanguageNode(languageName, languageColor);
|
|
@@ -115,14 +121,14 @@ const renderGistCard = (gistData: GistData, options: CardOptions<GistCardOptions
|
|
|
115
121
|
ICON_SIZE + measureText(`${totalForks}`, 12),
|
|
116
122
|
],
|
|
117
123
|
gap: 25,
|
|
118
|
-
})
|
|
124
|
+
});
|
|
119
125
|
|
|
120
126
|
const header = show_owner ? nameWithOwner : name;
|
|
121
127
|
|
|
122
128
|
const card = new Card({
|
|
123
129
|
defaultTitle:
|
|
124
130
|
header.length > HEADER_MAX_LENGTH ? `${header.slice(0, HEADER_MAX_LENGTH)}...` : header,
|
|
125
|
-
titlePrefixIcon:
|
|
131
|
+
titlePrefixIcon: CARD_ICON.gist,
|
|
126
132
|
width: CARD_DEFAULT_WIDTH,
|
|
127
133
|
height,
|
|
128
134
|
border_radius,
|
|
@@ -130,22 +136,23 @@ const renderGistCard = (gistData: GistData, options: CardOptions<GistCardOptions
|
|
|
130
136
|
});
|
|
131
137
|
|
|
132
138
|
card.setCSS({
|
|
133
|
-
light: ({ textColor, iconColor }) =>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
.
|
|
148
|
-
|
|
139
|
+
light: ({ textColor, iconColor }) => [
|
|
140
|
+
rule('.description', {
|
|
141
|
+
font: font('regular', 'meta'),
|
|
142
|
+
fill: textColor,
|
|
143
|
+
...(browser_rendering ? wrappedTextStyles(textColor) : {}),
|
|
144
|
+
}),
|
|
145
|
+
rule('.gray', { font: font('regular', 'small'), fill: textColor }),
|
|
146
|
+
rule('.icon', { fill: iconColor }),
|
|
147
|
+
],
|
|
148
|
+
dark: ({ textColor, iconColor }) => [
|
|
149
|
+
rule('.description', {
|
|
150
|
+
fill: textColor,
|
|
151
|
+
...(browser_rendering ? wrappedTextStyles(textColor) : {}),
|
|
152
|
+
}),
|
|
153
|
+
rule('.gray', { fill: textColor }),
|
|
154
|
+
rule('.icon', { fill: iconColor }),
|
|
155
|
+
],
|
|
149
156
|
});
|
|
150
157
|
|
|
151
158
|
card.setHideBorder(hide_border);
|
|
@@ -154,16 +161,15 @@ const renderGistCard = (gistData: GistData, options: CardOptions<GistCardOptions
|
|
|
154
161
|
// shows has to be repeated here.
|
|
155
162
|
card.setAccessibilityLabel({
|
|
156
163
|
title: card.title,
|
|
157
|
-
desc: `${desc}.
|
|
164
|
+
desc: `${desc}. ${i18n.t('gistcard.language')}: ${languageName}, ${i18n.t(
|
|
165
|
+
'gistcard.stars',
|
|
166
|
+
)}: ${totalStars}, ${i18n.t('gistcard.forks')}: ${totalForks}`,
|
|
158
167
|
});
|
|
159
168
|
|
|
160
|
-
return card.render(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
${starAndForkCount}
|
|
165
|
-
</g>
|
|
166
|
-
`);
|
|
169
|
+
return card.render([
|
|
170
|
+
descriptionSvg,
|
|
171
|
+
el('g', { transform: `translate(30, ${height - 75})` }, starAndForkCount),
|
|
172
|
+
]);
|
|
167
173
|
};
|
|
168
174
|
|
|
169
175
|
export { renderGistCard };
|
package/src/cards/options.ts
CHANGED
package/src/cards/repo.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { I18n } from '../common/I18n.
|
|
6
|
-
import { icons } from '../common/icons.
|
|
7
|
-
import { buildSearchFilter, clampValue, parseEmojis } from '../common/ops.
|
|
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 { I18n } from '../common/I18n.ts';
|
|
6
|
+
import { icons } from '../common/icons.ts';
|
|
7
|
+
import { buildSearchFilter, clampValue, parseEmojis } from '../common/ops.ts';
|
|
8
8
|
import {
|
|
9
|
+
NUMBER_FORMATS,
|
|
9
10
|
countWrappedLines,
|
|
11
|
+
wrapTextMultiline,
|
|
10
12
|
createLanguageNode,
|
|
11
13
|
createTextNode,
|
|
12
14
|
flexLayout,
|
|
@@ -14,19 +16,31 @@ import {
|
|
|
14
16
|
measureText,
|
|
15
17
|
wrappedTextNode,
|
|
16
18
|
wrappedTextStyles,
|
|
17
|
-
} from '../common/render.
|
|
18
|
-
import type { RepositoryData } from '../fetchers/types.
|
|
19
|
-
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 { repoCardLocales } from '../translations.ts';
|
|
20
24
|
|
|
21
|
-
import type { CardOptions, CommonCardOptions } from './options.
|
|
25
|
+
import type { CardOptions, CommonCardOptions } from './options.ts';
|
|
22
26
|
|
|
23
27
|
const ICON_SIZE = 16;
|
|
24
|
-
const CARD_DEFAULT_WIDTH =
|
|
28
|
+
const CARD_DEFAULT_WIDTH = CARD_WIDTH.standard;
|
|
25
29
|
const X_OFFSET = 25;
|
|
26
|
-
const DESCRIPTION_FONT_SIZE =
|
|
30
|
+
const DESCRIPTION_FONT_SIZE = FONT_SIZE.meta;
|
|
27
31
|
const DESCRIPTION_LINE_HEIGHT_PX = 16;
|
|
28
32
|
const DESCRIPTION_MAX_LINES = 3;
|
|
29
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
|
+
|
|
30
44
|
interface RepoCardOptions extends CommonCardOptions {
|
|
31
45
|
locale: string;
|
|
32
46
|
show_owner: boolean;
|
|
@@ -42,7 +56,7 @@ interface RepoCardOptions extends CommonCardOptions {
|
|
|
42
56
|
}
|
|
43
57
|
|
|
44
58
|
interface RepoStatItem {
|
|
45
|
-
icon:
|
|
59
|
+
icon: Child;
|
|
46
60
|
label: string;
|
|
47
61
|
value: number | undefined;
|
|
48
62
|
id: string;
|
|
@@ -53,41 +67,37 @@ interface RepoStatItem {
|
|
|
53
67
|
/**
|
|
54
68
|
* Retrieves the repository description and wraps it to fit the card width.
|
|
55
69
|
*
|
|
56
|
-
* @param label The repository description.
|
|
57
|
-
* @param xOffset Horizontal offset of the badge.
|
|
58
70
|
* @returns Wrapped repo description SVG object.
|
|
59
71
|
*/
|
|
60
|
-
const getBadgeSVG = (label: string, xOffset = 0):
|
|
72
|
+
const getBadgeSVG = (label: string, xOffset = 0): MarkupElement => {
|
|
61
73
|
if (!Number.isFinite(xOffset)) {
|
|
62
|
-
throw new
|
|
74
|
+
throw new TypeError(`Invalid xOffset: "${xOffset}"`);
|
|
63
75
|
}
|
|
64
76
|
|
|
65
|
-
return
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
return el(
|
|
78
|
+
'g',
|
|
79
|
+
{ 'data-testid': 'badge', class: 'badge', transform: `translate(${320 + xOffset}, -18)` },
|
|
80
|
+
el('rect', { 'stroke-width': 1, width: 70, height: 20, x: -12, y: -14, ry: 10, rx: 10 }),
|
|
81
|
+
el(
|
|
82
|
+
'text',
|
|
83
|
+
{
|
|
84
|
+
x: 23,
|
|
85
|
+
y: -5,
|
|
86
|
+
'alignment-baseline': 'central',
|
|
87
|
+
'dominant-baseline': 'central',
|
|
88
|
+
'text-anchor': 'middle',
|
|
89
|
+
},
|
|
90
|
+
label,
|
|
91
|
+
),
|
|
92
|
+
);
|
|
78
93
|
};
|
|
79
94
|
|
|
80
95
|
/**
|
|
81
96
|
* Renders repository card details.
|
|
82
97
|
*
|
|
83
|
-
* @param repo Repository data.
|
|
84
|
-
* @param options Card options.
|
|
85
98
|
* @returns Repository card SVG object.
|
|
86
99
|
*/
|
|
87
|
-
const
|
|
88
|
-
repo: RepositoryData,
|
|
89
|
-
options: CardOptions<RepoCardOptions> = {},
|
|
90
|
-
): string => {
|
|
100
|
+
const renderCard = (repo: RepositoryData, options: CardOptions<RepoCardOptions> = {}): string => {
|
|
91
101
|
const {
|
|
92
102
|
name,
|
|
93
103
|
nameWithOwner,
|
|
@@ -121,7 +131,7 @@ const renderRepoCard = (
|
|
|
121
131
|
} = options;
|
|
122
132
|
|
|
123
133
|
const card_width =
|
|
124
|
-
card_width_input && !isNaN(card_width_input)
|
|
134
|
+
card_width_input && !Number.isNaN(card_width_input)
|
|
125
135
|
? card_width_input
|
|
126
136
|
: show.length >= 2
|
|
127
137
|
? CARD_DEFAULT_WIDTH + 30
|
|
@@ -132,10 +142,13 @@ const renderRepoCard = (
|
|
|
132
142
|
translations: repoCardLocales,
|
|
133
143
|
});
|
|
134
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
|
+
|
|
135
148
|
const repoFilter = encodeURIComponent(buildSearchFilter([nameWithOwner], []));
|
|
136
149
|
const encodedUsername = encodeURIComponent(username ?? '');
|
|
137
150
|
const STATS: Record<string, RepoStatItem> = {};
|
|
138
|
-
if (
|
|
151
|
+
if (shows('prs_authored')) {
|
|
139
152
|
STATS['prs_authored'] = {
|
|
140
153
|
icon: icons.prs,
|
|
141
154
|
label: i18n.t('repocard.prs-authored'),
|
|
@@ -144,7 +157,7 @@ const renderRepoCard = (
|
|
|
144
157
|
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=pullrequests`,
|
|
145
158
|
};
|
|
146
159
|
}
|
|
147
|
-
if (
|
|
160
|
+
if (shows('prs_commented')) {
|
|
148
161
|
STATS['prs_commented'] = {
|
|
149
162
|
icon: icons.comments,
|
|
150
163
|
label: i18n.t('repocard.prs-commented'),
|
|
@@ -153,7 +166,7 @@ const renderRepoCard = (
|
|
|
153
166
|
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
154
167
|
};
|
|
155
168
|
}
|
|
156
|
-
if (
|
|
169
|
+
if (shows('prs_reviewed')) {
|
|
157
170
|
STATS['prs_reviewed'] = {
|
|
158
171
|
icon: icons.reviews,
|
|
159
172
|
label: i18n.t('repocard.prs-reviewed'),
|
|
@@ -162,7 +175,7 @@ const renderRepoCard = (
|
|
|
162
175
|
link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${encodedUsername}+-author%3A${encodedUsername}&type=pullrequests`,
|
|
163
176
|
};
|
|
164
177
|
}
|
|
165
|
-
if (
|
|
178
|
+
if (shows('issues_authored')) {
|
|
166
179
|
STATS['issues_authored'] = {
|
|
167
180
|
icon: icons.issues,
|
|
168
181
|
label: i18n.t('repocard.issues-authored'),
|
|
@@ -171,7 +184,7 @@ const renderRepoCard = (
|
|
|
171
184
|
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&type=issues`,
|
|
172
185
|
};
|
|
173
186
|
}
|
|
174
|
-
if (
|
|
187
|
+
if (shows('issues_commented')) {
|
|
175
188
|
STATS['issues_commented'] = {
|
|
176
189
|
icon: icons.discussions_started,
|
|
177
190
|
label: i18n.t('repocard.issues-commented'),
|
|
@@ -199,16 +212,16 @@ const renderRepoCard = (
|
|
|
199
212
|
}),
|
|
200
213
|
);
|
|
201
214
|
|
|
202
|
-
const extraLHeight = parseInt(String(line_height), 10);
|
|
215
|
+
const extraLHeight = Number.parseInt(String(line_height), 10);
|
|
203
216
|
const lineHeight = 10;
|
|
204
217
|
const header = show_owner ? nameWithOwner : name;
|
|
205
|
-
const langName =
|
|
206
|
-
const langColor =
|
|
207
|
-
const desc = parseEmojis(description || '
|
|
218
|
+
const langName = primaryLanguage?.name || i18n.t('repocard.unspecified-language');
|
|
219
|
+
const langColor = primaryLanguage?.color || '#333';
|
|
220
|
+
const desc = parseEmojis(description || i18n.t('repocard.no-description'));
|
|
208
221
|
const descriptionBoxWidth = card_width - 2 * X_OFFSET;
|
|
209
222
|
|
|
210
223
|
let descriptionLinesCount: number;
|
|
211
|
-
let descriptionSvg:
|
|
224
|
+
let descriptionSvg: Child;
|
|
212
225
|
if (browser_rendering) {
|
|
213
226
|
// The browser performs the actual text wrapping inside the foreignObject;
|
|
214
227
|
// we only estimate the line count server-side so the SVG can reserve enough
|
|
@@ -240,23 +253,21 @@ const renderRepoCard = (
|
|
|
240
253
|
descriptionLinesCount = description_lines_count
|
|
241
254
|
? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES)
|
|
242
255
|
: multiLineDescription.length;
|
|
243
|
-
descriptionSvg =
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
</text>`;
|
|
256
|
+
descriptionSvg = el(
|
|
257
|
+
'text',
|
|
258
|
+
{ class: 'description', x: X_OFFSET, y: -5 },
|
|
259
|
+
multiLineDescription.map((line) => el('tspan', { dy: '1.2em', x: X_OFFSET }, line)),
|
|
260
|
+
);
|
|
249
261
|
}
|
|
250
262
|
|
|
251
|
-
const extraHeight =
|
|
252
|
-
? -7 + (Math.ceil(statItems.length / 2) + 1) * extraLHeight
|
|
253
|
-
: 0;
|
|
263
|
+
const extraHeight =
|
|
264
|
+
Object.keys(STATS).length > 0 ? -7 + (Math.ceil(statItems.length / 2) + 1) * extraLHeight : 0;
|
|
254
265
|
const height =
|
|
255
266
|
(descriptionLinesCount > 1 ? 120 : 110) + descriptionLinesCount * lineHeight + extraHeight;
|
|
256
267
|
|
|
257
268
|
const { lightColors, darkColors } = getLightDarkColors({ ...options, theme });
|
|
258
269
|
|
|
259
|
-
const svgLanguage = primaryLanguage ? createLanguageNode(langName, langColor) :
|
|
270
|
+
const svgLanguage = primaryLanguage ? createLanguageNode(langName, langColor) : undefined;
|
|
260
271
|
|
|
261
272
|
const totalStars = kFormatter(stargazerCount);
|
|
262
273
|
const totalForks = kFormatter(forkCount);
|
|
@@ -271,31 +282,31 @@ const renderRepoCard = (
|
|
|
271
282
|
ICON_SIZE + measureText(`${totalForks}`, 12),
|
|
272
283
|
],
|
|
273
284
|
gap: 25,
|
|
274
|
-
})
|
|
285
|
+
});
|
|
275
286
|
|
|
276
|
-
const extraRows: Array<
|
|
287
|
+
const extraRows: Array<Child> = [];
|
|
277
288
|
for (let i = 0; i < statItems.length; i += 2) {
|
|
278
289
|
extraRows.push(
|
|
279
290
|
flexLayout({
|
|
280
291
|
items: statItems.slice(i, i + 2),
|
|
281
292
|
gap: 210,
|
|
282
293
|
direction: 'row',
|
|
283
|
-
})
|
|
294
|
+
}),
|
|
284
295
|
);
|
|
285
296
|
}
|
|
286
|
-
const extraItems =
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
297
|
+
const extraItems = el(
|
|
298
|
+
'svg',
|
|
299
|
+
{ x: 0, y: 0 },
|
|
300
|
+
el(
|
|
301
|
+
'g',
|
|
302
|
+
{ transform: `translate(-3, ${height - 52 - extraHeight})` },
|
|
303
|
+
flexLayout({ items: extraRows, gap: extraLHeight, direction: 'column' }),
|
|
304
|
+
),
|
|
305
|
+
);
|
|
295
306
|
|
|
296
307
|
const card = new Card({
|
|
297
308
|
defaultTitle: header.length > 35 ? `${header.slice(0, 35)}...` : header,
|
|
298
|
-
titlePrefixIcon:
|
|
309
|
+
titlePrefixIcon: CARD_ICON.repo,
|
|
299
310
|
width: card_width,
|
|
300
311
|
height,
|
|
301
312
|
border_radius,
|
|
@@ -306,39 +317,36 @@ const renderRepoCard = (
|
|
|
306
317
|
card.setHideBorder(hide_border);
|
|
307
318
|
card.setHideTitle(false);
|
|
308
319
|
card.setCSS({
|
|
309
|
-
light: ({ textColor, iconColor }) =>
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
.
|
|
337
|
-
.
|
|
338
|
-
|
|
339
|
-
.stat { fill: ${textColor} }
|
|
340
|
-
.icon { fill: ${iconColor}; }
|
|
341
|
-
`,
|
|
320
|
+
light: ({ textColor, iconColor }) => [
|
|
321
|
+
rule('.description', {
|
|
322
|
+
font: font('regular', 'meta'),
|
|
323
|
+
fill: textColor,
|
|
324
|
+
...(browser_rendering ? wrappedTextStyles(textColor) : {}),
|
|
325
|
+
}),
|
|
326
|
+
rule('.gray', { font: font('regular', 'small'), fill: textColor }),
|
|
327
|
+
rule('.badge', { font: font('semibold', 'micro') }),
|
|
328
|
+
rule('.badge rect', { opacity: 0.2, stroke: textColor }),
|
|
329
|
+
rule('.badge text', { fill: textColor }),
|
|
330
|
+
rule('.stat', { font: font('regular', 'small'), fill: textColor }),
|
|
331
|
+
rule('.stagger', {
|
|
332
|
+
opacity: 0,
|
|
333
|
+
animation: 'fadeInAnimation 0.3s ease-in-out forwards',
|
|
334
|
+
}),
|
|
335
|
+
rule('.not_bold', { 'font-weight': FONT_WEIGHT.regular }),
|
|
336
|
+
rule('.bold', { 'font-weight': FONT_WEIGHT.semibold }),
|
|
337
|
+
rule('.icon', { fill: iconColor, display: 'block' }),
|
|
338
|
+
],
|
|
339
|
+
dark: ({ textColor, iconColor }) => [
|
|
340
|
+
rule('.description', {
|
|
341
|
+
fill: textColor,
|
|
342
|
+
...(browser_rendering ? wrappedTextStyles(textColor) : {}),
|
|
343
|
+
}),
|
|
344
|
+
rule('.gray', { fill: textColor }),
|
|
345
|
+
rule('.badge rect', { stroke: textColor }),
|
|
346
|
+
rule('.badge text', { fill: textColor }),
|
|
347
|
+
rule('.stat', { fill: textColor }),
|
|
348
|
+
rule('.icon', { fill: iconColor }),
|
|
349
|
+
],
|
|
342
350
|
});
|
|
343
351
|
|
|
344
352
|
const extraStatLabels = Object.values(STATS)
|
|
@@ -355,22 +363,28 @@ const renderRepoCard = (
|
|
|
355
363
|
desc: [`${desc}.`, primaryLanguage ? langName : '', extraStatLabels].filter(Boolean).join(', '),
|
|
356
364
|
});
|
|
357
365
|
|
|
358
|
-
return card.render(
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
<g transform="translate(30, ${height - 75 - extraHeight})">
|
|
370
|
-
${starAndForkCount}
|
|
371
|
-
</g>
|
|
372
|
-
${extraItems}
|
|
373
|
-
`);
|
|
366
|
+
return card.render([
|
|
367
|
+
isTemplate
|
|
368
|
+
? getBadgeSVG(i18n.t('repocard.template'), card_width - CARD_DEFAULT_WIDTH)
|
|
369
|
+
: isArchived
|
|
370
|
+
? getBadgeSVG(i18n.t('repocard.archived'), card_width - CARD_DEFAULT_WIDTH)
|
|
371
|
+
: undefined,
|
|
372
|
+
descriptionSvg,
|
|
373
|
+
el('g', { transform: `translate(30, ${height - 75 - extraHeight})` }, starAndForkCount),
|
|
374
|
+
extraItems,
|
|
375
|
+
]);
|
|
374
376
|
};
|
|
375
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
|
+
|
|
376
390
|
export { renderRepoCard };
|