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