@dytsou/github-readme-stats 1.0.1

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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/api/gist.js +98 -0
  3. package/api/index.js +146 -0
  4. package/api/pin.js +114 -0
  5. package/api/status/pat-info.js +193 -0
  6. package/api/status/up.js +129 -0
  7. package/api/top-langs.js +163 -0
  8. package/api/wakatime.js +129 -0
  9. package/package.json +102 -0
  10. package/readme.md +412 -0
  11. package/src/calculateRank.js +87 -0
  12. package/src/cards/gist.js +151 -0
  13. package/src/cards/index.js +4 -0
  14. package/src/cards/repo.js +199 -0
  15. package/src/cards/stats.js +607 -0
  16. package/src/cards/top-languages.js +968 -0
  17. package/src/cards/types.d.ts +67 -0
  18. package/src/cards/wakatime.js +482 -0
  19. package/src/common/Card.js +294 -0
  20. package/src/common/I18n.js +41 -0
  21. package/src/common/access.js +69 -0
  22. package/src/common/api-utils.js +221 -0
  23. package/src/common/blacklist.js +10 -0
  24. package/src/common/cache.js +153 -0
  25. package/src/common/color.js +194 -0
  26. package/src/common/envs.js +15 -0
  27. package/src/common/error.js +84 -0
  28. package/src/common/fmt.js +90 -0
  29. package/src/common/html.js +43 -0
  30. package/src/common/http.js +24 -0
  31. package/src/common/icons.js +63 -0
  32. package/src/common/index.js +13 -0
  33. package/src/common/languageColors.json +651 -0
  34. package/src/common/log.js +14 -0
  35. package/src/common/ops.js +124 -0
  36. package/src/common/render.js +261 -0
  37. package/src/common/retryer.js +97 -0
  38. package/src/common/worker-adapter.js +148 -0
  39. package/src/common/worker-env.js +48 -0
  40. package/src/fetchers/gist.js +114 -0
  41. package/src/fetchers/repo.js +118 -0
  42. package/src/fetchers/stats.js +350 -0
  43. package/src/fetchers/top-languages.js +192 -0
  44. package/src/fetchers/types.d.ts +118 -0
  45. package/src/fetchers/wakatime.js +109 -0
  46. package/src/index.js +2 -0
  47. package/src/translations.js +1105 -0
  48. package/src/worker.ts +116 -0
  49. package/themes/README.md +229 -0
  50. package/themes/index.js +467 -0
