@stats-forge/github-stats-forge-core 0.0.1 → 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,14 +1,15 @@
1
- import { Card } from '../common/Card.js';
2
- import { getLightDarkColors, isPrefixedHexColor } from '../common/color.js';
3
- import { encodeHTML } from '../common/html.js';
4
- import { I18n } from '../common/I18n.js';
5
- import { getLanguageColor } from '../common/languageColors.js';
6
- import { clampValue, lowercaseTrim } from '../common/ops.js';
7
- import { createProgressNode, flexLayout } from '../common/render.js';
8
- import type { WakaTimeData, WakaTimeLang } from '../fetchers/types.js';
9
- import { wakatimeCardLocales } from '../translations.js';
10
-
11
- import type { CardOptions, CommonCardOptions } from './options.js';
1
+ import { Card } from '../common/Card.ts';
2
+ import { getLightDarkColors, isPrefixedHexColor } from '../common/color.ts';
3
+ import { I18n } from '../common/I18n.ts';
4
+ import { getLanguageColor } from '../common/languageColors.ts';
5
+ import { clampValue, lowercaseTrim } from '../common/ops.ts';
6
+ import { createProgressNode, flexLayout } from '../common/render.ts';
7
+ import type { WakaTimeData, WakaTimeLang } from '../fetchers/types.ts';
8
+ import type { Child, CssChild, MarkupElement } from '../markup/index.ts';
9
+ import { atRule, cssComment, el, rule } from '../markup/index.ts';
10
+ import { wakatimeCardLocales } from '../translations.ts';
11
+
12
+ import type { CardOptions, CommonCardOptions } from './options.ts';
12
13
 
13
14
  const DEFAULT_CARD_WIDTH = 495;
14
15
  const MIN_CARD_WIDTH = 250;
