@stats-forge/github-stats-forge-core 0.0.2 → 0.1.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.
Files changed (208) hide show
  1. package/README.md +0 -10
  2. package/build/api/api-result.d.ts +6 -7
  3. package/build/api/api-result.d.ts.map +1 -1
  4. package/build/api/api-result.js +4 -5
  5. package/build/api/gist.d.ts +3 -10
  6. package/build/api/gist.d.ts.map +1 -1
  7. package/build/api/gist.js +4 -11
  8. package/build/api/index.d.ts +12 -12
  9. package/build/api/params.d.ts +11 -8
  10. package/build/api/params.d.ts.map +1 -1
  11. package/build/api/params.js +16 -14
  12. package/build/api/pin.d.ts +3 -19
  13. package/build/api/pin.d.ts.map +1 -1
  14. package/build/api/pin.js +6 -22
  15. package/build/api/stats.d.ts +10 -4
  16. package/build/api/stats.d.ts.map +1 -1
  17. package/build/api/stats.js +6 -32
  18. package/build/api/top-langs.d.ts +12 -6
  19. package/build/api/top-langs.d.ts.map +1 -1
  20. package/build/api/top-langs.js +6 -26
  21. package/build/api/wakatime.d.ts +13 -5
  22. package/build/api/wakatime.d.ts.map +1 -1
  23. package/build/api/wakatime.js +4 -21
  24. package/build/calculateRank.d.ts +0 -9
  25. package/build/calculateRank.d.ts.map +1 -1
  26. package/build/calculateRank.js +12 -17
  27. package/build/cards/gist.d.ts +2 -4
  28. package/build/cards/gist.d.ts.map +1 -1
  29. package/build/cards/gist.js +27 -36
  30. package/build/cards/options.d.ts +1 -1
  31. package/build/cards/repo.d.ts +2 -4
  32. package/build/cards/repo.d.ts.map +1 -1
  33. package/build/cards/repo.js +61 -93
  34. package/build/cards/stats.d.ts +3 -8
  35. package/build/cards/stats.d.ts.map +1 -1
  36. package/build/cards/stats.js +119 -165
  37. package/build/cards/top-languages.d.ts +5 -31
  38. package/build/cards/top-languages.d.ts.map +1 -1
  39. package/build/cards/top-languages.js +176 -384
  40. package/build/cards/wakatime.d.ts +4 -6
  41. package/build/cards/wakatime.d.ts.map +1 -1
  42. package/build/cards/wakatime.js +118 -185
  43. package/build/common/Card.d.ts +14 -62
  44. package/build/common/Card.d.ts.map +1 -1
  45. package/build/common/Card.js +97 -199
  46. package/build/common/I18n.d.ts +0 -6
  47. package/build/common/I18n.d.ts.map +1 -1
  48. package/build/common/I18n.js +3 -6
  49. package/build/common/color.d.ts +4 -17
  50. package/build/common/color.d.ts.map +1 -1
  51. package/build/common/color.js +67 -49
  52. package/build/common/config.d.ts +4 -14
  53. package/build/common/config.d.ts.map +1 -1
  54. package/build/common/config.js +3 -16
  55. package/build/common/constants.d.ts +4 -2
  56. package/build/common/constants.d.ts.map +1 -1
  57. package/build/common/constants.js +3 -1
  58. package/build/common/date.d.ts +0 -2
  59. package/build/common/date.d.ts.map +1 -1
  60. package/build/common/date.js +0 -2
  61. package/build/common/emojiMap.d.ts +8 -0
  62. package/build/common/emojiMap.d.ts.map +1 -0
  63. package/build/common/emojiMap.js +10 -0
  64. package/build/common/emojiMap.json +1915 -0
  65. package/build/common/error.d.ts +1 -10
  66. package/build/common/error.d.ts.map +1 -1
  67. package/build/common/error.js +1 -10
  68. package/build/common/fmt.d.ts +3 -15
  69. package/build/common/fmt.d.ts.map +1 -1
  70. package/build/common/fmt.js +7 -35
  71. package/build/common/http.d.ts +2 -7
  72. package/build/common/http.d.ts.map +1 -1
  73. package/build/common/http.js +8 -17
  74. package/build/common/icons.d.ts +18 -20
  75. package/build/common/icons.d.ts.map +1 -1
  76. package/build/common/icons.js +103 -67
  77. package/build/common/languageColors.d.ts +0 -1
  78. package/build/common/languageColors.d.ts.map +1 -1
  79. package/build/common/languageColors.js +1 -4
  80. package/build/common/languageColors.json +18 -2
  81. package/build/common/ops.d.ts +3 -14
  82. package/build/common/ops.d.ts.map +1 -1
  83. package/build/common/ops.js +19 -30
  84. package/build/common/render.d.ts +28 -82
  85. package/build/common/render.d.ts.map +1 -1
  86. package/build/common/render.js +175 -211
  87. package/build/common/retryer.d.ts +2 -7
  88. package/build/common/retryer.d.ts.map +1 -1
  89. package/build/common/retryer.js +4 -11
  90. package/build/fetchers/gist.d.ts +2 -5
  91. package/build/fetchers/gist.d.ts.map +1 -1
  92. package/build/fetchers/gist.js +1 -5
  93. package/build/fetchers/index.d.ts +12 -12
  94. package/build/fetchers/repo.d.ts +2 -11
  95. package/build/fetchers/repo.d.ts.map +1 -1
  96. package/build/fetchers/repo.js +20 -33
  97. package/build/fetchers/stats.d.ts +2 -32
  98. package/build/fetchers/stats.d.ts.map +1 -1
  99. package/build/fetchers/stats.js +27 -86
  100. package/build/fetchers/top-languages.d.ts +2 -9
  101. package/build/fetchers/top-languages.d.ts.map +1 -1
  102. package/build/fetchers/top-languages.js +15 -31
  103. package/build/fetchers/types.d.ts +1 -1
  104. package/build/fetchers/wakatime.d.ts +2 -6
  105. package/build/fetchers/wakatime.d.ts.map +1 -1
  106. package/build/fetchers/wakatime.js +1 -5
  107. package/build/graphql/contributionsDocument.d.ts +3 -3
  108. package/build/graphql/contributionsDocument.d.ts.map +1 -1
  109. package/build/graphql/contributionsDocument.js +0 -1
  110. package/build/graphql/generated/gist.d.ts +1 -1
  111. package/build/graphql/generated/repo.d.ts +1 -1
  112. package/build/graphql/generated/stats.d.ts +3 -3
  113. package/build/graphql/generated/top-languages.d.ts +2 -2
  114. package/build/graphql/graphqlDocument.d.ts +0 -1
  115. package/build/graphql/graphqlDocument.d.ts.map +1 -1
  116. package/build/graphql/graphqlDocument.js +0 -1
  117. package/build/graphql/reposContributedToDocument.d.ts +4 -5
  118. package/build/graphql/reposContributedToDocument.d.ts.map +1 -1
  119. package/build/graphql/reposContributedToDocument.js +0 -2
  120. package/build/markup/css.d.ts +35 -0
  121. package/build/markup/css.d.ts.map +1 -0
  122. package/build/markup/css.js +64 -0
  123. package/build/markup/element.d.ts +41 -0
  124. package/build/markup/element.d.ts.map +1 -0
  125. package/build/markup/element.js +127 -0
  126. package/build/markup/html.d.ts +13 -0
  127. package/build/markup/html.d.ts.map +1 -0
  128. package/build/markup/html.js +27 -0
  129. package/build/markup/index.d.ts +9 -0
  130. package/build/markup/index.d.ts.map +1 -0
  131. package/build/markup/index.js +6 -0
  132. package/build/markup/serialize.d.ts +16 -0
  133. package/build/markup/serialize.d.ts.map +1 -0
  134. package/build/markup/serialize.js +17 -0
  135. package/build/themes/index.d.ts +332 -333
  136. package/build/themes/index.d.ts.map +1 -1
  137. package/build/themes/index.js +1 -1
  138. package/build/translations.d.ts +2 -6
  139. package/build/translations.d.ts.map +1 -1
  140. package/build/translations.js +621 -628
  141. package/package.json +17 -26
  142. package/src/api/api-result.ts +10 -9
  143. package/src/api/gist.ts +11 -18
  144. package/src/api/index.ts +12 -12
  145. package/src/api/params.ts +27 -21
  146. package/src/api/pin.ts +13 -28
  147. package/src/api/stats.ts +13 -39
  148. package/src/api/top-langs.ts +13 -33
  149. package/src/api/wakatime.ts +11 -28
  150. package/src/calculateRank.ts +12 -23
  151. package/src/cards/gist.ts +40 -43
  152. package/src/cards/options.ts +1 -1
  153. package/src/cards/repo.ts +97 -107
  154. package/src/cards/stats.ts +165 -184
  155. package/src/cards/top-languages.ts +297 -413
  156. package/src/cards/wakatime.ts +193 -221
  157. package/src/common/Card.ts +165 -217
  158. package/src/common/I18n.ts +3 -6
  159. package/src/common/color.ts +80 -57
  160. package/src/common/config.ts +5 -18
  161. package/src/common/constants.ts +5 -2
  162. package/src/common/date.ts +0 -2
  163. package/src/common/emojiMap.json +1915 -0
  164. package/src/common/emojiMap.ts +13 -0
  165. package/src/common/error.ts +2 -11
  166. package/src/common/fmt.ts +7 -44
  167. package/src/common/http.ts +9 -15
  168. package/src/common/icons.ts +117 -42
  169. package/src/common/languageColors.json +18 -2
  170. package/src/common/languageColors.ts +1 -4
  171. package/src/common/ops.ts +26 -34
  172. package/src/common/render.ts +279 -232
  173. package/src/common/retryer.ts +8 -15
  174. package/src/fetchers/gist.ts +8 -12
  175. package/src/fetchers/index.ts +12 -12
  176. package/src/fetchers/repo.ts +27 -40
  177. package/src/fetchers/stats.ts +44 -102
  178. package/src/fetchers/top-languages.ts +26 -40
  179. package/src/fetchers/types.ts +1 -1
  180. package/src/fetchers/wakatime.ts +5 -9
  181. package/src/graphql/contributionsDocument.ts +7 -5
  182. package/src/graphql/generated/gist.ts +1 -1
  183. package/src/graphql/generated/repo.ts +1 -1
  184. package/src/graphql/generated/stats.ts +2 -2
  185. package/src/graphql/generated/top-languages.ts +2 -2
  186. package/src/graphql/graphqlDocument.ts +0 -1
  187. package/src/graphql/reposContributedToDocument.ts +6 -7
  188. package/src/markup/css.ts +118 -0
  189. package/src/markup/element.ts +246 -0
  190. package/src/markup/html.ts +34 -0
  191. package/src/markup/index.ts +9 -0
  192. package/src/markup/serialize.ts +27 -0
  193. package/src/themes/index.ts +1 -1
  194. package/src/translations.ts +622 -629
  195. package/build/cards/index.d.ts +0 -11
  196. package/build/cards/index.d.ts.map +0 -1
  197. package/build/cards/index.js +0 -7
  198. package/build/common/html.d.ts +0 -8
  199. package/build/common/html.d.ts.map +0 -1
  200. package/build/common/html.js +0 -14
  201. package/build/index.d.ts +0 -15
  202. package/build/index.d.ts.map +0 -1
  203. package/build/index.js +0 -10
  204. package/src/_emoji-name-map.d.ts +0 -10
  205. package/src/_github-username-regex.d.ts +0 -4
  206. package/src/cards/index.ts +0 -21
  207. package/src/common/html.ts +0 -17
  208. package/src/index.ts +0 -18
