@stats-organization/github-readme-stats-core 0.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 (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/build/api/gist.d.ts +19 -0
  4. package/build/api/gist.d.ts.map +1 -0
  5. package/build/api/gist.js +77 -0
  6. package/build/api/index.d.ts +37 -0
  7. package/build/api/index.d.ts.map +1 -0
  8. package/build/api/index.js +115 -0
  9. package/build/api/pin.d.ts +27 -0
  10. package/build/api/pin.d.ts.map +1 -0
  11. package/build/api/pin.js +104 -0
  12. package/build/api/top-langs.d.ts +31 -0
  13. package/build/api/top-langs.d.ts.map +1 -0
  14. package/build/api/top-langs.js +121 -0
  15. package/build/api/wakatime.d.ts +28 -0
  16. package/build/api/wakatime.d.ts.map +1 -0
  17. package/build/api/wakatime.js +85 -0
  18. package/build/calculateRank.d.ts +28 -0
  19. package/build/calculateRank.d.ts.map +1 -0
  20. package/build/calculateRank.js +63 -0
  21. package/build/cards/gist.d.ts +21 -0
  22. package/build/cards/gist.d.ts.map +1 -0
  23. package/build/cards/gist.js +124 -0
  24. package/build/cards/repo.d.ts +21 -0
  25. package/build/cards/repo.d.ts.map +1 -0
  26. package/build/cards/repo.js +264 -0
  27. package/build/cards/stats.d.ts +49 -0
  28. package/build/cards/stats.d.ts.map +1 -0
  29. package/build/cards/stats.js +527 -0
  30. package/build/cards/top-languages.d.ts +143 -0
  31. package/build/cards/top-languages.d.ts.map +1 -0
  32. package/build/cards/top-languages.js +795 -0
  33. package/build/cards/wakatime.d.ts +16 -0
  34. package/build/cards/wakatime.d.ts.map +1 -0
  35. package/build/cards/wakatime.js +387 -0
  36. package/build/common/Card.d.ts +109 -0
  37. package/build/common/Card.d.ts.map +1 -0
  38. package/build/common/Card.js +241 -0
  39. package/build/common/I18n.d.ts +26 -0
  40. package/build/common/I18n.d.ts.map +1 -0
  41. package/build/common/I18n.js +34 -0
  42. package/build/common/color.d.ts +53 -0
  43. package/build/common/color.d.ts.map +1 -0
  44. package/build/common/color.js +92 -0
  45. package/build/common/config.d.ts +3 -0
  46. package/build/common/config.d.ts.map +1 -0
  47. package/build/common/config.js +69 -0
  48. package/build/common/constants.d.ts +6 -0
  49. package/build/common/constants.d.ts.map +1 -0
  50. package/build/common/constants.js +7 -0
  51. package/build/common/error.d.ts +53 -0
  52. package/build/common/error.d.ts.map +1 -0
  53. package/build/common/error.js +72 -0
  54. package/build/common/fmt.d.ts +27 -0
  55. package/build/common/fmt.d.ts.map +1 -0
  56. package/build/common/fmt.js +66 -0
  57. package/build/common/html.d.ts +10 -0
  58. package/build/common/html.d.ts.map +1 -0
  59. package/build/common/html.js +18 -0
  60. package/build/common/http.d.ts +9 -0
  61. package/build/common/http.d.ts.map +1 -0
  62. package/build/common/http.js +18 -0
  63. package/build/common/icons.d.ts +26 -0
  64. package/build/common/icons.d.ts.map +1 -0
  65. package/build/common/icons.js +77 -0
  66. package/build/common/languageColors.json +667 -0
  67. package/build/common/log.d.ts +9 -0
  68. package/build/common/log.d.ts.map +1 -0
  69. package/build/common/log.js +8 -0
  70. package/build/common/ops.d.ts +65 -0
  71. package/build/common/ops.d.ts.map +1 -0
  72. package/build/common/ops.js +137 -0
  73. package/build/common/render.d.ts +155 -0
  74. package/build/common/render.d.ts.map +1 -0
  75. package/build/common/render.js +361 -0
  76. package/build/common/retryer.d.ts +22 -0
  77. package/build/common/retryer.d.ts.map +1 -0
  78. package/build/common/retryer.js +86 -0
  79. package/build/fetchers/gist.d.ts +26 -0
  80. package/build/fetchers/gist.d.ts.map +1 -0
  81. package/build/fetchers/gist.js +99 -0
  82. package/build/fetchers/repo.d.ts +16 -0
  83. package/build/fetchers/repo.d.ts.map +1 -0
  84. package/build/fetchers/repo.js +116 -0
  85. package/build/fetchers/stats.d.ts +22 -0
  86. package/build/fetchers/stats.d.ts.map +1 -0
  87. package/build/fetchers/stats.js +339 -0
  88. package/build/fetchers/top-languages.d.ts +20 -0
  89. package/build/fetchers/top-languages.d.ts.map +1 -0
  90. package/build/fetchers/top-languages.js +139 -0
  91. package/build/fetchers/wakatime.d.ts +11 -0
  92. package/build/fetchers/wakatime.d.ts.map +1 -0
  93. package/build/fetchers/wakatime.js +25 -0
  94. package/build/index.d.ts +20 -0
  95. package/build/index.d.ts.map +1 -0
  96. package/build/index.js +19 -0
  97. package/build/themes/index.d.ts +469 -0
  98. package/build/themes/index.d.ts.map +1 -0
  99. package/build/themes/index.js +468 -0
  100. package/build/translations.d.ts +533 -0
  101. package/build/translations.d.ts.map +1 -0
  102. package/build/translations.js +1140 -0
  103. package/package.json +94 -0
  104. package/src/api/gist.js +99 -0
  105. package/src/api/index.js +185 -0
  106. package/src/api/pin.js +146 -0
  107. package/src/api/top-langs.js +168 -0
  108. package/src/api/wakatime.js +113 -0
  109. package/src/calculateRank.js +86 -0
  110. package/src/cards/gist.js +187 -0
  111. package/src/cards/repo.js +365 -0
  112. package/src/cards/stats.js +673 -0
  113. package/src/cards/top-languages.js +1023 -0
  114. package/src/cards/types.d.ts +78 -0
  115. package/src/cards/wakatime.js +471 -0
  116. package/src/common/Card.js +275 -0
  117. package/src/common/I18n.js +42 -0
  118. package/src/common/color.js +145 -0
  119. package/src/common/config.js +78 -0
  120. package/src/common/constants.js +9 -0
  121. package/src/common/error.js +90 -0
  122. package/src/common/fmt.js +80 -0
  123. package/src/common/html.js +22 -0
  124. package/src/common/http.js +21 -0
  125. package/src/common/icons.js +81 -0
  126. package/src/common/languageColors.json +667 -0
  127. package/src/common/log.ts +12 -0
  128. package/src/common/ops.js +172 -0
  129. package/src/common/render.js +442 -0
  130. package/src/common/retryer.js +104 -0
  131. package/src/fetchers/gist.js +114 -0
  132. package/src/fetchers/repo.js +165 -0
  133. package/src/fetchers/stats.js +478 -0
  134. package/src/fetchers/top-languages.js +177 -0
  135. package/src/fetchers/types.d.ts +128 -0
  136. package/src/fetchers/wakatime.js +37 -0
  137. package/src/index.ts +25 -0
  138. package/src/themes/README.md +229 -0
  139. package/src/themes/index.ts +476 -0
  140. package/src/translations.js +1156 -0
@@ -0,0 +1,85 @@
1
+ // @ts-check
2
+ import { renderWakatimeCard } from "../cards/wakatime.js";
3
+ import { MissingParamError, retrieveSecondaryMessage, } from "../common/error.js";
4
+ import { parseArray, parseBoolean } from "../common/ops.js";
5
+ import { renderError } from "../common/render.js";
6
+ import { fetchWakatimeStats } from "../fetchers/wakatime.js";
7
+ import { isLocaleAvailable } from "../translations.js";
8
+ // @ts-ignore
9
+ export default async ({ username, title_color, icon_color, hide_border, card_width, line_height, text_color, bg_color, theme, hide_title, hide_progress, custom_title, locale, layout, langs_count, hide, api_domain, border_radius, border_color, display_format, disable_animations, }) => {
10
+ if (locale && !isLocaleAvailable(locale)) {
11
+ return {
12
+ status: "error - permanent",
13
+ content: renderError({
14
+ message: "Something went wrong",
15
+ secondaryMessage: "Language not found",
16
+ renderOptions: {
17
+ title_color,
18
+ text_color,
19
+ bg_color,
20
+ border_color,
21
+ theme,
22
+ },
23
+ }),
24
+ };
25
+ }
26
+ try {
27
+ const stats = await fetchWakatimeStats({ username, api_domain });
28
+ return {
29
+ status: "success",
30
+ content: renderWakatimeCard(stats, {
31
+ custom_title,
32
+ hide_title: parseBoolean(hide_title),
33
+ hide_border: parseBoolean(hide_border),
34
+ card_width: parseInt(card_width, 10),
35
+ hide: parseArray(hide),
36
+ line_height,
37
+ title_color,
38
+ icon_color,
39
+ text_color,
40
+ bg_color,
41
+ theme,
42
+ hide_progress,
43
+ border_radius,
44
+ border_color,
45
+ locale: locale ? locale.toLowerCase() : null,
46
+ layout,
47
+ langs_count,
48
+ display_format,
49
+ disable_animations: parseBoolean(disable_animations),
50
+ }),
51
+ };
52
+ }
53
+ catch (err) {
54
+ if (err instanceof Error) {
55
+ return {
56
+ status: "error - temporary",
57
+ content: renderError({
58
+ message: err.message,
59
+ secondaryMessage: retrieveSecondaryMessage(err),
60
+ renderOptions: {
61
+ title_color,
62
+ text_color,
63
+ bg_color,
64
+ border_color,
65
+ theme,
66
+ show_repo_link: !(err instanceof MissingParamError),
67
+ },
68
+ }),
69
+ };
70
+ }
71
+ return {
72
+ status: "error - temporary",
73
+ content: renderError({
74
+ message: "An unknown error occurred",
75
+ renderOptions: {
76
+ title_color,
77
+ text_color,
78
+ bg_color,
79
+ border_color,
80
+ theme,
81
+ },
82
+ }),
83
+ };
84
+ }
85
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Calculates the users rank.
3
+ *
4
+ * @param {object} params Parameters on which the user's rank depends.
5
+ * @param {boolean} params.all_commits Whether `include_all_commits` was used.
6
+ * @param {number} params.commits Number of commits.
7
+ * @param {number} params.prs The number of pull requests.
8
+ * @param {number} params.issues The number of issues.
9
+ * @param {number} params.reviews The number of reviews.
10
+ * @param {number} params.repos Total number of repos.
11
+ * @param {number} params.stars The number of stars.
12
+ * @param {number} params.followers The number of followers.
13
+ * @returns {{ level: string, percentile: number }} The users rank.
14
+ */
15
+ export function calculateRank({ all_commits, commits, prs, issues, reviews, repos, stars, followers, }: {
16
+ all_commits: boolean;
17
+ commits: number;
18
+ prs: number;
19
+ issues: number;
20
+ reviews: number;
21
+ repos: number;
22
+ stars: number;
23
+ followers: number;
24
+ }): {
25
+ level: string;
26
+ percentile: number;
27
+ };
28
+ //# sourceMappingURL=calculateRank.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calculateRank.d.ts","sourceRoot":"","sources":["../src/calculateRank.js"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;GAaG;AACH,wGAVG;IAAwB,WAAW,EAA3B,OAAO;IACQ,OAAO,EAAtB,MAAM;IACS,GAAG,EAAlB,MAAM;IACS,MAAM,EAArB,MAAM;IACS,OAAO,EAAtB,MAAM;IACS,KAAK,EAApB,MAAM;IACS,KAAK,EAApB,MAAM;IACS,SAAS,EAAxB,MAAM;CACd,GAAU;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAkDjD"}
@@ -0,0 +1,63 @@
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
+ * Calculates the log normal cdf.
12
+ *
13
+ * @param {number} x The value.
14
+ * @returns {number} The log normal cdf.
15
+ */
16
+ function log_normal_cdf(x) {
17
+ // approximation
18
+ return x / (1 + x);
19
+ }
20
+ /**
21
+ * Calculates the users rank.
22
+ *
23
+ * @param {object} params Parameters on which the user's rank depends.
24
+ * @param {boolean} params.all_commits Whether `include_all_commits` was used.
25
+ * @param {number} params.commits Number of commits.
26
+ * @param {number} params.prs The number of pull requests.
27
+ * @param {number} params.issues The number of issues.
28
+ * @param {number} params.reviews The number of reviews.
29
+ * @param {number} params.repos Total number of repos.
30
+ * @param {number} params.stars The number of stars.
31
+ * @param {number} params.followers The number of followers.
32
+ * @returns {{ level: string, percentile: number }} The users rank.
33
+ */
34
+ function calculateRank({ all_commits, commits, prs, issues, reviews,
35
+ // eslint-disable-next-line no-unused-vars
36
+ repos, // unused
37
+ stars, followers, }) {
38
+ const COMMITS_MEDIAN = all_commits ? 1000 : 250, COMMITS_WEIGHT = 2;
39
+ const PRS_MEDIAN = 50, PRS_WEIGHT = 3;
40
+ const ISSUES_MEDIAN = 25, ISSUES_WEIGHT = 1;
41
+ const REVIEWS_MEDIAN = 2, REVIEWS_WEIGHT = 1;
42
+ const STARS_MEDIAN = 50, STARS_WEIGHT = 4;
43
+ const FOLLOWERS_MEDIAN = 10, FOLLOWERS_WEIGHT = 1;
44
+ const TOTAL_WEIGHT = COMMITS_WEIGHT +
45
+ PRS_WEIGHT +
46
+ ISSUES_WEIGHT +
47
+ REVIEWS_WEIGHT +
48
+ STARS_WEIGHT +
49
+ FOLLOWERS_WEIGHT;
50
+ const THRESHOLDS = [1, 12.5, 25, 37.5, 50, 62.5, 75, 87.5, 100];
51
+ const LEVELS = ["S", "A+", "A", "A-", "B+", "B", "B-", "C+", "C"];
52
+ const rank = 1 -
53
+ (COMMITS_WEIGHT * exponential_cdf(commits / COMMITS_MEDIAN) +
54
+ PRS_WEIGHT * exponential_cdf(prs / PRS_MEDIAN) +
55
+ ISSUES_WEIGHT * exponential_cdf(issues / ISSUES_MEDIAN) +
56
+ REVIEWS_WEIGHT * exponential_cdf(reviews / REVIEWS_MEDIAN) +
57
+ STARS_WEIGHT * log_normal_cdf(stars / STARS_MEDIAN) +
58
+ FOLLOWERS_WEIGHT * log_normal_cdf(followers / FOLLOWERS_MEDIAN)) /
59
+ TOTAL_WEIGHT;
60
+ const level = LEVELS[THRESHOLDS.findIndex((t) => rank * 100 <= t)];
61
+ return { level, percentile: rank * 100 };
62
+ }
63
+ export { calculateRank };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Gist card options.
3
+ */
4
+ export type GistCardOptions = any;
5
+ /**
6
+ * Gist data.
7
+ */
8
+ export type GistData = any;
9
+ /**
10
+ * @typedef {import('./types').GistCardOptions} GistCardOptions Gist card options.
11
+ * @typedef {import('../fetchers/types').GistData} GistData Gist data.
12
+ */
13
+ /**
14
+ * Render gist card.
15
+ *
16
+ * @param {GistData} gistData Gist data.
17
+ * @param {Partial<GistCardOptions>} options Gist card options.
18
+ * @returns {string} Gist card.
19
+ */
20
+ export function renderGistCard(gistData: GistData, options?: Partial<GistCardOptions>): string;
21
+ //# sourceMappingURL=gist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gist.d.ts","sourceRoot":"","sources":["../../src/cards/gist.js"],"names":[],"mappings":";;;;;;;;AA4BA;;;GAGG;AAEH;;;;;;GAMG;AACH,yCAJW,QAAQ,YACR,OAAO,CAAC,eAAe,CAAC,GACtB,MAAM,CAkJlB"}
@@ -0,0 +1,124 @@
1
+ // @ts-check
2
+ import { default as Card } from "../common/Card.js";
3
+ import { getCardColors } from "../common/color.js";
4
+ import { kFormatter, wrapTextMultiline } from "../common/fmt.js";
5
+ import { encodeHTML } from "../common/html.js";
6
+ import { icons } from "../common/icons.js";
7
+ import languageColors from "../common/languageColors.json" with { type: "json" };
8
+ import { parseEmojis } from "../common/ops.js";
9
+ import { countWrappedLines, createLanguageNode, flexLayout, iconWithLabel, measureText, wrappedTextNode, wrappedTextStyles, } from "../common/render.js";
10
+ const ICON_SIZE = 16;
11
+ const CARD_DEFAULT_WIDTH = 400;
12
+ const X_OFFSET = 25;
13
+ const HEADER_MAX_LENGTH = 35;
14
+ const DESCRIPTION_BOX_WIDTH = CARD_DEFAULT_WIDTH - 2 * X_OFFSET;
15
+ const DESCRIPTION_FONT_SIZE = 13;
16
+ const DESCRIPTION_LINE_HEIGHT_PX = 16;
17
+ const DESCRIPTION_MAX_LINES = 10;
18
+ /**
19
+ * @typedef {import('./types').GistCardOptions} GistCardOptions Gist card options.
20
+ * @typedef {import('../fetchers/types').GistData} GistData Gist data.
21
+ */
22
+ /**
23
+ * Render gist card.
24
+ *
25
+ * @param {GistData} gistData Gist data.
26
+ * @param {Partial<GistCardOptions>} options Gist card options.
27
+ * @returns {string} Gist card.
28
+ */
29
+ const renderGistCard = (gistData, options = {}) => {
30
+ const { name, nameWithOwner, description, language, starsCount, forksCount } = gistData;
31
+ const { title_color, icon_color, text_color, bg_color, theme = "default_repocard", border_radius, border_color, show_owner = false, browser_rendering = false, hide_border = false, } = options;
32
+ // returns theme based colors with proper overrides and defaults
33
+ const { titleColor, textColor, iconColor, bgColor, borderColor } = getCardColors({
34
+ title_color,
35
+ icon_color,
36
+ text_color,
37
+ bg_color,
38
+ border_color,
39
+ theme,
40
+ });
41
+ const desc = parseEmojis(description || "No description provided");
42
+ let descriptionLines, descriptionSvg;
43
+ if (browser_rendering) {
44
+ // The browser performs the actual text wrapping inside the foreignObject;
45
+ // we only estimate the line count server-side so the SVG can reserve enough
46
+ // height. The estimate uses measureText for font-aware widths instead of a
47
+ // fixed character count.
48
+ descriptionLines = countWrappedLines(desc, DESCRIPTION_FONT_SIZE, DESCRIPTION_BOX_WIDTH, DESCRIPTION_MAX_LINES);
49
+ descriptionSvg = wrappedTextNode({
50
+ text: desc,
51
+ x: X_OFFSET,
52
+ y: -3,
53
+ width: DESCRIPTION_BOX_WIDTH,
54
+ height: descriptionLines * DESCRIPTION_LINE_HEIGHT_PX + 10, // 10px extra for "descenders" like g, j, q, p, y
55
+ lineCount: descriptionLines,
56
+ className: "description",
57
+ testId: "description-text",
58
+ });
59
+ }
60
+ else {
61
+ const linesLimit = 10;
62
+ const multiLineDescription = wrapTextMultiline(desc, DESCRIPTION_BOX_WIDTH, DESCRIPTION_FONT_SIZE, linesLimit);
63
+ descriptionLines = multiLineDescription.length;
64
+ descriptionSvg = multiLineDescription
65
+ .map((line) => `<tspan dy="1.2em" x="${X_OFFSET}">${encodeHTML(line)}</tspan>`)
66
+ .join("");
67
+ descriptionSvg = `<text class="description" x="${X_OFFSET}" y="-5">
68
+ ${descriptionSvg}
69
+ </text>`;
70
+ }
71
+ const lineHeight = descriptionLines > 3 ? 12 : 10;
72
+ const height = (descriptionLines > 1 ? 120 : 110) + descriptionLines * lineHeight;
73
+ const totalStars = kFormatter(starsCount);
74
+ const totalForks = kFormatter(forksCount);
75
+ const svgStars = iconWithLabel(icons.star, totalStars, "starsCount", ICON_SIZE);
76
+ const svgForks = iconWithLabel(icons.fork, totalForks, "forksCount", ICON_SIZE);
77
+ const languageName = language || "Unspecified";
78
+ // @ts-ignore
79
+ const languageColor = languageColors[languageName] || "#858585";
80
+ const svgLanguage = createLanguageNode(languageName, languageColor);
81
+ const starAndForkCount = flexLayout({
82
+ items: [svgLanguage, svgStars, svgForks],
83
+ sizes: [
84
+ measureText(languageName, 12),
85
+ ICON_SIZE + measureText(`${totalStars}`, 12),
86
+ ICON_SIZE + measureText(`${totalForks}`, 12),
87
+ ],
88
+ gap: 25,
89
+ }).join("");
90
+ const header = show_owner ? nameWithOwner : name;
91
+ const card = new Card({
92
+ defaultTitle: header.length > HEADER_MAX_LENGTH
93
+ ? `${header.slice(0, HEADER_MAX_LENGTH)}...`
94
+ : header,
95
+ titlePrefixIcon: icons.gist,
96
+ width: CARD_DEFAULT_WIDTH,
97
+ height,
98
+ border_radius,
99
+ colors: {
100
+ titleColor,
101
+ textColor,
102
+ iconColor,
103
+ bgColor,
104
+ borderColor,
105
+ },
106
+ });
107
+ card.setCSS(`
108
+ .description {
109
+ font: 400 ${DESCRIPTION_FONT_SIZE}px 'Segoe UI', Ubuntu, Sans-Serif;fill: ${textColor};
110
+ ${browser_rendering ? wrappedTextStyles(textColor) : ""}
111
+ }
112
+ .gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
113
+ .icon { fill: ${iconColor} }
114
+ `);
115
+ card.setHideBorder(hide_border);
116
+ return card.render(`
117
+ ${descriptionSvg}
118
+
119
+ <g transform="translate(30, ${height - 75})">
120
+ ${starAndForkCount}
121
+ </g>
122
+ `);
123
+ };
124
+ export { renderGistCard };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Repository data.
3
+ */
4
+ export type RepositoryData = any;
5
+ /**
6
+ * Repo card options.
7
+ */
8
+ export type RepoCardOptions = any;
9
+ /**
10
+ * @typedef {import("../fetchers/types").RepositoryData} RepositoryData Repository data.
11
+ * @typedef {import("./types").RepoCardOptions} RepoCardOptions Repo card options.
12
+ */
13
+ /**
14
+ * Renders repository card details.
15
+ *
16
+ * @param {RepositoryData} repo Repository data.
17
+ * @param {Partial<RepoCardOptions>} options Card options.
18
+ * @returns {string} Repository card SVG object.
19
+ */
20
+ export function renderRepoCard(repo: RepositoryData, options?: Partial<RepoCardOptions>): string;
21
+ //# sourceMappingURL=repo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/cards/repo.js"],"names":[],"mappings":";;;;;;;;AAmDA;;;GAGG;AAEH;;;;;;GAMG;AACH,qCAJW,cAAc,YACd,OAAO,CAAC,eAAe,CAAC,GACtB,MAAM,CA6SlB"}
@@ -0,0 +1,264 @@
1
+ // @ts-check
2
+ import { Card } from "../common/Card.js";
3
+ import { I18n } from "../common/I18n.js";
4
+ import { getCardColors } from "../common/color.js";
5
+ import { kFormatter, wrapTextMultiline } from "../common/fmt.js";
6
+ import { encodeHTML } from "../common/html.js";
7
+ import { icons } from "../common/icons.js";
8
+ import { buildSearchFilter, clampValue, parseEmojis } from "../common/ops.js";
9
+ import { countWrappedLines, createLanguageNode, flexLayout, iconWithLabel, measureText, wrappedTextNode, wrappedTextStyles, } from "../common/render.js";
10
+ import { repoCardLocales } from "../translations.js";
11
+ import { createTextNode } from "./stats.js";
12
+ const ICON_SIZE = 16;
13
+ const CARD_DEFAULT_WIDTH = 400;
14
+ const X_OFFSET = 25;
15
+ const DESCRIPTION_FONT_SIZE = 13;
16
+ const DESCRIPTION_LINE_HEIGHT_PX = 16;
17
+ const DESCRIPTION_MAX_LINES = 3;
18
+ /**
19
+ * Retrieves the repository description and wraps it to fit the card width.
20
+ *
21
+ * @param {string} label The repository description.
22
+ * @param {string} textColor The color of the text.
23
+ * @returns {string} Wrapped repo description SVG object.
24
+ */
25
+ const getBadgeSVG = (label, textColor, xOffset = 0) => `
26
+ <g data-testid="badge" class="badge" transform="translate(${320 + xOffset}, -18)">
27
+ <rect stroke="${textColor}" stroke-width="1" width="70" height="20" x="-12" y="-14" ry="10" rx="10"></rect>
28
+ <text
29
+ x="23" y="-5"
30
+ alignment-baseline="central"
31
+ dominant-baseline="central"
32
+ text-anchor="middle"
33
+ fill="${textColor}"
34
+ >
35
+ ${label}
36
+ </text>
37
+ </g>
38
+ `;
39
+ /**
40
+ * @typedef {import("../fetchers/types").RepositoryData} RepositoryData Repository data.
41
+ * @typedef {import("./types").RepoCardOptions} RepoCardOptions Repo card options.
42
+ */
43
+ /**
44
+ * Renders repository card details.
45
+ *
46
+ * @param {RepositoryData} repo Repository data.
47
+ * @param {Partial<RepoCardOptions>} options Card options.
48
+ * @returns {string} Repository card SVG object.
49
+ */
50
+ const renderRepoCard = (repo, options = {}) => {
51
+ const { name, nameWithOwner, description, primaryLanguage, isArchived, isTemplate, starCount, forkCount, totalPRsAuthored, totalPRsCommented, totalPRsReviewed, totalIssuesAuthored, totalIssuesCommented, } = repo;
52
+ const { hide_border = false, title_color, icon_color, text_color, bg_color, card_width_input, show_owner = false, browser_rendering = false, show = [], show_icons = true, number_format = "short", text_bold = false, line_height = 22, username, theme = "default_repocard", border_radius, border_color, locale, description_lines_count, } = options;
53
+ const card_width = card_width_input && !isNaN(card_width_input)
54
+ ? card_width_input
55
+ : show.length >= 2
56
+ ? CARD_DEFAULT_WIDTH + 30
57
+ : CARD_DEFAULT_WIDTH;
58
+ const i18n = new I18n({
59
+ locale,
60
+ translations: repoCardLocales,
61
+ });
62
+ let repoFilter = encodeURIComponent(buildSearchFilter([nameWithOwner], []));
63
+ const STATS = {};
64
+ if (show.includes("prs_authored")) {
65
+ STATS.prs_authored = {
66
+ icon: icons.prs,
67
+ label: i18n.t("repocard.prs-authored"),
68
+ value: totalPRsAuthored,
69
+ id: "prs_authored",
70
+ link: `https://github.com/search?q=${repoFilter}author%3A${username}&amp;type=pullrequests`,
71
+ };
72
+ }
73
+ if (show.includes("prs_commented")) {
74
+ STATS.prs_commented = {
75
+ icon: icons.comments,
76
+ label: i18n.t("repocard.prs-commented"),
77
+ value: totalPRsCommented,
78
+ id: "prs_commented",
79
+ link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&amp;type=pullrequests`,
80
+ };
81
+ }
82
+ if (show.includes("prs_reviewed")) {
83
+ STATS.prs_reviewed = {
84
+ icon: icons.reviews,
85
+ label: i18n.t("repocard.prs-reviewed"),
86
+ value: totalPRsReviewed,
87
+ id: "prs_reviewed",
88
+ link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${username}+-author%3A${username}&amp;type=pullrequests`,
89
+ };
90
+ }
91
+ if (show.includes("issues_authored")) {
92
+ STATS.issues_authored = {
93
+ icon: icons.issues,
94
+ label: i18n.t("repocard.issues-authored"),
95
+ value: totalIssuesAuthored,
96
+ id: "issues_authored",
97
+ link: `https://github.com/search?q=${repoFilter}author%3A${username}&amp;type=issues`,
98
+ };
99
+ }
100
+ if (show.includes("issues_commented")) {
101
+ STATS.issues_commented = {
102
+ icon: icons.discussions_started,
103
+ label: i18n.t("repocard.issues-commented"),
104
+ value: totalIssuesCommented,
105
+ id: "issues_commented",
106
+ link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&amp;type=issues`,
107
+ };
108
+ }
109
+ const statItems = Object.keys(STATS).map((key, index) =>
110
+ // create the text nodes, and pass index so that we can calculate the line spacing
111
+ createTextNode({
112
+ icon: STATS[key].icon,
113
+ label: STATS[key].label,
114
+ value: STATS[key].value,
115
+ id: STATS[key].id,
116
+ unitSymbol: STATS[key].unitSymbol,
117
+ index,
118
+ showIcons: show_icons,
119
+ shiftValuePos: 14.01,
120
+ bold: text_bold,
121
+ numberFormat: number_format,
122
+ link: STATS[key].link,
123
+ labelXOffset: 23,
124
+ }));
125
+ const extraLHeight = parseInt(String(line_height), 10);
126
+ const lineHeight = 10;
127
+ const header = show_owner ? nameWithOwner : name;
128
+ const langName = (primaryLanguage && primaryLanguage.name) || "Unspecified";
129
+ const langColor = (primaryLanguage && primaryLanguage.color) || "#333";
130
+ const desc = parseEmojis(description || "No description provided");
131
+ const descriptionBoxWidth = card_width - 2 * X_OFFSET;
132
+ let descriptionLinesCount, descriptionSvg;
133
+ if (browser_rendering) {
134
+ // The browser performs the actual text wrapping inside the foreignObject;
135
+ // we only estimate the line count server-side so the SVG can reserve enough
136
+ // height. The estimate uses measureText for font-aware widths instead of a
137
+ // fixed character count.
138
+ descriptionLinesCount = description_lines_count
139
+ ? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES)
140
+ : countWrappedLines(desc, DESCRIPTION_FONT_SIZE, descriptionBoxWidth, DESCRIPTION_MAX_LINES);
141
+ descriptionSvg = wrappedTextNode({
142
+ text: desc,
143
+ x: X_OFFSET,
144
+ y: -3,
145
+ width: descriptionBoxWidth,
146
+ height: descriptionLinesCount * DESCRIPTION_LINE_HEIGHT_PX + 10, // 10px extra for "descenders" like g, j, q, p, y
147
+ lineCount: descriptionLinesCount,
148
+ className: "description",
149
+ testId: "description-text",
150
+ });
151
+ }
152
+ else {
153
+ const descriptionMaxLines = description_lines_count
154
+ ? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES)
155
+ : DESCRIPTION_MAX_LINES;
156
+ const multiLineDescription = wrapTextMultiline(desc, descriptionBoxWidth, DESCRIPTION_FONT_SIZE, descriptionMaxLines);
157
+ descriptionLinesCount = description_lines_count
158
+ ? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES)
159
+ : multiLineDescription.length;
160
+ descriptionSvg = multiLineDescription
161
+ .map((line) => `<tspan dy="1.2em" x="${X_OFFSET}">${encodeHTML(line)}</tspan>`)
162
+ .join("");
163
+ descriptionSvg = `<text class="description" x="${X_OFFSET}" y="-5">
164
+ ${descriptionSvg}
165
+ </text>`;
166
+ }
167
+ const extraHeight = Object.keys(STATS).length
168
+ ? -7 + (Math.ceil(statItems.length / 2) + 1) * extraLHeight
169
+ : 0;
170
+ const height = (descriptionLinesCount > 1 ? 120 : 110) +
171
+ descriptionLinesCount * lineHeight +
172
+ extraHeight;
173
+ // returns theme based colors with proper overrides and defaults
174
+ const colors = getCardColors({
175
+ title_color,
176
+ icon_color,
177
+ text_color,
178
+ bg_color,
179
+ border_color,
180
+ theme,
181
+ });
182
+ const svgLanguage = primaryLanguage
183
+ ? createLanguageNode(langName, langColor)
184
+ : "";
185
+ const totalStars = kFormatter(starCount);
186
+ const totalForks = kFormatter(forkCount);
187
+ const svgStars = iconWithLabel(icons.star, totalStars, "stargazers", ICON_SIZE);
188
+ const svgForks = iconWithLabel(icons.fork, totalForks, "forkcount", ICON_SIZE);
189
+ const starAndForkCount = flexLayout({
190
+ items: [svgLanguage, svgStars, svgForks],
191
+ sizes: [
192
+ measureText(langName, 12),
193
+ ICON_SIZE + measureText(`${totalStars}`, 12),
194
+ ICON_SIZE + measureText(`${totalForks}`, 12),
195
+ ],
196
+ gap: 25,
197
+ }).join("");
198
+ let extraRows = [];
199
+ for (let i = 0; i < statItems.length; i += 2) {
200
+ extraRows.push(flexLayout({
201
+ items: statItems.slice(i, i + 2),
202
+ gap: 210,
203
+ direction: "row",
204
+ }).join(""));
205
+ }
206
+ const extraItems = `
207
+ <svg x="0" y="0"><g transform="translate(-3, ${height - 52 - extraHeight})">
208
+ ${flexLayout({
209
+ items: extraRows,
210
+ gap: extraLHeight,
211
+ direction: "column",
212
+ }).join("")}
213
+ </g></svg>
214
+ `;
215
+ const card = new Card({
216
+ defaultTitle: header.length > 35 ? `${header.slice(0, 35)}...` : header,
217
+ titlePrefixIcon: icons.contribs,
218
+ width: card_width,
219
+ height,
220
+ border_radius,
221
+ colors,
222
+ });
223
+ card.disableAnimations();
224
+ card.setHideBorder(hide_border);
225
+ card.setHideTitle(false);
226
+ card.setCSS(`
227
+ .description {
228
+ font: 400 ${DESCRIPTION_FONT_SIZE}px 'Segoe UI', Ubuntu, Sans-Serif;fill: ${colors.textColor};
229
+ ${browser_rendering ? wrappedTextStyles(colors.textColor) : ""}
230
+ }
231
+ .gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} }
232
+ .badge { font: 600 11px 'Segoe UI', Ubuntu, Sans-Serif; }
233
+ .badge rect { opacity: 0.2 }
234
+
235
+ .stat { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} }
236
+ .stagger {
237
+ opacity: 0;
238
+ animation: fadeInAnimation 0.3s ease-in-out forwards;
239
+ }
240
+ .not_bold { font-weight: 400 }
241
+ .bold { font-weight: 700 }
242
+ .icon {
243
+ fill: ${colors.iconColor};
244
+ display: block;
245
+ }
246
+ `);
247
+ return card.render(`
248
+ ${isTemplate
249
+ ? // @ts-ignore
250
+ getBadgeSVG(i18n.t("repocard.template"), colors.textColor, card_width - CARD_DEFAULT_WIDTH)
251
+ : isArchived
252
+ ? // @ts-ignore
253
+ getBadgeSVG(i18n.t("repocard.archived"), colors.textColor, card_width - CARD_DEFAULT_WIDTH)
254
+ : ""}
255
+
256
+ ${descriptionSvg}
257
+
258
+ <g transform="translate(30, ${height - 75 - extraHeight})">
259
+ ${starAndForkCount}
260
+ </g>
261
+ ${extraItems}
262
+ `);
263
+ };
264
+ export { renderRepoCard };
@@ -0,0 +1,49 @@
1
+ export type StatsData = any;
2
+ export type StatCardOptions = any;
3
+ /**
4
+ * @typedef {import('../fetchers/types').StatsData} StatsData
5
+ * @typedef {import('./types').StatCardOptions} StatCardOptions
6
+ */
7
+ /**
8
+ * Renders the stats card.
9
+ *
10
+ * @param {StatsData} stats The stats data.
11
+ * @param {Partial<StatCardOptions>} options The card options.
12
+ * @returns {string} The stats card SVG object.
13
+ */
14
+ export function renderStatsCard(stats: StatsData, options: Partial<StatCardOptions>, username: any, repo?: any[], owner?: any[]): string;
15
+ /**
16
+ * Create a stats card text item.
17
+ *
18
+ * @param {object} params Object that contains the createTextNode parameters.
19
+ * @param {string} params.icon The icon to display.
20
+ * @param {string} params.label The label to display.
21
+ * @param {number} params.value The value to display.
22
+ * @param {string} params.id The id of the stat.
23
+ * @param {string=} params.unitSymbol The unit symbol of the stat.
24
+ * @param {number} params.index The index of the stat.
25
+ * @param {boolean} params.showIcons Whether to show icons.
26
+ * @param {number} params.shiftValuePos Number of pixels the value has to be shifted to the right.
27
+ * @param {boolean} params.bold Whether to bold the label.
28
+ * @param {string} params.numberFormat The format of numbers on card.
29
+ * @param {number=} params.numberPrecision The precision of numbers on card.
30
+ * @param {string} params.link Url to link to.
31
+ * @param {number} params.labelXOffset horizontal offset for label.
32
+ * @returns {string} The stats card text item SVG object.
33
+ */
34
+ export function createTextNode({ icon, label, value, id, unitSymbol, index, showIcons, shiftValuePos, bold, numberFormat, numberPrecision, link, labelXOffset, }: {
35
+ icon: string;
36
+ label: string;
37
+ value: number;
38
+ id: string;
39
+ unitSymbol?: string | undefined;
40
+ index: number;
41
+ showIcons: boolean;
42
+ shiftValuePos: number;
43
+ bold: boolean;
44
+ numberFormat: string;
45
+ numberPrecision?: number | undefined;
46
+ link: string;
47
+ labelXOffset: number;
48
+ }): string;
49
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/cards/stats.js"],"names":[],"mappings":";;AA8PA;;;GAGG;AAEH;;;;;;GAMG;AACH,uCAJW,SAAS,WACT,OAAO,CAAC,eAAe,CAAC,+CACtB,MAAM,CAsZlB;AAzmBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,kKAfG;IAAuB,IAAI,EAAnB,MAAM;IACS,KAAK,EAApB,MAAM;IACS,KAAK,EAApB,MAAM;IACS,EAAE,EAAjB,MAAM;IACU,UAAU,GAA1B,MAAM,YAAC;IACQ,KAAK,EAApB,MAAM;IACU,SAAS,EAAzB,OAAO;IACQ,aAAa,EAA5B,MAAM;IACU,IAAI,EAApB,OAAO;IACQ,YAAY,EAA3B,MAAM;IACU,eAAe,GAA/B,MAAM,YAAC;IACQ,IAAI,EAAnB,MAAM;IACS,YAAY,EAA3B,MAAM;CACd,GAAU,MAAM,CAuDlB"}