@@ -44,22 +45,14 @@ interface WakaTimeOptions extends CommonCardOptions {
44
45
  /**
45
46
  * Creates the no coding activity SVG node.
46
47
  *
47
- * @param props The function properties.
48
- * @param props.text No coding activity translated text.
49
48
  * @returns No coding activity SVG node string.
50
49
  */
51
- const noCodingActivityNode = ({ text }: { text: string }): string => {
52
- return `
53
- <text x="25" y="11" class="stat bold">${encodeHTML(text)}</text>
54
- `;
55
- };
50
+ const noCodingActivityNode = ({ text }: { text: string }): MarkupElement =>
51
+ el('text', { x: 25, y: 11, class: 'stat bold' }, text);
56
52
 
57
53
  /**
58
54
  * Format language value.
59
55
  *
60
- * @param args The function arguments.
61
- * @param args.lang The language object.
62
- * @param args.display_format The display format of the language node.
63
56
  * @returns The formatted language value.
64
57
  */
65
58
  const formatLanguageValue = ({
@@ -68,18 +61,11 @@ const formatLanguageValue = ({
68
61
  }: {
69
62
  display_format: DisplayFormat;
70
63
  lang: WakaTimeLang;
71
- }): string => {
72
- return display_format === 'percent' ? `${lang.percent.toFixed(2)} %` : lang.text;
73
- };
64
+ }): string => (display_format === 'percent' ? `${lang.percent.toFixed(2)} %` : lang.text);
74
65
 
75
66
  /**
76
67
  * Create compact WakaTime layout.
77
68
  *
78
- * @param args The function arguments.
79
- * @param args.lang The languages array.
80
- * @param args.x The x position of the language node.
81
- * @param args.y The y position of the language node.
82
- * @param args.display_format The display format of the language node.
83
69
  * @returns The compact layout language SVG node.
84
70
  */
85
71
  const createCompactLangNode = ({
@@ -92,35 +78,32 @@ const createCompactLangNode = ({
92
78
  x: number;
93
79
  y: number;
94
80
  display_format: DisplayFormat;
95
- }): string => {
81
+ }): MarkupElement => {
96
82
  if (!Number.isFinite(x)) {
97
- throw new Error(`Invalid x: "${x}"`);
83
+ throw new TypeError(`Invalid x: "${x}"`);
98
84
  }
99
85
  if (!Number.isFinite(y)) {
100
- throw new Error(`Invalid y: "${y}"`);
86
+ throw new TypeError(`Invalid y: "${y}"`);
101
87
  }
102
88
 
103
89
  const color = getLanguageColor(lang.name);
104
90
  const value = formatLanguageValue({ display_format, lang });
105
91
 
106
- return `
107
- <g transform="translate(${x}, ${y})">
108
- <circle cx="5" cy="6" r="5" fill="${color}" />
109
- <text data-testid="lang-name" x="15" y="10" class='lang-name'>
110
- ${encodeHTML(lang.name)} - ${encodeHTML(value)}
111
- </text>
112
- </g>
113
- `;
92
+ return el(
93
+ 'g',
94
+ { transform: `translate(${x}, ${y})` },
95
+ el('circle', { cx: 5, cy: 6, r: 5, fill: color }),
96
+ el(
97
+ 'text',
98
+ { 'data-testid': 'lang-name', x: 15, y: 10, class: 'lang-name' },
99
+ `${lang.name} - ${value}`,
100
+ ),
101
+ );
114
102
  };
115
103
 
116
104
  /**
117
105
  * Create WakaTime language text node item.
118
106
  *
119
- * @param args The function arguments.
120
- * @param args.langs The language objects.
121
- * @param args.y The y position of the language node.
122
- * @param args.display_format The display format of the language node.
123
- * @param args.card_width Width in px of the card.
124
107
  * @returns The language text node items.
125
108
  */
126
109
  const createLanguageTextNode = ({
@@ -133,7 +116,7 @@ const createLanguageTextNode = ({
133
116
  y: number;
134
117
  display_format: DisplayFormat;
135
118
  card_width: number;
136
- }): Array<string> => {
119
+ }): Array<Child> => {
137
120
  const LEFT_X = 25;
138
121
  const RIGHT_X_BASE = 230;
139
122
  const rightOffset = (card_width - DEFAULT_CARD_WIDTH) / 2;
@@ -153,14 +136,6 @@ const createLanguageTextNode = ({
153
136
  /**
154
137
  * Create WakaTime text item.
155
138
  *
156
- * @param args The function arguments.
157
- * @param args.id The id of the text node item.
158
- * @param args.label The label of the text node item.
159
- * @param args.value The value of the text node item.
160
- * @param args.index The index of the text node item.
161
- * @param args.percent Percentage of the text node item.
162
- * @param args.hideProgress Whether to hide the progress bar.
163
- * @param args.progressBarWidth The width of the progress bar.
164
139
  * @returns The text SVG node.
165
140
  */
166
141
  const createTextNode = ({
@@ -179,87 +154,89 @@ const createTextNode = ({
179
154
  percent: number;
180
155
  hideProgress?: boolean | undefined;
181
156
  progressBarWidth: number;
182
- }): string => {
157
+ }): MarkupElement => {
183
158
  if (!Number.isFinite(index)) {
184
- throw new Error(`Invalid index: "${index}"`);
159
+ throw new TypeError(`Invalid index: "${index}"`);
185
160
  }
186
161
  if (!Number.isFinite(progressBarWidth)) {
187
- throw new Error(`Invalid progressBarWidth: "${progressBarWidth}"`);
162
+ throw new TypeError(`Invalid progressBarWidth: "${progressBarWidth}"`);
188
163
  }
189
164
 
190
165
  const staggerDelay = (index + 3) * 150;
191
- const cardProgress = hideProgress
192
- ? null
193
- : createProgressNode({
166
+
167
+ return el(
168
+ 'g',
169
+ {
170
+ class: 'stagger',
171
+ style: `animation-delay: ${staggerDelay}ms`,
172
+ transform: 'translate(25, 0)',
173
+ },
174
+ el('text', { class: 'stat bold', y: 12.5, 'data-testid': id }, `${label}:`),
175
+ el(
176
+ 'text',
177
+ {
178
+ class: 'stat',
179
+ x: hideProgress ? HIDDEN_PROGRESSBAR_PADDING : PROGRESSBAR_PADDING + progressBarWidth,
180
+ y: 12.5,
181
+ },
182
+ value,
183
+ ),
184
+ !hideProgress &&
185
+ createProgressNode({
194
186
  x: 110,
195
187
  y: 4,
196
188
  progress: percent,
197
189
  width: progressBarWidth,
198
190
  delay: staggerDelay + 300,
199
- });
200
-
201
- return `
202
- <g class="stagger" style="animation-delay: ${staggerDelay}ms" transform="translate(25, 0)">
203
- <text class="stat bold" y="12.5" data-testid="${encodeHTML(id)}">${encodeHTML(label)}:</text>
204
- <text
205
- class="stat"
206
- x="${hideProgress ? HIDDEN_PROGRESSBAR_PADDING : PROGRESSBAR_PADDING + progressBarWidth}"
207
- y="12.5"
208
- >${encodeHTML(value)}</text>
209
- ${String(cardProgress)}
210
- </g>
211
- `;
191
+ }),
192
+ );
212
193
  };
213
194
 
214
195
  /**
215
196
  * Recalculating percentages so that, compact layout's progress bar does not break when
216
197
  * hiding languages.
217
- *
218
- * @param languages The languages array.
219
198
  */
220
199
  const recalculatePercentages = (languages: Array<WakaTimeLang>): void => {
221
- const totalSum = languages.reduce((totalSum, language) => totalSum + language.percent, 0);
222
- const weight = +(100 / totalSum).toFixed(2);
223
- languages.forEach((language) => {
224
- language.percent = +(language.percent * weight).toFixed(2);
225
- });
200
+ const totalSum = languages.reduce((sum, language) => sum + language.percent, 0);
201
+ const weight = Number((100 / totalSum).toFixed(2));
202
+ for (const language of languages) {
203
+ language.percent = Number((language.percent * weight).toFixed(2));
204
+ }
226
205
  };
227
206
 
228
207
  /**
229
208
  * Retrieves CSS styles for a card.
230
209
  *
231
- * @param colors The colors to use for the card.
232
- * @param colors.textColor The text color.
233
210
  * @returns Card CSS styles.
234
211
  */
235
- const getStyles = ({ textColor }: { textColor: string }): string => {
212
+ const getStyles = ({ textColor }: { textColor: string }): Array<CssChild> => {
236
213
  if (!isPrefixedHexColor(textColor)) {
237
214
  throw new Error(`Invalid text color: "${textColor}"`);
238
215
  }
239
216
 
240
- return `
241
- .stat {
242
- font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${textColor};
243
- }
244
- @supports(-moz-appearance: auto) {
245
- /* Selector detects Firefox */
246
- .stat { font-size:12px; }
247
- }
248
- .stagger {
249
- opacity: 0;
250
- animation: fadeInAnimation 0.3s ease-in-out forwards;
251
- }
252
- .not_bold { font-weight: 400 }
253
- .bold { font-weight: 700 }
254
- `;
217
+ return [
218
+ rule('.stat', {
219
+ font: `600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif`,
220
+ fill: textColor,
221
+ }),
222
+ atRule(
223
+ '@supports(-moz-appearance: auto)',
224
+ cssComment('Selector detects Firefox'),
225
+ rule('.stat', { 'font-size': '12px' }),
226
+ ),
227
+ rule('.stagger', { opacity: 0, animation: 'fadeInAnimation 0.3s ease-in-out forwards' }),
228
+ rule('.not_bold', { 'font-weight': 400 }),
229
+ rule('.bold', { 'font-weight': 700 }),
230
+ ];
255
231
  };
256
232
 
233
+ /** A bar the same color as the text behind it would be invisible, so it goes transparent. */
234
+ const progressBackground = (titleColor: string, textColor: string): string =>
235
+ textColor === titleColor ? '#fff0' : textColor;
236
+
257
237
  /**
258
238
  * Normalize incoming width (string or number) and clamp to minimum.
259
239
  *
260
- * @param args The function arguments.
261
- * @param args.layout The incoming layout value.
262
- * @param args.value The incoming width value.
263
240
  * @returns The normalized width value.
264
241
  */
265
242
  const normalizeCardWidth = ({
@@ -269,7 +246,7 @@ const normalizeCardWidth = ({
269
246
  value?: number | undefined;
270
247
  layout?: WakaTimeLayout | undefined;
271
248
  }): number => {
272
- if (value === undefined || isNaN(value)) {
249
+ if (value === undefined || Number.isNaN(value)) {
273
250
  return DEFAULT_CARD_WIDTH;
274
251
  }
275
252
  return Math.max(layout === 'compact' ? COMPACT_LAYOUT_MIN_WIDTH : MIN_CARD_WIDTH, value);
@@ -278,13 +255,11 @@ const normalizeCardWidth = ({
278
255
  /**
279
256
  * Renders WakaTime card.
280
257
  *
281
- * @param stats WakaTime stats.
282
- * @param options Card options.
283
258
  * @returns WakaTime card SVG.
284
259
  */
285
260
  const renderWakatimeCard = (
286
261
  stats: Partial<WakaTimeData> = {},
287
- options: CardOptions<WakaTimeOptions> = { hide: [] },
262
+ options: CardOptions<WakaTimeOptions> = {},
288
263
  ): string => {
289
264
  let { languages = [] } = stats;
290
265
  const {
@@ -321,7 +296,7 @@ const renderWakatimeCard = (
321
296
  translations: wakatimeCardLocales,
322
297
  });
323
298
 
324
- const lheight = parseInt(String(line_height), 10);
299
+ const lheight = Number.parseInt(String(line_height), 10);
325
300
 
326
301
  const langsCount = clampValue(langs_count, 1, langs_count);
327
302
 
@@ -331,11 +306,10 @@ const renderWakatimeCard = (
331
306
  .filter((language) => language.hours || language.minutes)
332
307
  .slice(0, langsCount);
333
308
 
334
- // Calculate the card height depending on how many items there are
335
- // but if rank circle is visible clamp the minimum height to `150`
309
+ // A short language list would otherwise draw a stub card, so 150 is the floor.
336
310
  let height = Math.max(45 + (filteredLanguages.length + 1) * lheight, 150);
337
311
 
338
- let finalLayout: string;
312
+ let finalLayout: Child;
339
313
 
340
314
  // RENDER COMPACT LAYOUT
341
315
  if (layout === 'compact') {
@@ -345,87 +319,94 @@ const renderWakatimeCard = (
345
319
  // progressOffset holds the previous language's width and used to offset the next language
346
320
  // so that we can stack them one after another, like this: [--][----][---]
347
321
  let progressOffset = 0;
348
- const compactProgressBar = filteredLanguages
349
- .map((language) => {
350
- const progress = ((width - COMPACT_LAYOUT_PROGRESSBAR_PADDING) * language.percent) / 100;
351
-
352
- const languageColor = getLanguageColor(language.name);
353
-
354
- const output = `
355
- <rect
356
- mask="url(#rect-mask)"
357
- data-testid="lang-progress"
358
- x="${progressOffset}"
359
- y="0"
360
- width="${progress}"
361
- height="8"
362
- fill="${languageColor}"
363
- />
364
- `;
365
- progressOffset += progress;
366
- return output;
367
- })
368
- .join('');
369
-
370
- finalLayout = `
371
- <mask id="rect-mask">
372
- <rect x="${COMPACT_LAYOUT_PROGRESSBAR_PADDING}" y="0" width="${width - 2 * COMPACT_LAYOUT_PROGRESSBAR_PADDING}" height="8" fill="white" rx="5" />
373
- </mask>
374
- ${compactProgressBar}
375
- ${
376
- filteredLanguages.length
377
- ? createLanguageTextNode({
378
- y: 25,
379
- langs: filteredLanguages,
380
- display_format,
381
- card_width: normalizedWidth,
382
- }).join('')
383
- : noCodingActivityNode({
384
- text: stats.is_coding_activity_visible
385
- ? stats.is_other_usage_visible
386
- ? i18n.t('wakatimecard.nocodingactivity')
387
- : i18n.t('wakatimecard.nocodedetails')
388
- : i18n.t('wakatimecard.notpublic'),
389
- })
390
- }
391
- `;
322
+ const compactProgressBar = filteredLanguages.map((language) => {
323
+ const progress = ((width - COMPACT_LAYOUT_PROGRESSBAR_PADDING) * language.percent) / 100;
324
+ const x = progressOffset;
325
+ progressOffset += progress;
326
+
327
+ return el('rect', {
328
+ mask: 'url(#rect-mask)',
329
+ 'data-testid': 'lang-progress',
330
+ x,
331
+ y: 0,
332
+ width: progress,
333
+ height: 8,
334
+ fill: getLanguageColor(language.name),
335
+ });
336
+ });
337
+
338
+ finalLayout = [
339
+ el(
340
+ 'mask',
341
+ { id: 'rect-mask' },
342
+ el('rect', {
343
+ x: COMPACT_LAYOUT_PROGRESSBAR_PADDING,
344
+ y: 0,
345
+ width: width - 2 * COMPACT_LAYOUT_PROGRESSBAR_PADDING,
346
+ height: 8,
347
+ fill: 'white',
348
+ rx: 5,
349
+ }),
350
+ ),
351
+ compactProgressBar,
352
+ filteredLanguages.length > 0
353
+ ? createLanguageTextNode({
354
+ y: 25,
355
+ langs: filteredLanguages,
356
+ display_format,
357
+ card_width: normalizedWidth,
358
+ })
359
+ : noCodingActivityNode({
360
+ text: stats.is_coding_activity_visible
361
+ ? stats.is_other_usage_visible
362
+ ? i18n.t('wakatimecard.nocodingactivity')
363
+ : i18n.t('wakatimecard.nocodedetails')
364
+ : i18n.t('wakatimecard.notpublic'),
365
+ }),
366
+ ];
392
367
  } else {
393
368
  finalLayout = flexLayout({
394
- items: filteredLanguages.length
395
- ? filteredLanguages.map((language, index) => {
396
- return createTextNode({
397
- id: language.name,
398
- label: language.name,
399
- value: formatLanguageValue({ display_format, lang: language }),
400
- index,
401
- percent: language.percent,
402
- hideProgress: hide_progress,
403
- progressBarWidth: normalizedWidth - TOTAL_TEXT_WIDTH,
404
- });
405
- })
406
- : [
407
- noCodingActivityNode({
408
- text: stats.is_coding_activity_visible
409
- ? stats.is_other_usage_visible
410
- ? i18n.t('wakatimecard.nocodingactivity')
411
- : i18n.t('wakatimecard.nocodedetails')
412
- : i18n.t('wakatimecard.notpublic'),
413
- }),
414
- ],
369
+ items:
370
+ filteredLanguages.length > 0
371
+ ? filteredLanguages.map((language, index) =>
372
+ createTextNode({
373
+ id: language.name,
374
+ label: language.name,
375
+ value: formatLanguageValue({ display_format, lang: language }),
376
+ index,
377
+ percent: language.percent,
378
+ hideProgress: hide_progress,
379
+ progressBarWidth: normalizedWidth - TOTAL_TEXT_WIDTH,
380
+ }),
381
+ )
382
+ : [
383
+ noCodingActivityNode({
384
+ text: stats.is_coding_activity_visible
385
+ ? stats.is_other_usage_visible
386
+ ? i18n.t('wakatimecard.nocodingactivity')
387
+ : i18n.t('wakatimecard.nocodedetails')
388
+ : i18n.t('wakatimecard.notpublic'),
389
+ }),
390
+ ],
415
391
  gap: lheight,
416
392
  direction: 'column',
417
- }).join('');
393
+ });
418
394
  }
419
395
 
420
396
  // Get title range text
421
397
  let titleText = i18n.t('wakatimecard.title');
422
398
  switch (stats.range) {
423
- case 'last_7_days':
399
+ case 'last_7_days': {
424
400
  titleText += ` (${i18n.t('wakatimecard.last7days')})`;
425
401
  break;
426
- case 'last_year':
402
+ }
403
+ case 'last_year': {
427
404
  titleText += ` (${i18n.t('wakatimecard.lastyear')})`;
428
405
  break;
406
+ }
407
+ default: {
408
+ break;
409
+ }
429
410
  }
430
411
 
431
412
  const card = new Card({
@@ -444,58 +425,49 @@ const renderWakatimeCard = (
444
425
  card.setHideBorder(hide_border);
445
426
  card.setHideTitle(hide_title);
446
427
  card.setCSS({
447
- light: ({ titleColor, textColor }) => `
448
- ${getStyles({ textColor })}
449
- @keyframes slideInAnimation {
450
- from {
451
- width: 0;
452
- }
453
- to {
454
- width: calc(100%-100px);
455
- }
456
- }
457
- @keyframes growWidthAnimation {
458
- from {
459
- width: 0;
460
- }
461
- to {
462
- width: 100%;
463
- }
464
- }
465
- .lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
466
- #rect-mask rect{
467
- animation: slideInAnimation 1s ease-in-out forwards;
468
- }
469
- .lang-progress{
470
- animation: growWidthAnimation 0.6s ease-in-out forwards;
471
- fill: ${titleColor};
472
- }
473
- .progress-background { fill: ${textColor === titleColor ? '#fff0' /* transparent */ : textColor}; }
474
- `,
475
- dark: ({ titleColor, textColor }) => `
476
- ${getStyles({ textColor })}
477
- .lang-name { fill: ${textColor} }
478
- .lang-progress { fill: ${titleColor}; }
479
- .progress-background { fill: ${textColor === titleColor ? '#fff0' /* transparent */ : textColor}; }
480
- `,
428
+ light: ({ titleColor, textColor }) => [
429
+ getStyles({ textColor }),
430
+ atRule(
431
+ '@keyframes slideInAnimation',
432
+ rule('from', { width: 0 }),
433
+ // Invalid on purpose — `calc` needs spaces around its `-`. Browsers drop the
434
+ // declaration and animate to the rect's own width, which is the reveal the mask wants.
435
+ rule('to', { width: 'calc(100%-100px)' }),
436
+ ),
437
+ atRule(
438
+ '@keyframes growWidthAnimation',
439
+ rule('from', { width: 0 }),
440
+ rule('to', { width: '100%' }),
441
+ ),
442
+ rule('.lang-name', { font: "400 11px 'Segoe UI', Ubuntu, Sans-Serif", fill: textColor }),
443
+ rule('#rect-mask rect', { animation: 'slideInAnimation 1s ease-in-out forwards' }),
444
+ rule('.lang-progress', {
445
+ animation: 'growWidthAnimation 0.6s ease-in-out forwards',
446
+ fill: titleColor,
447
+ }),
448
+ rule('.progress-background', { fill: progressBackground(titleColor, textColor) }),
449
+ ],
450
+ dark: ({ titleColor, textColor }) => [
451
+ getStyles({ textColor }),
452
+ rule('.lang-name', { fill: textColor }),
453
+ rule('.lang-progress', { fill: titleColor }),
454
+ rule('.progress-background', { fill: progressBackground(titleColor, textColor) }),
455
+ ],
481
456
  });
482
457
 
483
458
  // `role="img"` hides the inner text from assistive tech, so everything the card
484
459
  // shows has to be repeated here.
485
460
  card.setAccessibilityLabel({
486
461
  title: card.title,
487
- desc: filteredLanguages.length
488
- ? filteredLanguages
489
- .map((lang) => `${lang.name}: ${formatLanguageValue({ display_format, lang })}`)
490
- .join(', ')
491
- : i18n.t('wakatimecard.nocodingactivity'),
462
+ desc:
463
+ filteredLanguages.length > 0
464
+ ? filteredLanguages
465
+ .map((lang) => `${lang.name}: ${formatLanguageValue({ display_format, lang })}`)
466
+ .join(', ')
467
+ : i18n.t('wakatimecard.nocodingactivity'),
492
468
  });
493
469
 
494
- return card.render(`
495
- <svg x="0" y="0" width="100%">
496
- ${finalLayout}
497
- </svg>
498
- `);
470
+ return card.render(el('svg', { x: 0, y: 0, width: '100%' }, finalLayout));
499
471
  };
500
472
 
501
473
  export { DISPLAY_FORMATS, WAKATIME_LAYOUTS, renderWakatimeCard };