Skip to content
Open
2 changes: 2 additions & 0 deletions .github/workflows/repeat-recent-requests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ on:
- cron: "15,45 * * * *"
workflow_dispatch:

permissions: {}

jobs:
triggerRepeatRecent:
if: |
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/api/gist.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { renderGistCard } from "../cards/gist.js";
import { findInvalidColor } from "../common/color.js";
import {
MissingParamError,
retrieveSecondaryMessage,
Expand Down Expand Up @@ -26,6 +27,23 @@ export default async (
},
pat = null,
) => {
const invalidColorInput = findInvalidColor({
title_color,
icon_color,
text_color,
bg_color,
border_color,
});
if (invalidColorInput) {
return {
status: "error - permanent",
content: renderError({
message: "Something went wrong",
secondaryMessage: `Invalid color input for parameter "${invalidColorInput}"`,
}),
};
}

if (locale && !isLocaleAvailable(locale)) {
return {
status: "error - permanent",
Expand Down
19 changes: 19 additions & 0 deletions packages/core/src/api/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { renderStatsCard } from "../cards/stats.js";
import { findInvalidColor } from "../common/color.js";
import {
MissingParamError,
retrieveSecondaryMessage,
Expand Down Expand Up @@ -44,6 +45,24 @@ export default async (
},
pat = null,
) => {
const invalidColorInput = findInvalidColor({
title_color,
ring_color,
icon_color,
text_color,
bg_color,
border_color,
});
if (invalidColorInput) {
return {
status: "error - permanent",
content: renderError({
message: "Something went wrong",
secondaryMessage: `Invalid color input for parameter "${invalidColorInput}"`,
}),
};
}

if (locale && !isLocaleAvailable(locale)) {
return {
status: "error - permanent",
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/api/pin.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { renderRepoCard } from "../cards/repo.js";
import { findInvalidColor } from "../common/color.js";
import {
MissingParamError,
retrieveSecondaryMessage,
Expand Down Expand Up @@ -34,6 +35,23 @@ export default async (
},
pat = null,
) => {
const invalidColorInput = findInvalidColor({
title_color,
icon_color,
text_color,
bg_color,
border_color,
});
if (invalidColorInput) {
return {
status: "error - permanent",
content: renderError({
message: "Something went wrong",
secondaryMessage: `Invalid color input for parameter "${invalidColorInput}"`,
}),
};
}

if (locale && !isLocaleAvailable(locale)) {
return {
status: "error - permanent",
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/api/top-langs.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { renderTopLanguages } from "../cards/top-languages.js";
import { findInvalidColor } from "../common/color.js";
import {
MissingParamError,
retrieveSecondaryMessage,
Expand Down Expand Up @@ -38,6 +39,23 @@ export default async (
},
pat = null,
) => {
const invalidColorInput = findInvalidColor({
title_color,
text_color,
bg_color,
prog_bar_bg_color,
border_color,
});
if (invalidColorInput) {
return {
status: "error - permanent",
content: renderError({
message: "Something went wrong",
secondaryMessage: `Invalid color input for parameter "${invalidColorInput}"`,
}),
};
}

if (locale && !isLocaleAvailable(locale)) {
return {
status: "error - permanent",
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/api/wakatime.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { renderWakatimeCard } from "../cards/wakatime.js";
import { findInvalidColor } from "../common/color.js";
import {
MissingParamError,
retrieveSecondaryMessage,
Expand Down Expand Up @@ -32,6 +33,23 @@ export default async ({
display_format,
disable_animations,
}) => {
const invalidColorInput = findInvalidColor({
title_color,
icon_color,
text_color,
bg_color,
border_color,
});
if (invalidColorInput) {
return {
status: "error - permanent",
content: renderError({
message: "Something went wrong",
secondaryMessage: `Invalid color input for parameter "${invalidColorInput}"`,
}),
};
}

if (locale && !isLocaleAvailable(locale)) {
return {
status: "error - permanent",
Expand Down
50 changes: 30 additions & 20 deletions packages/core/src/cards/repo.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Card } from "../common/Card.js";
import { I18n } from "../common/I18n.js";
import { getCardColors } from "../common/color.js";
import { getCardColors, isValidHexColor } from "../common/color.js";
import { kFormatter, wrapTextMultiline } from "../common/fmt.js";
import { encodeHTML } from "../common/html.js";
import { icons } from "../common/icons.js";
Expand Down Expand Up @@ -32,20 +32,29 @@ const DESCRIPTION_MAX_LINES = 3;
* @param {string} textColor The color of the text.
* @returns {string} Wrapped repo description SVG object.
*/
const getBadgeSVG = (label, textColor, xOffset = 0) => `
<g data-testid="badge" class="badge" transform="translate(${320 + xOffset}, -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>
`;
const getBadgeSVG = (label, textColor, xOffset = 0) => {
if (!isValidHexColor(textColor, true)) {
throw new Error(`Invalid text color: "${textColor}"`);
}
if (!Number.isFinite(xOffset)) {
throw new Error(`Invalid xOffset: "${xOffset}"`);
}

return `
<g data-testid="badge" class="badge" transform="translate(${320 + xOffset}, -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}"
>
${encodeHTML(label)}
</text>
</g>
`;
};

/**
* @typedef {import("../fetchers/types").RepositoryData} RepositoryData Repository data.
Expand Down Expand Up @@ -110,14 +119,15 @@ const renderRepoCard = (repo, options = {}) => {
});

let repoFilter = encodeURIComponent(buildSearchFilter([nameWithOwner], []));
const encodedUsername = encodeURIComponent(username);
const STATS = {};
if (show.includes("prs_authored")) {
STATS.prs_authored = {
icon: icons.prs,
label: i18n.t("repocard.prs-authored"),
value: totalPRsAuthored,
id: "prs_authored",
link: `https://github.com/search?q=${repoFilter}author%3A${username}&amp;type=pullrequests`,
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&amp;type=pullrequests`,
};
}
if (show.includes("prs_commented")) {
Expand All @@ -126,7 +136,7 @@ const renderRepoCard = (repo, options = {}) => {
label: i18n.t("repocard.prs-commented"),
value: totalPRsCommented,
id: "prs_commented",
link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&amp;type=pullrequests`,
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=pullrequests`,
};
}
if (show.includes("prs_reviewed")) {
Expand All @@ -135,7 +145,7 @@ const renderRepoCard = (repo, options = {}) => {
label: i18n.t("repocard.prs-reviewed"),
value: totalPRsReviewed,
id: "prs_reviewed",
link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${username}+-author%3A${username}&amp;type=pullrequests`,
link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=pullrequests`,
};
}
if (show.includes("issues_authored")) {
Expand All @@ -144,7 +154,7 @@ const renderRepoCard = (repo, options = {}) => {
label: i18n.t("repocard.issues-authored"),
value: totalIssuesAuthored,
id: "issues_authored",
link: `https://github.com/search?q=${repoFilter}author%3A${username}&amp;type=issues`,
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&amp;type=issues`,
};
}
if (show.includes("issues_commented")) {
Expand All @@ -153,7 +163,7 @@ const renderRepoCard = (repo, options = {}) => {
label: i18n.t("repocard.issues-commented"),
value: totalIssuesCommented,
id: "issues_commented",
link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&amp;type=issues`,
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=issues`,
};
}

Expand Down
30 changes: 22 additions & 8 deletions packages/core/src/cards/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { I18n } from "../common/I18n.js";
import { getCardColors } from "../common/color.js";
import { CustomError } from "../common/error.js";
import { kFormatter } from "../common/fmt.js";
import { encodeHTML } from "../common/html.js";
import { icons, rankIcon } from "../common/icons.js";
import { buildSearchFilter, clampValue } from "../common/ops.js";
import { flexLayout, measureText } from "../common/render.js";
Expand Down Expand Up @@ -52,8 +53,10 @@ const LONG_LOCALES = [
/**
* Create a stats card text item.
*
* The caller must ensure that the passed `icon` and `link` are properly sanitized!
*
* @param {object} params Object that contains the createTextNode parameters.
* @param {string} params.icon The icon to display.
* @param {string} params.icon The sanitized icon to display.
* @param {string} params.label The label to display.
* @param {number} params.value The value to display.
* @param {string} params.id The id of the stat.
Expand All @@ -64,7 +67,7 @@ const LONG_LOCALES = [
* @param {boolean} params.bold Whether to bold the label.
* @param {string} params.numberFormat The format of numbers on card.
* @param {number=} params.numberPrecision The precision of numbers on card.
* @param {string} params.link Url to link to.
* @param {string} params.link Sanitized url to link to.
* @param {number} params.labelXOffset horizontal offset for label.
* @returns {string} The stats card text item SVG object.
*/
Expand All @@ -83,6 +86,16 @@ const createTextNode = ({
link,
labelXOffset = 25,
}) => {
if (!Number.isFinite(labelXOffset)) {
throw new Error(`Invalid labelXOffset: "${labelXOffset}"`);
}
if (!Number.isFinite(shiftValuePos)) {
throw new Error(`Invalid shiftValuePos: "${shiftValuePos}"`);
}
if (!Number.isFinite(index)) {
throw new Error(`Invalid index: "${index}"`);
}

const precision =
typeof numberPrecision === "number" && !isNaN(numberPrecision)
? clampValue(numberPrecision, 0, 2)
Expand All @@ -109,7 +122,7 @@ const createTextNode = ({
${iconSvg}
<text class="stat ${
bold ? " bold" : "not_bold"
}" ${labelOffset} y="12.5">${label}:</text>
}" ${labelOffset} y="12.5">${encodeHTML(label)}:</text>
<text
class="stat ${bold ? " bold" : "not_bold"}"
x="${(showIcons ? 140 : 120) + (bold ? 5 : 0) + shiftValuePos}"
Expand Down Expand Up @@ -422,13 +435,14 @@ const renderStatsCard = (
}

let repoFilter = encodeURIComponent(buildSearchFilter(repo, owner));
const encodedUsername = encodeURIComponent(username);
if (show.includes("prs_authored")) {
STATS.prs_authored = {
icon: icons.prs,
label: i18n.t("statcard.prs-authored"),
value: totalPRsAuthored,
id: "prs_authored",
link: `https://github.com/search?q=${repoFilter}author%3A${username}&amp;type=pullrequests`,
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&amp;type=pullrequests`,
};
}
if (show.includes("prs_commented")) {
Expand All @@ -437,7 +451,7 @@ const renderStatsCard = (
label: i18n.t("statcard.prs-commented"),
value: totalPRsCommented,
id: "prs_commented",
link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&amp;type=pullrequests`,
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=pullrequests`,
};
}
if (show.includes("prs_reviewed")) {
Expand All @@ -446,7 +460,7 @@ const renderStatsCard = (
label: i18n.t("statcard.prs-reviewed"),
value: totalPRsReviewed,
id: "prs_reviewed",
link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${username}+-author%3A${username}&amp;type=pullrequests`,
link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=pullrequests`,
};
}
if (show.includes("issues_authored")) {
Expand All @@ -455,7 +469,7 @@ const renderStatsCard = (
label: i18n.t("statcard.issues-authored"),
value: totalIssuesAuthored,
id: "issues_authored",
link: `https://github.com/search?q=${repoFilter}author%3A${username}&amp;type=issues`,
link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&amp;type=issues`,
};
}
if (show.includes("issues_commented")) {
Expand All @@ -464,7 +478,7 @@ const renderStatsCard = (
label: i18n.t("statcard.issues-commented"),
value: totalIssuesCommented,
id: "issues_commented",
link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&amp;type=issues`,
link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=issues`,
};
}

Expand Down
Loading
Loading