@@ -1,12 +1,12 @@
1
1
  import * as z from 'zod/mini';
2
2
 
3
- import { DISPLAY_FORMATS, WAKATIME_LAYOUTS, renderWakatimeCard } from '../cards/wakatime.js';
4
- import type { CardConfig } from '../common/config.js';
5
- import { fetchWakatimeStats } from '../fetchers/wakatime.js';
3
+ import { DISPLAY_FORMATS, WAKATIME_LAYOUTS, renderWakatimeCard } from '../cards/wakatime.ts';
4
+ import type { CardConfig } from '../common/config.ts';
5
+ import { fetchWakatimeStats } from '../fetchers/wakatime.ts';
6
6
 
7
- import type { ApiResult } from './api-result.js';
8
- import { errorResult } from './api-result.js';
9
- import type { ApiQuery } from './params.js';
7
+ import type { ApiResult } from './api-result.ts';
8
+ import { errorResult } from './api-result.ts';
9
+ import type { ApiQuery } from './params.ts';
10
10
  import {
11
11
  booleanParam,
12
12
  enumParam,
@@ -18,7 +18,7 @@ import {
18
18
  parseParams,
19
19
  rawParam,
20
20
  safeParam,
21
- } from './params.js';
21
+ } from './params.ts';
22
22
 
