/
githubmirror
/
github-readme-stats
Обзор
Документация
Войти
/
githubmirror
/
github-readme-stats
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/cards/repo.js
193 строки
5 KB
Alexandr Garbuzov
refactor: move encode HTML function into separate module (#4591)
17 окт 2025, 21:26
Не верифицирован
17 окт 2025, 21:26
b240a06
Код
Авторство
О чём код?
// @ts-check import { Card } from "../common/Card.js"; import { getCardColors } from "../common/color.js"; import { kFormatter, wrapTextMultiline } from "../common/fmt.js"; import { encodeHTML } from "../common/html.js"; import { I18n } from "../common/I18n.js"; import { icons } from "../common/icons.js"; import { clampValue, parseEmojis } from "../common/ops.js"; import { flexLayout, measureText, iconWithLabel, createLanguageNode, } from "../common/render.js"; import { repoCardLocales } from "../translations.js"; const ICON_SIZE = 16; const DESCRIPTION_LINE_WIDTH = 59; const DESCRIPTION_MAX_LINES = 3; /** * Retrieves the repository description and wraps it to fit the card width. * * @param {string} label The repository description. * @param {string} textColor The color of the text. * @returns {string} Wrapped repo description SVG object. */ const getBadgeSVG = (label, textColor) => ` <g data-testid="badge" class="badge" transform="translate(320, -18)"> <rect stroke="${textColor}" stroke-width="1" width="70" height="20" x="-12" y="-14" ry="10" rx="10"></rect> <text x="23" y="-5" alignment-baseline="central" dominant-baseline="central" text-anchor="middle" fill="${textColor}" > ${label} </text> </g> `; /** * @typedef {import("../fetchers/types").RepositoryData} RepositoryData Repository data. * @typedef {import("./types").RepoCardOptions} RepoCardOptions Repo card options. */ /** * Renders repository card details. * * @param {RepositoryData} repo Repository data. * @param {Partial<RepoCardOptions>} options Card options. * @returns {string} Repository card SVG object. */ const renderRepoCard = (repo, options = {}) => { const { name, nameWithOwner, description, primaryLanguage, isArchived, isTemplate, starCount, forkCount, } = repo; const { hide_border = false, title_color, icon_color, text_color, bg_color, show_owner = false, theme = "default_repocard", border_radius, border_color, locale, description_lines_count, } = options; const lineHeight = 10; const header = show_owner ? nameWithOwner : name; const langName = (primaryLanguage && primaryLanguage.name) || "Unspecified"; const langColor = (primaryLanguage && primaryLanguage.color) || "#333"; const descriptionMaxLines = description_lines_count ? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES) : DESCRIPTION_MAX_LINES; const desc = parseEmojis(description || "No description provided"); const multiLineDescription = wrapTextMultiline( desc, DESCRIPTION_LINE_WIDTH, descriptionMaxLines, ); const descriptionLinesCount = description_lines_count ? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES) : multiLineDescription.length; const descriptionSvg = multiLineDescription .map((line) => `<tspan dy="1.2em" x="25">${encodeHTML(line)}</tspan>`) .join(""); const height = (descriptionLinesCount > 1 ? 120 : 110) + descriptionLinesCount * lineHeight; const i18n = new I18n({ locale, translations: repoCardLocales, }); // returns theme based colors with proper overrides and defaults const colors = getCardColors({ title_color, icon_color, text_color, bg_color, border_color, theme, }); const svgLanguage = primaryLanguage ? createLanguageNode(langName, langColor) : ""; const totalStars = kFormatter(starCount); const totalForks = kFormatter(forkCount); const svgStars = iconWithLabel( icons.star, totalStars, "stargazers", ICON_SIZE, ); const svgForks = iconWithLabel( icons.fork, totalForks, "forkcount", ICON_SIZE, ); const starAndForkCount = flexLayout({ items: [svgLanguage, svgStars, svgForks], sizes: [ measureText(langName, 12), ICON_SIZE + measureText(`${totalStars}`, 12), ICON_SIZE + measureText(`${totalForks}`, 12), ], gap: 25, }).join(""); const card = new Card({ defaultTitle: header.length > 35 ? `${header.slice(0, 35)}...` : header, titlePrefixIcon: icons.contribs, width: 400, height, border_radius, colors, }); card.disableAnimations(); card.setHideBorder(hide_border); card.setHideTitle(false); card.setCSS(` .description { font: 400 13px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} } .gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} } .icon { fill: ${colors.iconColor} } .badge { font: 600 11px 'Segoe UI', Ubuntu, Sans-Serif; } .badge rect { opacity: 0.2 } `); return card.render(` ${ isTemplate ? // @ts-ignore getBadgeSVG(i18n.t("repocard.template"), colors.textColor) : isArchived ? // @ts-ignore getBadgeSVG(i18n.t("repocard.archived"), colors.textColor) : "" } <text class="description" x="25" y="-5"> ${descriptionSvg} </text> <g transform="translate(30, ${height - 75})"> ${starAndForkCount} </g> `); }; export { renderRepoCard }; export default renderRepoCard;