mirror of
https://github.com/anuraghazra/github-readme-stats.git
synced 2024-12-15 06:04:17 +08:00
feat: multiline text in repo description (#206)
* Enable multi line repo card descriptions * Adjust unit tests to multi line descriptions * refactor: refactored code & fixed tests * style: minor height adjustments * chore: codecov stuck Co-authored-by: anuraghazra <hazru.anurag@gmail.com>
This commit is contained in:
parent
68198d8b16
commit
11757dba42
@ -21,7 +21,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^8.2.0",
|
||||
"emoji-name-map": "^1.2.8"
|
||||
"emoji-name-map": "^1.2.8",
|
||||
"word-wrap": "^1.2.3"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
@ -3,6 +3,7 @@ const {
|
||||
encodeHTML,
|
||||
getCardColors,
|
||||
FlexLayout,
|
||||
wrapTextMultiline,
|
||||
} = require("../src/utils");
|
||||
const icons = require("./icons");
|
||||
const toEmoji = require("emoji-name-map");
|
||||
@ -31,7 +32,6 @@ const renderRepoCard = (repo, options = {}) => {
|
||||
const langName = (primaryLanguage && primaryLanguage.name) || "Unspecified";
|
||||
const langColor = (primaryLanguage && primaryLanguage.color) || "#333";
|
||||
|
||||
const height = 120;
|
||||
const shiftText = langName.length > 15 ? 0 : 30;
|
||||
|
||||
let desc = description || "No description provided";
|
||||
@ -41,9 +41,12 @@ const renderRepoCard = (repo, options = {}) => {
|
||||
return toEmoji.get(emoji) || "";
|
||||
});
|
||||
|
||||
if (desc.length > 55) {
|
||||
desc = `${desc.slice(0, 55)}..`;
|
||||
}
|
||||
const multiLineDescription = wrapTextMultiline(desc);
|
||||
const descriptionLines = multiLineDescription.length;
|
||||
const lineHeight = 10;
|
||||
|
||||
const height =
|
||||
(descriptionLines > 1 ? 120 : 110) + descriptionLines * lineHeight;
|
||||
|
||||
// returns theme based colors with proper overrides and defaults
|
||||
const { titleColor, textColor, iconColor, bgColor } = getCardColors({
|
||||
@ -60,11 +63,11 @@ const renderRepoCard = (repo, options = {}) => {
|
||||
const getBadgeSVG = (label) => `
|
||||
<g data-testid="badge" class="badge" transform="translate(320, 38)">
|
||||
<rect stroke="${textColor}" stroke-width="1" width="70" height="20" x="-12" y="-14" ry="10" rx="10"></rect>
|
||||
<text
|
||||
<text
|
||||
x="23" y="-5"
|
||||
alignment-baseline="central"
|
||||
dominant-baseline="central"
|
||||
text-anchor="middle"
|
||||
alignment-baseline="central"
|
||||
dominant-baseline="central"
|
||||
text-anchor="middle"
|
||||
fill="${textColor}"
|
||||
>
|
||||
${label}
|
||||
@ -74,7 +77,7 @@ const renderRepoCard = (repo, options = {}) => {
|
||||
|
||||
const svgLanguage = primaryLanguage
|
||||
? `
|
||||
<g data-testid="primary-lang" transform="translate(30, 100)">
|
||||
<g data-testid="primary-lang" transform="translate(30, 0)">
|
||||
<circle data-testid="lang-color" cx="0" cy="-5" r="6" fill="${langColor}" />
|
||||
<text data-testid="lang-name" class="gray" x="15">${langName}</text>
|
||||
</g>
|
||||
@ -124,15 +127,23 @@ const renderRepoCard = (repo, options = {}) => {
|
||||
? getBadgeSVG("Archived")
|
||||
: ""
|
||||
}
|
||||
|
||||
<text class="description" x="25" y="70">${encodeHTML(desc)}</text>
|
||||
|
||||
${svgLanguage}
|
||||
|
||||
<g transform="translate(${primaryLanguage ? 155 - shiftText : 25}, 100)">
|
||||
${FlexLayout({ items: [svgStars, svgForks], gap: 65 }).join("")}
|
||||
</g>
|
||||
|
||||
<text class="description" x="25" y="50">
|
||||
${multiLineDescription
|
||||
.map((line) => `<tspan dy="1.2em" x="25">${encodeHTML(line)}</tspan>`)
|
||||
.join("")}
|
||||
</text>
|
||||
|
||||
<g transform="translate(0, ${height - 20})">
|
||||
${svgLanguage}
|
||||
|
||||
<g
|
||||
data-testid="star-fork-group"
|
||||
transform="translate(${primaryLanguage ? 155 - shiftText : 25}, 0)"
|
||||
>
|
||||
${FlexLayout({ items: [svgStars, svgForks], gap: 65 }).join("")}
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
`;
|
||||
};
|
||||
|
23
src/utils.js
23
src/utils.js
@ -1,4 +1,5 @@
|
||||
const axios = require("axios");
|
||||
const wrap = require("word-wrap");
|
||||
const themes = require("../themes");
|
||||
|
||||
const renderError = (message, secondaryMessage = "") => {
|
||||
@ -127,10 +128,27 @@ function getCardColors({
|
||||
return { titleColor, iconColor, textColor, bgColor };
|
||||
}
|
||||
|
||||
const fn = () => {};
|
||||
function wrapTextMultiline(text, width = 60, maxLines = 3) {
|
||||
const wrapped = wrap(encodeHTML(text), { width })
|
||||
.split("\n") // Split wrapped lines to get an array of lines
|
||||
.map((line) => line.trim()); // Remove leading and trailing whitespace of each line
|
||||
|
||||
const lines = wrapped.slice(0, maxLines); // Only consider maxLines lines
|
||||
|
||||
// Add "..." to the last line if the text exceeds maxLines
|
||||
if (wrapped.length > maxLines) {
|
||||
lines[maxLines - 1] += "...";
|
||||
}
|
||||
|
||||
// Remove empty lines if text fits in less than maxLines lines
|
||||
const multiLineText = lines.filter(Boolean);
|
||||
return multiLineText;
|
||||
}
|
||||
|
||||
const noop = () => {};
|
||||
// return console instance based on the environment
|
||||
const logger =
|
||||
process.env.NODE_ENV !== "test" ? console : { log: fn, error: fn };
|
||||
process.env.NODE_ENV !== "test" ? console : { log: noop, error: noop };
|
||||
|
||||
const CONSTANTS = {
|
||||
THIRTY_MINUTES: 1800,
|
||||
@ -150,6 +168,7 @@ module.exports = {
|
||||
FlexLayout,
|
||||
getCardColors,
|
||||
clampValue,
|
||||
wrapTextMultiline,
|
||||
logger,
|
||||
CONSTANTS,
|
||||
};
|
||||
|
@ -29,7 +29,7 @@ describe("Test renderRepoCard", () => {
|
||||
expect(header).toHaveTextContent("convoychat");
|
||||
expect(header).not.toHaveTextContent("anuraghazra");
|
||||
expect(document.getElementsByClassName("description")[0]).toHaveTextContent(
|
||||
"Help us take over the world! React + TS + GraphQL Chat .."
|
||||
"Help us take over the world! React + TS + GraphQL Chat App"
|
||||
);
|
||||
expect(queryByTestId(document.body, "stargazers")).toHaveTextContent("38k");
|
||||
expect(queryByTestId(document.body, "forkcount")).toHaveTextContent("100");
|
||||
@ -55,12 +55,16 @@ describe("Test renderRepoCard", () => {
|
||||
document.body.innerHTML = renderRepoCard({
|
||||
...data_repo.repository,
|
||||
description:
|
||||
"Very long long long long long long long long text it should trim it",
|
||||
"The quick brown fox jumps over the lazy dog is an English-language pangram—a sentence that contains all of the letters of the English alphabet",
|
||||
});
|
||||
|
||||
expect(document.getElementsByClassName("description")[0]).toHaveTextContent(
|
||||
"Very long long long long long long long long text it sh.."
|
||||
);
|
||||
expect(
|
||||
document.getElementsByClassName("description")[0].children[0].textContent
|
||||
).toBe("The quick brown fox jumps over the lazy dog is an");
|
||||
|
||||
expect(
|
||||
document.getElementsByClassName("description")[0].children[1].textContent
|
||||
).toBe("English-language pangram—a sentence that contains all");
|
||||
|
||||
// Should not trim
|
||||
document.body.innerHTML = renderRepoCard({
|
||||
@ -95,9 +99,9 @@ describe("Test renderRepoCard", () => {
|
||||
});
|
||||
|
||||
expect(queryByTestId(document.body, "primary-lang")).toBeInTheDocument();
|
||||
expect(document.getElementsByTagName("g")[1]).toHaveAttribute(
|
||||
expect(queryByTestId(document.body, "star-fork-group")).toHaveAttribute(
|
||||
"transform",
|
||||
"translate(155, 100)"
|
||||
"translate(155, 0)"
|
||||
);
|
||||
|
||||
// Small lang
|
||||
@ -109,9 +113,9 @@ describe("Test renderRepoCard", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(document.getElementsByTagName("g")[1]).toHaveAttribute(
|
||||
expect(queryByTestId(document.body, "star-fork-group")).toHaveAttribute(
|
||||
"transform",
|
||||
"translate(125, 100)"
|
||||
"translate(125, 0)"
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -5,6 +5,7 @@ const {
|
||||
renderError,
|
||||
FlexLayout,
|
||||
getCardColors,
|
||||
wrapTextMultiline,
|
||||
} = require("../src/utils");
|
||||
|
||||
const { queryByTestId } = require("@testing-library/dom");
|
||||
@ -108,3 +109,28 @@ describe("Test utils.js", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("wrapTextMultiline", () => {
|
||||
it("should not wrap small texts", () => {
|
||||
{
|
||||
let multiLineText = wrapTextMultiline("Small text should not wrap");
|
||||
expect(multiLineText).toEqual(["Small text should not wrap"]);
|
||||
}
|
||||
});
|
||||
it("should wrap large texts", () => {
|
||||
let multiLineText = wrapTextMultiline(
|
||||
"Hello world long long long text",
|
||||
20,
|
||||
3
|
||||
);
|
||||
expect(multiLineText).toEqual(["Hello world long", "long long text"]);
|
||||
});
|
||||
it("should wrap large texts and limit max lines", () => {
|
||||
let multiLineText = wrapTextMultiline(
|
||||
"Hello world long long long text",
|
||||
10,
|
||||
2
|
||||
);
|
||||
expect(multiLineText).toEqual(["Hello", "world long..."]);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user