23
23
  /** What the wakatime endpoint accepts, on top of the shared color params. */
24
24
  const wakatimeQuery = z.object({
@@ -47,32 +47,15 @@ type WakatimeApiQuery = ApiQuery<typeof wakatimeQuery>;
47
47
  *
48
48
  * WakaTime needs no GitHub token, so the config is read only for the transport it carries.
49
49
  *
50
- * @param query Raw query params, plus any of the shared color params.
51
- * @param query.username WakaTime username.
52
- * @param query.hide_border Whether to hide the card border.
53
- * @param query.card_width Card width.
54
- * @param query.line_height Line height between the languages.
55
- * @param query.hide_title Whether to hide the card title.
56
- * @param query.hide_progress Whether to hide the progress bars.
57
- * @param query.custom_title Card title.
58
- * @param query.locale Language the card is rendered in.
59
- * @param query.layout How the languages are laid out.
60
- * @param query.langs_count Number of languages to show.
61
- * @param query.hide Comma-separated languages to hide.
62
- * @param query.api_domain WakaTime instance the stats are read from.
63
- * @param query.border_radius Card border radius.
64
- * @param query.display_format Whether values are shown as time or percentages.
65
- * @param query.disable_animations Whether to disable the card animations.
66
- * @param config Deployment config supplying the transport.
67
50
  * @returns The rendered card, or a rendered error.
68
51
  */
69
52
  const renderWakatime = async (query: WakatimeApiQuery, config: CardConfig): Promise<ApiResult> => {
70
53
  let colors;
71
54
  try {
72
55
  colors = parseColorParams(query);
73
- } catch (err) {
56
+ } catch (error) {
74
57
  // A rejected color cannot be used to draw its own error card.
75
- return errorResult(err);
58
+ return errorResult(error);
76
59
  }
77
60
 
78
61
  try {
@@ -115,8 +98,8 @@ const renderWakatime = async (query: WakatimeApiQuery, config: CardConfig): Prom
115
98
  disable_animations,
116
99
  }),
117
100
  };
118
- } catch (err) {
119
- return errorResult(err, colors);
101
+ } catch (error) {
102
+ return errorResult(error, colors);
120
103
  }