package/readme.md ADDED
@@ -0,0 +1,412 @@
1
+ # GitHub Readme Stats
2
+
3
+ Dynamically generated GitHub stats cards for your README.
4
+
5
+ <div>
6
+ <h1>GitHub Readme Stats</h1>
7
+ <p>Get dynamically generated GitHub stats on your READMEs!</p>
8
+ </div>
9
+
10
+ <details>
11
+ <summary>Table of contents</summary>
12
+
13
+ - [Quick Start](#quick-start)
14
+ - [GitHub Stats Card](#github-stats-card)
15
+ - [Top Languages Card](#top-languages-card)
16
+ - [Repository Card](#repository-card)
17
+ - [Gist Card](#gist-card)
18
+ - [WakaTime Card](#wakatime-card)
19
+ - [Deployment](#deployment)
20
+ - [Configuration](#configuration)
21
+ - [Support](#support)
22
+
23
+ </details>
24
+
25
+ ## Quick Start
26
+
27
+ Add this to your README (replace `YOUR_USERNAME` with your GitHub username):
28
+
29
+ ```markdown
30
+ ![GitHub Stats](https://YOUR-INSTANCE.WORKERS.DEV/api?username=YOUR_USERNAME)
31
+ ```
32
+
33
+ > **Note:** Replace `YOUR-INSTANCE.WORKERS.DEV` with your deployed Cloudflare Workers instance URL and `YOUR_USERNAME` with your GitHub username.
34
+
35
+ ---
36
+
37
+ ## Important Notes
38
+
39
+ > [!IMPORTANT]
40
+ > The GitHub API allows 5k requests per hour per account. Public instances may hit rate limits. We use caching to prevent this. For better control, [deploy your own instance](#deployment).
41
+
42
+ > [!WARNING]
43
+ > By default, cards only show statistics from public repositories. To include private repository statistics, deploy your own instance with a GitHub Personal Access Token.
44
+
45
+ ---
46
+
47
+ ## GitHub Stats Card
48
+
49
+ Display your GitHub statistics including stars, commits, pull requests, and more.
50
+
51
+ ### Basic Usage
52
+
53
+ ```markdown
54
+ ![GitHub Stats](https://YOUR-INSTANCE.WORKERS.DEV/api?username=YOUR_USERNAME)
55
+ ```
56
+
57
+ ### Common Options
58
+
59
+ | Parameter | Description | Example |
60
+ |-----------|-------------|---------|
61
+ | `username` | GitHub username (required) | `anuraghazra` |
62
+ | `theme` | Theme name | `dark`, `radical`, `merko`, etc. |
63
+ | `hide` | Hide specific stats | `stars,commits,prs` |
64
+ | `show` | Show additional stats | `reviews,discussions_started` |
65
+ | `show_icons` | Display icons | `true` |
66
+ | `hide_border` | Hide card border | `true` |
67
+ | `bg_color` | Background color (hex) | `1a1b27` |
68
+ | `title_color` | Title color (hex) | `fff` |
69
+ | `text_color` | Text color (hex) | `9f9f9f` |
70
+ | `border_color` | Border color (hex) | `e4e2e2` |
71
+ | `locale` | Language | `en`, `es`, `fr`, etc. |
72
+ | `cache_seconds` | Cache duration (21600-86400) | `21600` |
73
+
74
+ ### Stats Card Specific Options
75
+
76
+ | Parameter | Description | Default |
77
+ |-----------|-------------|---------|
78
+ | `hide_title` | Hide card title | `false` |
79
+ | `hide_rank` | Hide rank badge | `false` |
80
+ | `rank_icon` | Rank icon style | `default`, `github`, `percentile` |
81
+ | `card_width` | Card width in pixels | `500` |
82
+ | `include_all_commits` | Count all commits (not just current year) | `false` |
83
+ | `commits_year` | Filter commits by year | Current year |
84
+ | `exclude_repo` | Exclude repositories | Comma-separated list |
85
+ | `custom_title` | Custom card title | `<username> GitHub Stats` |
86
+ | `number_format` | Number format | `short` (e.g., `6.6k`) or `long` (e.g., `6626`) |
87
+
88
+ ### Examples
89
+
90
+ **Hide specific stats:**
91
+ ```markdown
92
+ ![GitHub Stats](https://YOUR-INSTANCE.WORKERS.DEV/api?username=YOUR_USERNAME&hide=contribs,prs)
93
+ ```
94
+
95
+ **Show icons:**
96
+ ```markdown
97
+ ![GitHub Stats](https://YOUR-INSTANCE.WORKERS.DEV/api?username=YOUR_USERNAME&show_icons=true)
98
+ ```
99
+
100
+ **Use a theme:**
101
+ ```markdown
102
+ ![GitHub Stats](https://YOUR-INSTANCE.WORKERS.DEV/api?username=YOUR_USERNAME&theme=radical)
103
+ ```
104
+
105
+ **Custom colors:**
106
+ ```markdown
107
+ ![GitHub Stats](https://YOUR-INSTANCE.WORKERS.DEV/api?username=YOUR_USERNAME&bg_color=151515&title_color=fff&text_color=9f9f9f)
108
+ ```
109
+
110
+ ### Available Themes
111
+
112
+ Popular themes include: `dark`, `radical`, `merko`, `gruvbox`, `tokyonight`, `onedark`, `cobalt`, `synthwave`, `highcontrast`, `dracula`, `transparent`, and more.
113
+
114
+ See [all available themes](themes/README.md) for the complete list.
115
+
116
+ ### Responsive Themes
117
+
118
+ Use GitHub's theme context tags for automatic dark/light mode:
119
+
120
+ ```markdown
121
+ [![GitHub Stats Dark](https://YOUR-INSTANCE.WORKERS.DEV/api?username=YOUR_USERNAME&theme=dark#gh-dark-mode-only)](https://github.com/YOUR_USERNAME)
122
+ [![GitHub Stats Light](https://YOUR-INSTANCE.WORKERS.DEV/api?username=YOUR_USERNAME&theme=default#gh-light-mode-only)](https://github.com/YOUR_USERNAME)
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Top Languages Card
128
+
129
+ Display your most frequently used programming languages.
130
+
131
+ ### Basic Usage
132
+
133
+ ```markdown
134
+ ![Top Languages](https://YOUR-INSTANCE.WORKERS.DEV/api/top-langs?username=YOUR_USERNAME)
135
+ ```
136
+
137
+ ### Options
138
+
139
+ | Parameter | Description | Default |
140
+ |-----------|-------------|---------|
141
+ | `layout` | Layout style | `normal`, `compact`, `donut`, `donut-vertical`, `pie` |
142
+ | `langs_count` | Number of languages to show (1-20) | `5` |
143
+ | `hide` | Hide specific languages | Comma-separated list |
144
+ | `exclude_repo` | Exclude repositories | Comma-separated list |
145
+ | `size_weight` | Weight for byte count in ranking | `1` |
146
+ | `count_weight` | Weight for repo count in ranking | `0` |
147
+ | `stats_format` | Display format | `percentages` or `bytes` |
148
+ | `hide_progress` | Hide progress bars | `false` |
149
+
150
+ ### Examples
151
+
152
+ **Compact layout:**
153
+ ```markdown
154
+ ![Top Languages](https://YOUR-INSTANCE.WORKERS.DEV/api/top-langs?username=YOUR_USERNAME&layout=compact)
155
+ ```
156
+
157
+ **Donut chart:**
158
+ ```markdown
159
+ ![Top Languages](https://YOUR-INSTANCE.WORKERS.DEV/api/top-langs?username=YOUR_USERNAME&layout=donut)
160
+ ```
161
+
162
+ **Show more languages:**
163
+ ```markdown
164
+ ![Top Languages](https://YOUR-INSTANCE.WORKERS.DEV/api/top-langs?username=YOUR_USERNAME&langs_count=8)
165
+ ```
166
+
167
+ **Hide specific languages:**
168
+ ```markdown
169
+ ![Top Languages](https://YOUR-INSTANCE.WORKERS.DEV/api/top-langs?username=YOUR_USERNAME&hide=javascript,html)
170
+ ```
171
+
172
+ ### Language Ranking Algorithm
173
+
174
+ Languages are ranked using:
175
+ ```
176
+ ranking_index = (byte_count ^ size_weight) * (repo_count ^ count_weight)
177
+ ```
178
+
179
+ - Default (`size_weight=1, count_weight=0`): Orders by byte count
180
+ - Recommended (`size_weight=0.5, count_weight=0.5`): Uses both byte and repo count
181
+ - Repo-based (`size_weight=0, count_weight=1`): Orders by repo count
182
+
183
+ ---
184
+
185
+ ## Repository Card
186
+
187
+ Pin additional repositories beyond GitHub's 6-repo limit.
188
+
189
+ ### Basic Usage
190
+
191
+ ```markdown
192
+ ![Repository Card](https://YOUR-INSTANCE.WORKERS.DEV/api/pin?username=YOUR_USERNAME&repo=REPO_NAME)
193
+ ```
194
+
195
+ ### Options
196
+
197
+ | Parameter | Description | Default |
198
+ |-----------|-------------|---------|
199
+ | `show_owner` | Show repository owner | `false` |
200
+ | `description_lines_count` | Number of description lines (1-3) | Auto |
201
+
202
+ ### Example
203
+
204
+ ```markdown
205
+ ![Repository Card](https://YOUR-INSTANCE.WORKERS.DEV/api/pin?username=YOUR_USERNAME&repo=github-readme-stats&show_owner=true)
206
+ ```
207
+
208
+ ---
209
+
210
+ ## Gist Card
211
+
212
+ Display GitHub Gists in your README.
213
+
214
+ ### Basic Usage
215
+
216
+ ```markdown
217
+ ![Gist Card](https://YOUR-INSTANCE.WORKERS.DEV/api/gist?id=GIST_ID)
218
+ ```
219
+
220
+ ### Options
221
+
222
+ | Parameter | Description | Default |
223
+ |-----------|-------------|---------|
224
+ | `show_owner` | Show gist owner | `false` |
225
+
226
+ ### Example
227
+
228
+ ```markdown
229
+ ![Gist Card](https://YOUR-INSTANCE.WORKERS.DEV/api/gist?id=bbfce31e0217a3689c8d961a356cb10d&show_owner=true)
230
+ ```
231
+
232
+ ---
233
+
234
+ ## WakaTime Card
235
+
236
+ Display your WakaTime coding statistics.
237
+
238
+ > [!WARNING]
239
+ > Your WakaTime profile must be public. Enable both "Display code time publicly" and "Display languages, editors, os, categories publicly" in your WakaTime settings.
240
+
241
+ ### Basic Usage
242
+
243
+ ```markdown
244
+ ![WakaTime Stats](https://YOUR-INSTANCE.WORKERS.DEV/api/wakatime?username=YOUR_WAKATIME_USERNAME)
245
+ ```
246
+
247
+ ### Options
248
+
249
+ | Parameter | Description | Default |
250
+ |-----------|-------------|---------|
251
+ | `layout` | Layout style | `default`, `compact` |
252
+ | `langs_count` | Limit number of languages | All languages |
253
+ | `hide_progress` | Hide progress bars | `false` |
254
+ | `display_format` | Display format | `time` or `percent` |
255
+ | `api_domain` | Custom API domain | `wakatime.com` |
256
+
257
+ ### Example
258
+
259
+ ```markdown
260
+ ![WakaTime Stats](https://YOUR-INSTANCE.WORKERS.DEV/api/wakatime?username=YOUR_USERNAME&layout=compact)
261
+ ```
262
+
263
+ ---
264
+
265
+ ## Deployment
266
+
267
+ ### Prerequisites
268
+
269
+ 1. **GitHub Personal Access Token (PAT)**
270
+ - Go to [GitHub Settings > Developer Settings > Personal Access Tokens](https://github.com/settings/tokens)
271
+ - Create a new token (classic) with `repo` and `read:user` scopes
272
+ - Copy the token
273
+
274
+ ### Deploy to Cloudflare Workers
275
+
276
+ 1. **Fork this repository**
277
+
278
+ 2. **Install dependencies:**
279
+ ```bash
280
+ pnpm install
281
+ ```
282
+
283
+ > **Note:** This project uses [pnpm](https://pnpm.io/) as the package manager. If you don't have pnpm installed, you can install it with `npm install -g pnpm`.
284
+
285
+ 3. **Configure `wrangler.toml`:**
286
+ ```toml
287
+ name = "github-readme-stats"
288
+ main = "src/worker.ts"
289
+ compatibility_date = "2025-12-04"
290
+ compatibility_flags = ["nodejs_compat"]
291
+
292
+ [observability]
293
+ enabled = true
294
+ head_sampling_rate = 1
295
+
296
+ [observability.logs]
297
+ enabled = true
298
+ head_sampling_rate = 1
299
+ persist = true
300
+ invocation_logs = true
301
+
302
+ [observability.traces]
303
+ enabled = true
304
+ persist = true
305
+ head_sampling_rate = 1
306
+
307
+ [vars]
308
+ PAT_1 = "your_pat_token_here"
309
+ ```
310
+
311
+ > [!WARNING]
312
+ > For production, use Cloudflare secrets instead of `[vars]`:
313
+ > ```bash
314
+ > wrangler secret put PAT_1
315
+ > ```
316
+
317
+ 4. **Deploy:**
318
+
319
+ **Option A: Manual deployment**
320
+ ```bash
321
+ npx wrangler deploy
322
+ ```
323
+
324
+ **Option B: GitHub Actions (Recommended)**
325
+
326
+ Set up the following secrets in your GitHub repository:
327
+ - `CLOUDFLARE_API_TOKEN`: Your Cloudflare API token (create at [Cloudflare Dashboard > My Profile > API Tokens](https://dash.cloudflare.com/profile/api-tokens))
328
+ - `CLOUDFLARE_ACCOUNT_ID`: Your Cloudflare Account ID (found in the right sidebar of your Cloudflare dashboard)
329
+ - `PAT_1`: Your GitHub Personal Access Token (optional, if not set in `wrangler.toml`)
330
+
331
+ The workflow will automatically deploy on every push to `main`/`master` branch, or you can trigger it manually from the Actions tab.
332
+
333
+ 5. **Your instance will be available at:**
334
+ ```
335
+ https://your-worker-name.your-subdomain.workers.dev
336
+ ```
337
+
338
+ 6. **Update your README URLs** to use your new domain!
339
+
340
+ ### Environment Variables
341
+
342
+ Configure your instance using these environment variables:
343
+
344
+ | Variable | Description | Example |
345
+ |----------|-------------|---------|
346
+ | `PAT_1`, `PAT_2`, etc. | GitHub Personal Access Tokens | `ghp_...` |
347
+ | `CACHE_SECONDS` | Cache duration in seconds (0 to disable) | `21600` |
348
+ | `WHITELIST` | Comma-separated allowed usernames | `user1,user2` |
349
+ | `GIST_WHITELIST` | Comma-separated allowed Gist IDs | `id1,id2` |
350
+ | `EXCLUDE_REPO` | Comma-separated repositories to exclude | `repo1,repo2` |
351
+ | `FETCH_MULTI_PAGE_STARS` | Fetch all starred repos | `true` or `false` |
352
+
353
+ > [!WARNING]
354
+ > Redeploy your instance after changing environment variables for changes to take effect.
355
+
356
+ ### Keep Your Fork Updated
357
+
358
+ Use GitHub's [Sync Fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork) feature or the [pull](https://github.com/wei/pull) package to keep your fork up to date.
359
+
360
+ ---
361
+
362
+ ## Configuration
363
+
364
+ ### Available Locales
365
+
366
+ The cards support multiple languages. Set the `locale` parameter to use:
367
+
368
+ `ar`, `az`, `bn`, `bg`, `my`, `ca`, `cn`, `zh-tw`, `cs`, `nl`, `en`, `fil`, `fi`, `fr`, `de`, `el`, `he`, `hi`, `hu`, `id`, `it`, `ja`, `kr`, `ml`, `np`, `no`, `fa`, `pl`, `pt-br`, `pt-pt`, `ro`, `ru`, `sa`, `sr`, `sr-latn`, `sk`, `es`, `sw`, `se`, `ta`, `th`, `tr`, `uk-ua`, `ur`, `uz`, `vi`
369
+
370
+ Example:
371
+ ```markdown
372
+ ![GitHub Stats](https://YOUR-INSTANCE.WORKERS.DEV/api?username=YOUR_USERNAME&locale=es)
373
+ ```
374
+
375
+ ### Caching
376
+
377
+ Default cache durations:
378
+ - Stats card: 24 hours
379
+ - Top languages card: 144 hours (6 days)
380
+ - Repository card: 240 hours (10 days)
381
+ - Gist card: 48 hours (2 days)
382
+ - WakaTime card: 24 hours
383
+
384
+ Override with `cache_seconds` parameter (min: 21600, max: 86400) or set `CACHE_SECONDS` environment variable.
385
+
386
+ ### Aligning Cards Side by Side
387
+
388
+ Use HTML with `align` attribute:
389
+
390
+ ```html
391
+ <a href="https://github.com/YOUR_USERNAME">
392
+ <img height=200 align="center" src="https://YOUR-INSTANCE.WORKERS.DEV/api?username=YOUR_USERNAME" />
393
+ </a>
394
+ <a href="https://github.com/YOUR_USERNAME">
395
+ <img height=200 align="center" src="https://YOUR-INSTANCE.WORKERS.DEV/api/top-langs?username=YOUR_USERNAME&layout=compact" />
396
+ </a>
397
+ ```
398
+
399
+ ---
400
+
401
+ ## Support
402
+
403
+ Contributions are welcome! If you find this project useful:
404
+
405
+ - ⭐ Star the repository
406
+ - 🐛 Report bugs
407
+ - 💡 Suggest features
408
+ - 📖 Improve documentation
409
+
410
+ Made with ❤️ and JavaScript.
411
+
412
+ [![Powered by Cloudflare Workers](https://img.shields.io/badge/Powered%20by-Cloudflare%20Workers-F38020?logo=cloudflare&logoColor=white)](https://workers.cloudflare.com/)
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Calculates the exponential cdf.
3
+ *
4
+ * @param {number} x The value.
5
+ * @returns {number} The exponential cdf.
6
+ */
7
+ function exponential_cdf(x) {
8
+ return 1 - 2 ** -x;
9
+ }
10
+
11
+ /**
12
+ * Calculates the log normal cdf.
13
+ *
14
+ * @param {number} x The value.
15
+ * @returns {number} The log normal cdf.
16
+ */
17
+ function log_normal_cdf(x) {
18
+ // approximation
19
+ return x / (1 + x);
20
+ }
21
+
22
+ /**
23
+ * Calculates the users rank.
24
+ *
25
+ * @param {object} params Parameters on which the user's rank depends.
26
+ * @param {boolean} params.all_commits Whether `include_all_commits` was used.
27
+ * @param {number} params.commits Number of commits.
28
+ * @param {number} params.prs The number of pull requests.
29
+ * @param {number} params.issues The number of issues.
30
+ * @param {number} params.reviews The number of reviews.
31
+ * @param {number} params.repos Total number of repos.
32
+ * @param {number} params.stars The number of stars.
33
+ * @param {number} params.followers The number of followers.
34
+ * @returns {{ level: string, percentile: number }} The users rank.
35
+ */
36
+ function calculateRank({
37
+ all_commits,
38
+ commits,
39
+ prs,
40
+ issues,
41
+ reviews,
42
+ // eslint-disable-next-line no-unused-vars
43
+ repos, // unused
44
+ stars,
45
+ followers,
46
+ }) {
47
+ const COMMITS_MEDIAN = all_commits ? 1000 : 250,
48
+ COMMITS_WEIGHT = 2;
49
+ const PRS_MEDIAN = 50,
50
+ PRS_WEIGHT = 3;
51
+ const ISSUES_MEDIAN = 25,
52
+ ISSUES_WEIGHT = 1;
53
+ const REVIEWS_MEDIAN = 2,
54
+ REVIEWS_WEIGHT = 1;
55
+ const STARS_MEDIAN = 50,
56
+ STARS_WEIGHT = 4;
57
+ const FOLLOWERS_MEDIAN = 10,
58
+ FOLLOWERS_WEIGHT = 1;
59
+
60
+ const TOTAL_WEIGHT =
61
+ COMMITS_WEIGHT +
62
+ PRS_WEIGHT +
63
+ ISSUES_WEIGHT +
64
+ REVIEWS_WEIGHT +
65
+ STARS_WEIGHT +
66
+ FOLLOWERS_WEIGHT;
67
+
68
+ const THRESHOLDS = [1, 12.5, 25, 37.5, 50, 62.5, 75, 87.5, 100];
69
+ const LEVELS = ["S", "A+", "A", "A-", "B+", "B", "B-", "C+", "C"];
70
+
71
+ const rank =
72
+ 1 -
73
+ (COMMITS_WEIGHT * exponential_cdf(commits / COMMITS_MEDIAN) +
74
+ PRS_WEIGHT * exponential_cdf(prs / PRS_MEDIAN) +
75
+ ISSUES_WEIGHT * exponential_cdf(issues / ISSUES_MEDIAN) +
76
+ REVIEWS_WEIGHT * exponential_cdf(reviews / REVIEWS_MEDIAN) +
77
+ STARS_WEIGHT * log_normal_cdf(stars / STARS_MEDIAN) +
78
+ FOLLOWERS_WEIGHT * log_normal_cdf(followers / FOLLOWERS_MEDIAN)) /
79
+ TOTAL_WEIGHT;
80
+
81
+ const level = LEVELS[THRESHOLDS.findIndex((t) => rank * 100 <= t)];
82
+
83
+ return { level, percentile: rank * 100 };
84
+ }
85
+
86
+ export { calculateRank };
87
+ export default calculateRank;
@@ -0,0 +1,151 @@
1
+ // @ts-check
2
+
3
+ import {
4
+ measureText,
5
+ flexLayout,
6
+ iconWithLabel,
7
+ createLanguageNode,
8
+ } from "../common/render.js";
9
+ import Card from "../common/Card.js";
10
+ import { getCardColors } from "../common/color.js";
11
+ import { kFormatter, wrapTextMultiline } from "../common/fmt.js";
12
+ import { encodeHTML, escapeCSSValue } from "../common/html.js";
13
+ import { icons } from "../common/icons.js";
14
+ import { parseEmojis } from "../common/ops.js";
15
+
16
+ /** Import language colors.
17
+ *
18
+ * @description Using ES module JSON import which works in modern Node.js and Cloudflare Workers.
19
+ */
20
+ // @ts-ignore - JSON import
21
+ import languageColors from "../common/languageColors.json";
22
+
23
+ const ICON_SIZE = 16;
24
+ const CARD_DEFAULT_WIDTH = 400;
25
+ const HEADER_MAX_LENGTH = 35;
26
+
27
+ /**
28
+ * @typedef {import('./types').GistCardOptions} GistCardOptions Gist card options.
29
+ * @typedef {import('../fetchers/types').GistData} GistData Gist data.
30
+ */
31
+
32
+ /**
33
+ * Render gist card.
34
+ *
35
+ * @param {GistData} gistData Gist data.
36
+ * @param {Partial<GistCardOptions>} options Gist card options.
37
+ * @returns {string} Gist card.
38
+ */
39
+ const renderGistCard = (gistData, options = {}) => {
40
+ const { name, nameWithOwner, description, language, starsCount, forksCount } =
41
+ gistData;
42
+ const {
43
+ title_color,
44
+ icon_color,
45
+ text_color,
46
+ bg_color,
47
+ theme,
48
+ border_radius,
49
+ border_color,
50
+ show_owner = false,
51
+ hide_border = false,
52
+ } = options;
53
+
54
+ // returns theme based colors with proper overrides and defaults
55
+ const { titleColor, textColor, iconColor, bgColor, borderColor } =
56
+ getCardColors({
57
+ title_color,
58
+ icon_color,
59
+ text_color,
60
+ bg_color,
61
+ border_color,
62
+ theme,
63
+ });
64
+
65
+ const lineWidth = 59;
66
+ const linesLimit = 10;
67
+ const desc = parseEmojis(description || "No description provided");
68
+ const multiLineDescription = wrapTextMultiline(desc, lineWidth, linesLimit);
69
+ const descriptionLines = multiLineDescription.length;
70
+ const descriptionSvg = multiLineDescription
71
+ .map((line) => `<tspan dy="1.2em" x="25">${encodeHTML(line)}</tspan>`)
72
+ .join("");
73
+
74
+ const lineHeight = descriptionLines > 3 ? 12 : 10;
75
+ const height =
76
+ (descriptionLines > 1 ? 120 : 110) + descriptionLines * lineHeight;
77
+
78
+ const totalStars = kFormatter(starsCount);
79
+ const totalForks = kFormatter(forksCount);
80
+ const svgStars = iconWithLabel(
81
+ icons.star,
82
+ totalStars,
83
+ "starsCount",
84
+ ICON_SIZE,
85
+ );
86
+ const svgForks = iconWithLabel(
87
+ icons.fork,
88
+ totalForks,
89
+ "forksCount",
90
+ ICON_SIZE,
91
+ );
92
+
93
+ const languageName = language || "Unspecified";
94
+ // @ts-ignore
95
+ const languageColor = languageColors[languageName] || "#858585";
96
+
97
+ const svgLanguage = createLanguageNode(languageName, languageColor);
98
+
99
+ const starAndForkCount = flexLayout({
100
+ items: [svgLanguage, svgStars, svgForks],
101
+ sizes: [
102
+ measureText(languageName, 12),
103
+ ICON_SIZE + measureText(`${totalStars}`, 12),
104
+ ICON_SIZE + measureText(`${totalForks}`, 12),
105
+ ],
106
+ gap: 25,
107
+ }).join("");
108
+
109
+ const header = show_owner ? nameWithOwner : name;
110
+
111
+ const card = new Card({
112
+ defaultTitle:
113
+ header.length > HEADER_MAX_LENGTH
114
+ ? `${header.slice(0, HEADER_MAX_LENGTH)}...`
115
+ : header,
116
+ titlePrefixIcon: icons.gist,
117
+ width: CARD_DEFAULT_WIDTH,
118
+ height,
119
+ border_radius,
120
+ colors: {
121
+ titleColor,
122
+ textColor,
123
+ iconColor,
124
+ bgColor,
125
+ borderColor,
126
+ },
127
+ });
128
+
129
+ // Sanitize color values to prevent XSS
130
+ const safeTextColor = escapeCSSValue(textColor);
131
+ const safeIconColor = escapeCSSValue(iconColor);
132
+ card.setCSS(`
133
+ .description { font: 400 13px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${safeTextColor} }
134
+ .gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${safeTextColor} }
135
+ .icon { fill: ${safeIconColor} }
136
+ `);
137
+ card.setHideBorder(hide_border);
138
+
139
+ return card.render(`
140
+ <text class="description" x="25" y="-5">
141
+ ${descriptionSvg}
142
+ </text>
143
+
144
+ <g transform="translate(30, ${height - 75})">
145
+ ${starAndForkCount}
146
+ </g>
147
+ `);
148
+ };
149
+
150
+ export { renderGistCard, HEADER_MAX_LENGTH };
151
+ export default renderGistCard;
@@ -0,0 +1,4 @@
1
+ export { renderRepoCard } from "./repo.js";
2
+ export { renderStatsCard } from "./stats.js";
3
+ export { renderTopLanguages } from "./top-languages.js";
4
+ export { renderWakatimeCard } from "./wakatime.js";