@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.
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/build/api/gist.d.ts +19 -0
- package/build/api/gist.d.ts.map +1 -0
- package/build/api/gist.js +77 -0
- package/build/api/index.d.ts +37 -0
- package/build/api/index.d.ts.map +1 -0
- package/build/api/index.js +115 -0
- package/build/api/pin.d.ts +27 -0
- package/build/api/pin.d.ts.map +1 -0
- package/build/api/pin.js +104 -0
- package/build/api/top-langs.d.ts +31 -0
- package/build/api/top-langs.d.ts.map +1 -0
- package/build/api/top-langs.js +121 -0
- package/build/api/wakatime.d.ts +28 -0
- package/build/api/wakatime.d.ts.map +1 -0
- package/build/api/wakatime.js +85 -0
- package/build/calculateRank.d.ts +28 -0
- package/build/calculateRank.d.ts.map +1 -0
- package/build/calculateRank.js +63 -0
- package/build/cards/gist.d.ts +21 -0
- package/build/cards/gist.d.ts.map +1 -0
- package/build/cards/gist.js +124 -0
- package/build/cards/repo.d.ts +21 -0
- package/build/cards/repo.d.ts.map +1 -0
- package/build/cards/repo.js +264 -0
- package/build/cards/stats.d.ts +49 -0
- package/build/cards/stats.d.ts.map +1 -0
- package/build/cards/stats.js +527 -0
- package/build/cards/top-languages.d.ts +143 -0
- package/build/cards/top-languages.d.ts.map +1 -0
- package/build/cards/top-languages.js +795 -0
- package/build/cards/wakatime.d.ts +16 -0
- package/build/cards/wakatime.d.ts.map +1 -0
- package/build/cards/wakatime.js +387 -0
- package/build/common/Card.d.ts +109 -0
- package/build/common/Card.d.ts.map +1 -0
- package/build/common/Card.js +241 -0
- package/build/common/I18n.d.ts +26 -0
- package/build/common/I18n.d.ts.map +1 -0
- package/build/common/I18n.js +34 -0
- package/build/common/color.d.ts +53 -0
- package/build/common/color.d.ts.map +1 -0
- package/build/common/color.js +92 -0
- package/build/common/config.d.ts +3 -0
- package/build/common/config.d.ts.map +1 -0
- package/build/common/config.js +69 -0
- package/build/common/constants.d.ts +6 -0
- package/build/common/constants.d.ts.map +1 -0
- package/build/common/constants.js +7 -0
- package/build/common/error.d.ts +53 -0
- package/build/common/error.d.ts.map +1 -0
- package/build/common/error.js +72 -0
- package/build/common/fmt.d.ts +27 -0
- package/build/common/fmt.d.ts.map +1 -0
- package/build/common/fmt.js +66 -0
- package/build/common/html.d.ts +10 -0
- package/build/common/html.d.ts.map +1 -0
- package/build/common/html.js +18 -0
- package/build/common/http.d.ts +9 -0
- package/build/common/http.d.ts.map +1 -0
- package/build/common/http.js +18 -0
- package/build/common/icons.d.ts +26 -0
- package/build/common/icons.d.ts.map +1 -0
- package/build/common/icons.js +77 -0
- package/build/common/languageColors.json +667 -0
- package/build/common/log.d.ts +9 -0
- package/build/common/log.d.ts.map +1 -0
- package/build/common/log.js +8 -0
- package/build/common/ops.d.ts +65 -0
- package/build/common/ops.d.ts.map +1 -0
- package/build/common/ops.js +137 -0
- package/build/common/render.d.ts +155 -0
- package/build/common/render.d.ts.map +1 -0
- package/build/common/render.js +361 -0
- package/build/common/retryer.d.ts +22 -0
- package/build/common/retryer.d.ts.map +1 -0
- package/build/common/retryer.js +86 -0
- package/build/fetchers/gist.d.ts +26 -0
- package/build/fetchers/gist.d.ts.map +1 -0
- package/build/fetchers/gist.js +99 -0
- package/build/fetchers/repo.d.ts +16 -0
- package/build/fetchers/repo.d.ts.map +1 -0
- package/build/fetchers/repo.js +116 -0
- package/build/fetchers/stats.d.ts +22 -0
- package/build/fetchers/stats.d.ts.map +1 -0
- package/build/fetchers/stats.js +339 -0
- package/build/fetchers/top-languages.d.ts +20 -0
- package/build/fetchers/top-languages.d.ts.map +1 -0
- package/build/fetchers/top-languages.js +139 -0
- package/build/fetchers/wakatime.d.ts +11 -0
- package/build/fetchers/wakatime.d.ts.map +1 -0
- package/build/fetchers/wakatime.js +25 -0
- package/build/index.d.ts +20 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +19 -0
- package/build/themes/index.d.ts +469 -0
- package/build/themes/index.d.ts.map +1 -0
- package/build/themes/index.js +468 -0
- package/build/translations.d.ts +533 -0
- package/build/translations.d.ts.map +1 -0
- package/build/translations.js +1140 -0
- package/package.json +94 -0
- package/src/api/gist.js +99 -0
- package/src/api/index.js +185 -0
- package/src/api/pin.js +146 -0
- package/src/api/top-langs.js +168 -0
- package/src/api/wakatime.js +113 -0
- package/src/calculateRank.js +86 -0
- package/src/cards/gist.js +187 -0
- package/src/cards/repo.js +365 -0
- package/src/cards/stats.js +673 -0
- package/src/cards/top-languages.js +1023 -0
- package/src/cards/types.d.ts +78 -0
- package/src/cards/wakatime.js +471 -0
- package/src/common/Card.js +275 -0
- package/src/common/I18n.js +42 -0
- package/src/common/color.js +145 -0
- package/src/common/config.js +78 -0
- package/src/common/constants.js +9 -0
- package/src/common/error.js +90 -0
- package/src/common/fmt.js +80 -0
- package/src/common/html.js +22 -0
- package/src/common/http.js +21 -0
- package/src/common/icons.js +81 -0
- package/src/common/languageColors.json +667 -0
- package/src/common/log.ts +12 -0
- package/src/common/ops.js +172 -0
- package/src/common/render.js +442 -0
- package/src/common/retryer.js +104 -0
- package/src/fetchers/gist.js +114 -0
- package/src/fetchers/repo.js +165 -0
- package/src/fetchers/stats.js +478 -0
- package/src/fetchers/top-languages.js +177 -0
- package/src/fetchers/types.d.ts +128 -0
- package/src/fetchers/wakatime.js +37 -0
- package/src/index.ts +25 -0
- package/src/themes/README.md +229 -0
- package/src/themes/index.ts +476 -0
- 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}&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}&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}&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}&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}&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"}
|