121
104
  };
122
105
 
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Calculates the exponential cdf.
3
3
  *
4
- * @param x The value.
5
4
  * @returns The exponential cdf.
6
5
  */
7
6
  function exponential_cdf(x: number): number {
@@ -11,7 +10,6 @@ function exponential_cdf(x: number): number {
11
10
  /**
12
11
  * Calculates the log normal cdf.
13
12
  *
14
- * @param x The value.
15
13
  * @returns The log normal cdf.
16
14
  */
17
15
  function log_normal_cdf(x: number): number {
@@ -22,15 +20,6 @@ function log_normal_cdf(x: number): number {
22
20
  /**
23
21
  * Calculates the users rank.
24
22
  *
25
- * @param params Parameters on which the user's rank depends.
26
- * @param params.all_commits Whether `include_all_commits` was used.
27
- * @param params.commits Number of commits.
28
- * @param params.prs The number of pull requests.
29
- * @param params.issues The number of issues.
30
- * @param params.reviews The number of reviews.
31
- * @param params.repos Total number of repos (accepted for compatibility, unused in the calculation).
32
- * @param params.stars The number of stars.
33
- * @param params.followers The number of followers.
34
23
  * @returns The users rank.
35
24
  */
36
25
  function calculateRank({
@@ -51,18 +40,18 @@ function calculateRank({
51
40
  stars: number;
52
41
  followers: number;
53
42
  }): { level: string; percentile: number } {
54
- const COMMITS_MEDIAN = all_commits ? 1000 : 250,
55
- COMMITS_WEIGHT = 2;
56
- const PRS_MEDIAN = 50,
57
- PRS_WEIGHT = 3;
58
- const ISSUES_MEDIAN = 25,
59
- ISSUES_WEIGHT = 1;
60
- const REVIEWS_MEDIAN = 2,
61
- REVIEWS_WEIGHT = 1;
62
- const STARS_MEDIAN = 50,
63
- STARS_WEIGHT = 4;
64
- const FOLLOWERS_MEDIAN = 10,
65
- FOLLOWERS_WEIGHT = 1;
43
+ const COMMITS_MEDIAN = all_commits ? 1000 : 250;
44
+ const COMMITS_WEIGHT = 2;
45
+ const PRS_MEDIAN = 50;
46
+ const PRS_WEIGHT = 3;
47
+ const ISSUES_MEDIAN = 25;
48
+ const ISSUES_WEIGHT = 1;
49
+ const REVIEWS_MEDIAN = 2;
50
+ const REVIEWS_WEIGHT = 1;
51
+ const STARS_MEDIAN = 50;
52
+ const STARS_WEIGHT = 4;
53
+ const FOLLOWERS_MEDIAN = 10;
54
+ const FOLLOWERS_WEIGHT = 1;
66
55
 
67
56
  const TOTAL_WEIGHT =
68
57
  COMMITS_WEIGHT + PRS_WEIGHT + ISSUES_WEIGHT + REVIEWS_WEIGHT + STARS_WEIGHT + FOLLOWERS_WEIGHT;
package/src/cards/gist.ts CHANGED
@@ -1,22 +1,24 @@
1
- import { default as Card } from '../common/Card.js';
2
- import { getLightDarkColors } from '../common/color.js';
3
- import { kFormatter, wrapTextMultiline } from '../common/fmt.js';
4
- import { encodeHTML } from '../common/html.js';
5
- import { icons } from '../common/icons.js';
6
- import { getLanguageColor } from '../common/languageColors.js';
7
- import { parseEmojis } from '../common/ops.js';
1
+ import { Card } from '../common/Card.ts';
2
+ import { getLightDarkColors } from '../common/color.ts';
3
+ import { kFormatter } from '../common/fmt.ts';
4
+ import { icons } from '../common/icons.ts';
5
+ import { getLanguageColor } from '../common/languageColors.ts';
6
+ import { parseEmojis } from '../common/ops.ts';
8
7
  import {
9
8
  countWrappedLines,
9
+ wrapTextMultiline,
10
10
  createLanguageNode,
11
11
  flexLayout,
12
12
  iconWithLabel,
13
13
  measureText,
14
14
  wrappedTextNode,
15
15
  wrappedTextStyles,
16
- } from '../common/render.js';
17
- import type { GistData } from '../fetchers/types.js';
16
+ } from '../common/render.ts';
17
+ import type { GistData } from '../fetchers/types.ts';
18
+ import type { Child } from '../markup/index.ts';
19
+ import { el, rule } from '../markup/index.ts';
18
20
 
19
- import type { CardOptions, CommonCardOptions } from './options.js';
21
+ import type { CardOptions, CommonCardOptions } from './options.ts';
20
22
 
21
23
  const ICON_SIZE = 16;
22
24
  const CARD_DEFAULT_WIDTH = 400;
@@ -35,8 +37,6 @@ interface GistCardOptions extends CommonCardOptions {
35
37
  /**
36
38
  * Render gist card.
37
39
  *
38
- * @param gistData Gist data.
39
- * @param options Gist card options.
40
40
  * @returns Gist card.
41
41
  */
42
42
  const renderGistCard = (gistData: GistData, options: CardOptions<GistCardOptions> = {}): string => {
@@ -54,7 +54,7 @@ const renderGistCard = (gistData: GistData, options: CardOptions<GistCardOptions
54
54
  const desc = parseEmojis(description || 'No description provided');
55
55
 
56
56
  let descriptionLines: number;
57
- let descriptionSvg: string;
57
+ let descriptionSvg: Child;
58
58
  if (browser_rendering) {
59
59
  // The browser performs the actual text wrapping inside the foreignObject;
60
60
  // we only estimate the line count server-side so the SVG can reserve enough
@@ -86,12 +86,11 @@ const renderGistCard = (gistData: GistData, options: CardOptions<GistCardOptions
86
86
  linesLimit,
87
87
  );
88
88
  descriptionLines = multiLineDescription.length;
89
- descriptionSvg = multiLineDescription
90
- .map((line) => `<tspan dy="1.2em" x="${X_OFFSET}">${encodeHTML(line)}</tspan>`)
91
- .join('');
92
- descriptionSvg = `<text class="description" x="${X_OFFSET}" y="-5">
93
- ${descriptionSvg}
94
- </text>`;
89
+ descriptionSvg = el(
90
+ 'text',
91
+ { class: 'description', x: X_OFFSET, y: -5 },
92
+ multiLineDescription.map((line) => el('tspan', { dy: '1.2em', x: X_OFFSET }, line)),
93
+ );
95
94
  }
96
95
 
97
96
  const lineHeight = descriptionLines > 3 ? 12 : 10;
@@ -115,7 +114,7 @@ const renderGistCard = (gistData: GistData, options: CardOptions<GistCardOptions
115
114
  ICON_SIZE + measureText(`${totalForks}`, 12),
116
115
  ],
117
116
  gap: 25,
118
- }).join('');
117
+ });
119
118
 
120
119
  const header = show_owner ? nameWithOwner : name;
121
120
 
@@ -130,22 +129,23 @@ const renderGistCard = (gistData: GistData, options: CardOptions<GistCardOptions
130
129
  });
131
130
 
132
131
  card.setCSS({
133
- light: ({ textColor, iconColor }) => `
134
- .description {
135
- font: 400 ${DESCRIPTION_FONT_SIZE}px 'Segoe UI', Ubuntu, Sans-Serif;fill: ${textColor};
136
- ${browser_rendering ? wrappedTextStyles(textColor) : ''}
137
- }
138
- .gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
139
- .icon { fill: ${iconColor} }
140
- `,
141
- dark: ({ textColor, iconColor }) => `
142
- .description {
143
- fill: ${textColor};
144
- ${browser_rendering ? wrappedTextStyles(textColor) : ''}
145
- }
146
- .gray { fill: ${textColor} }
147
- .icon { fill: ${iconColor} }
148
- `,
132
+ light: ({ textColor, iconColor }) => [
133
+ rule('.description', {
134
+ font: `400 ${DESCRIPTION_FONT_SIZE}px 'Segoe UI', Ubuntu, Sans-Serif`,
135
+ fill: textColor,
136
+ ...(browser_rendering ? wrappedTextStyles(textColor) : {}),
137
+ }),
138
+ rule('.gray', { font: "400 12px 'Segoe UI', Ubuntu, Sans-Serif", fill: textColor }),
139
+ rule('.icon', { fill: iconColor }),
140
+ ],
141
+ dark: ({ textColor, iconColor }) => [
142
+ rule('.description', {
143
+ fill: textColor,
144
+ ...(browser_rendering ? wrappedTextStyles(textColor) : {}),
145
+ }),
146
+ rule('.gray', { fill: textColor }),
147
+ rule('.icon', { fill: iconColor }),
148
+ ],
149
149
  });
150
150
 
151
151
  card.setHideBorder(hide_border);
@@ -157,13 +157,10 @@ const renderGistCard = (gistData: GistData, options: CardOptions<GistCardOptions
157
157
  desc: `${desc}. Language: ${languageName}, Stars: ${totalStars}, Forks: ${totalForks}`,
158
158
  });
159
159
 
160
- return card.render(`
161
- ${descriptionSvg}
162
-
163
- <g transform="translate(30, ${height - 75})">
164
- ${starAndForkCount}
165
- </g>
166
- `);
160
+ return card.render([
161
+ descriptionSvg,
162
+ el('g', { transform: `translate(30, ${height - 75})` }, starAndForkCount),
163
+ ]);
167
164
  };
168
165
 
169
166
  export { renderGistCard };
@@ -1,4 +1,4 @@
1
- import type { ColorParams } from '../common/color.js';
1
+ import type { ColorParams } from '../common/color.ts';
2
2
 
3
3
  /**
4
4
  * Options every card accepts.
package/src/cards/repo.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { Card } from '../common/Card.js';
2
- import { getLightDarkColors } from '../common/color.js';
3
- import { kFormatter, wrapTextMultiline } from '../common/fmt.js';
4
- import { encodeHTML } from '../common/html.js';
5
- import { I18n } from '../common/I18n.js';
6
- import { icons } from '../common/icons.js';
7
- import { buildSearchFilter, clampValue, parseEmojis } from '../common/ops.js';
1
+ import { Card } from '../common/Card.ts';
2
+ import { getLightDarkColors } from '../common/color.ts';
3
+ import { kFormatter } from '../common/fmt.ts';
4
+ import { I18n } from '../common/I18n.ts';
5
+ import { icons } from '../common/icons.ts';
6
+ import { buildSearchFilter, clampValue, parseEmojis } from '../common/ops.ts';
8
7
  import {
9
8
  countWrappedLines,
9
+ wrapTextMultiline,
10
10
  createLanguageNode,
11
11
  createTextNode,
12
12
  flexLayout,
@@ -14,11 +14,13 @@ import {
14
14
  measureText,
15
15
  wrappedTextNode,
16
16
  wrappedTextStyles,
17
- } from '../common/render.js';
18
- import type { RepositoryData } from '../fetchers/types.js';
19
- import { repoCardLocales } from '../translations.js';
17
+ } from '../common/render.ts';
18
+ import type { RepositoryData } from '../fetchers/types.ts';
19
+ import type { Child, MarkupElement } from '../markup/index.ts';
20
+ import { el, rule } from '../markup/index.ts';
21
+ import { repoCardLocales } from '../translations.ts';
20
22
 
21
- import type { CardOptions, CommonCardOptions } from './options.js';
23
+ import type { CardOptions, CommonCardOptions } from './options.ts';
22
24
 
23
25
  const ICON_SIZE = 16;
24
26
  const CARD_DEFAULT_WIDTH = 400;
@@ -42,7 +44,7 @@ interface RepoCardOptions extends CommonCardOptions {
42
44
  }
43
45
 
44
46
  interface RepoStatItem {
45
- icon: string;
47
+ icon: Child;
46
48
  label: string;
47
49
  value: number | undefined;
48
50
  id: string;
@@ -53,35 +55,34 @@ interface RepoStatItem {
53
55
  /**
54
56
  * Retrieves the repository description and wraps it to fit the card width.
55
57
  *
56
- * @param label The repository description.
57
- * @param xOffset Horizontal offset of the badge.
58
58
  * @returns Wrapped repo description SVG object.
59
59
  */
60
- const getBadgeSVG = (label: string, xOffset = 0): string => {
60
+ const getBadgeSVG = (label: string, xOffset = 0): MarkupElement => {
61
61
  if (!Number.isFinite(xOffset)) {
62
- throw new Error(`Invalid xOffset: "${xOffset}"`);
62
+ throw new TypeError(`Invalid xOffset: "${xOffset}"`);
63
63
  }
64
64
 
65
- return `
66
- <g data-testid="badge" class="badge" transform="translate(${320 + xOffset}, -18)">
67
- <rect stroke-width="1" width="70" height="20" x="-12" y="-14" ry="10" rx="10"></rect>
68
- <text
69
- x="23" y="-5"
70
- alignment-baseline="central"
71
- dominant-baseline="central"
72
- text-anchor="middle"
73
- >
74
- ${encodeHTML(label)}
75
- </text>
76
- </g>
77
- `;
65
+ return el(
66
+ 'g',
67
+ { 'data-testid': 'badge', class: 'badge', transform: `translate(${320 + xOffset}, -18)` },
68
+ el('rect', { 'stroke-width': 1, width: 70, height: 20, x: -12, y: -14, ry: 10, rx: 10 }),
69
+ el(
70
+ 'text',
71
+ {
72
+ x: 23,
73
+ y: -5,
74
+ 'alignment-baseline': 'central',
75
+ 'dominant-baseline': 'central',
76
+ 'text-anchor': 'middle',
77
+ },
78
+ label,
79
+ ),
80
+ );
78
81
  };
79
82
 
80
83
  /**
81
84
  * Renders repository card details.
82
85
  *
83
- * @param repo Repository data.
84
- * @param options Card options.
85
86
  * @returns Repository card SVG object.
86
87
  */
87
88
  const renderRepoCard = (
@@ -121,7 +122,7 @@ const renderRepoCard = (
121
122
  } = options;
122
123
 
123
124
  const card_width =
124
- card_width_input && !isNaN(card_width_input)
125
+ card_width_input && !Number.isNaN(card_width_input)
125
126
  ? card_width_input
126
127
  : show.length >= 2
127
128
  ? CARD_DEFAULT_WIDTH + 30
@@ -199,16 +200,16 @@ const renderRepoCard = (
199
200
  }),
200
201
  );
201
202
 
202
- const extraLHeight = parseInt(String(line_height), 10);
203
+ const extraLHeight = Number.parseInt(String(line_height), 10);
203
204
  const lineHeight = 10;
204
205
  const header = show_owner ? nameWithOwner : name;
205
- const langName = (primaryLanguage && primaryLanguage.name) || 'Unspecified';
206
- const langColor = (primaryLanguage && primaryLanguage.color) || '#333';
206
+ const langName = primaryLanguage?.name || 'Unspecified';
207
+ const langColor = primaryLanguage?.color || '#333';
207
208
  const desc = parseEmojis(description || 'No description provided');
208
209
  const descriptionBoxWidth = card_width - 2 * X_OFFSET;
209
210
 
210
211
  let descriptionLinesCount: number;
211
- let descriptionSvg: string;
212
+ let descriptionSvg: Child;
212
213
  if (browser_rendering) {
213
214
  // The browser performs the actual text wrapping inside the foreignObject;
214
215
  // we only estimate the line count server-side so the SVG can reserve enough
@@ -240,23 +241,21 @@ const renderRepoCard = (
240
241
  descriptionLinesCount = description_lines_count
241
242
  ? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES)
242
243
  : multiLineDescription.length;
243
- descriptionSvg = multiLineDescription
244
- .map((line) => `<tspan dy="1.2em" x="${X_OFFSET}">${encodeHTML(line)}</tspan>`)
245
- .join('');
246
- descriptionSvg = `<text class="description" x="${X_OFFSET}" y="-5">
247
- ${descriptionSvg}
248
- </text>`;
244
+ descriptionSvg = el(
245
+ 'text',
246
+ { class: 'description', x: X_OFFSET, y: -5 },
247
+ multiLineDescription.map((line) => el('tspan', { dy: '1.2em', x: X_OFFSET }, line)),
248
+ );
249
249
  }
250
250
 
251
- const extraHeight = Object.keys(STATS).length
252
- ? -7 + (Math.ceil(statItems.length / 2) + 1) * extraLHeight
253
- : 0;
251
+ const extraHeight =
252
+ Object.keys(STATS).length > 0 ? -7 + (Math.ceil(statItems.length / 2) + 1) * extraLHeight : 0;
254
253
  const height =
255
254
  (descriptionLinesCount > 1 ? 120 : 110) + descriptionLinesCount * lineHeight + extraHeight;
256
255
 
257
256
  const { lightColors, darkColors } = getLightDarkColors({ ...options, theme });
258
257
 
259
- const svgLanguage = primaryLanguage ? createLanguageNode(langName, langColor) : '';
258
+ const svgLanguage = primaryLanguage ? createLanguageNode(langName, langColor) : undefined;
260
259
 
261
260
  const totalStars = kFormatter(stargazerCount);
262
261
  const totalForks = kFormatter(forkCount);
@@ -271,27 +270,27 @@ const renderRepoCard = (
271
270
  ICON_SIZE + measureText(`${totalForks}`, 12),
272
271
  ],
273
272
  gap: 25,
274
- }).join('');
273
+ });
275
274
 
276
- const extraRows: Array<string> = [];
275
+ const extraRows: Array<Child> = [];
277
276
  for (let i = 0; i < statItems.length; i += 2) {
278
277
  extraRows.push(
279
278
  flexLayout({
280
279
  items: statItems.slice(i, i + 2),
281
280
  gap: 210,
282
281
  direction: 'row',
283
- }).join(''),
282
+ }),
284
283
  );
285
284
  }
286
- const extraItems = `
287
- <svg x="0" y="0"><g transform="translate(-3, ${height - 52 - extraHeight})">
288
- ${flexLayout({
289
- items: extraRows,
290
- gap: extraLHeight,
291
- direction: 'column',
292
- }).join('')}
293
- </g></svg>
294
- `;
285
+ const extraItems = el(
286
+ 'svg',
287
+ { x: 0, y: 0 },
288
+ el(
289
+ 'g',
290
+ { transform: `translate(-3, ${height - 52 - extraHeight})` },
291
+ flexLayout({ items: extraRows, gap: extraLHeight, direction: 'column' }),
292
+ ),
293
+ );
295
294
 
296
295
  const card = new Card({
297
296
  defaultTitle: header.length > 35 ? `${header.slice(0, 35)}...` : header,
@@ -306,39 +305,36 @@ const renderRepoCard = (
306
305
  card.setHideBorder(hide_border);
307
306
  card.setHideTitle(false);
308
307
  card.setCSS({
309
- light: ({ textColor, iconColor }) => `
310
- .description {
311
- font: 400 ${DESCRIPTION_FONT_SIZE}px 'Segoe UI', Ubuntu, Sans-Serif;fill: ${textColor};
312
- ${browser_rendering ? wrappedTextStyles(textColor) : ''}
313
- }
314
- .gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
315
- .badge { font: 600 11px 'Segoe UI', Ubuntu, Sans-Serif; }
316
- .badge rect { opacity: 0.2; stroke: ${textColor} }
317
- .badge text { fill: ${textColor} }
318
-
319
- .stat { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
320
- .stagger {
321
- opacity: 0;
322
- animation: fadeInAnimation 0.3s ease-in-out forwards;
323
- }
324
- .not_bold { font-weight: 400 }
325
- .bold { font-weight: 700 }
326
- .icon {
327
- fill: ${iconColor};
328
- display: block;
329
- }
330
- `,
331
- dark: ({ textColor, iconColor }) => `
332
- .description {
333
- fill: ${textColor};
334
- ${browser_rendering ? wrappedTextStyles(textColor) : ''}
335
- }
336
- .gray { fill: ${textColor} }
337
- .badge rect { stroke: ${textColor} }
338
- .badge text { fill: ${textColor} }
339
- .stat { fill: ${textColor} }
340
- .icon { fill: ${iconColor}; }
341
- `,
308
+ light: ({ textColor, iconColor }) => [
309
+ rule('.description', {
310
+ font: `400 ${DESCRIPTION_FONT_SIZE}px 'Segoe UI', Ubuntu, Sans-Serif`,
311
+ fill: textColor,
312
+ ...(browser_rendering ? wrappedTextStyles(textColor) : {}),
313
+ }),
314
+ rule('.gray', { font: "400 12px 'Segoe UI', Ubuntu, Sans-Serif", fill: textColor }),
315
+ rule('.badge', { font: "600 11px 'Segoe UI', Ubuntu, Sans-Serif" }),
316
+ rule('.badge rect', { opacity: 0.2, stroke: textColor }),
317
+ rule('.badge text', { fill: textColor }),
318
+ rule('.stat', { font: "400 12px 'Segoe UI', Ubuntu, Sans-Serif", fill: textColor }),
319
+ rule('.stagger', {
320
+ opacity: 0,
321
+ animation: 'fadeInAnimation 0.3s ease-in-out forwards',
322
+ }),
323
+ rule('.not_bold', { 'font-weight': 400 }),
324
+ rule('.bold', { 'font-weight': 700 }),
325
+ rule('.icon', { fill: iconColor, display: 'block' }),
326
+ ],
327
+ dark: ({ textColor, iconColor }) => [
328
+ rule('.description', {
329
+ fill: textColor,
330
+ ...(browser_rendering ? wrappedTextStyles(textColor) : {}),
331
+ }),
332
+ rule('.gray', { fill: textColor }),
333
+ rule('.badge rect', { stroke: textColor }),
334
+ rule('.badge text', { fill: textColor }),
335
+ rule('.stat', { fill: textColor }),
336
+ rule('.icon', { fill: iconColor }),
337
+ ],
342
338
  });
343
339
 
344
340
  const extraStatLabels = Object.values(STATS)
@@ -355,22 +351,16 @@ const renderRepoCard = (
355
351
  desc: [`${desc}.`, primaryLanguage ? langName : '', extraStatLabels].filter(Boolean).join(', '),
356
352
  });
357
353
 
358
- return card.render(`
359
- ${
360
- isTemplate
361
- ? getBadgeSVG(i18n.t('repocard.template'), card_width - CARD_DEFAULT_WIDTH)
362
- : isArchived
363
- ? getBadgeSVG(i18n.t('repocard.archived'), card_width - CARD_DEFAULT_WIDTH)
364
- : ''
365
- }
366
-
367
- ${descriptionSvg}
368
-
369
- <g transform="translate(30, ${height - 75 - extraHeight})">
370
- ${starAndForkCount}
371
- </g>
372
- ${extraItems}
373
- `);
354
+ return card.render([
355
+ isTemplate
356
+ ? getBadgeSVG(i18n.t('repocard.template'), card_width - CARD_DEFAULT_WIDTH)
357
+ : isArchived
358
+ ? getBadgeSVG(i18n.t('repocard.archived'), card_width - CARD_DEFAULT_WIDTH)
359
+ : undefined,
360
+ descriptionSvg,
361
+ el('g', { transform: `translate(30, ${height - 75 - extraHeight})` }, starAndForkCount),
362
+ extraItems,
363
+ ]);
374
364
  };
375
365
 
376
366
  export { renderRepoCard };