Merge branch 'dev'

This commit is contained in:
thororen1234 2026-03-18 19:19:04 -04:00
commit d4f811962f
No known key found for this signature in database
5 changed files with 179 additions and 90 deletions

View file

@ -1,7 +1,7 @@
{
"name": "equicord",
"private": "true",
"version": "1.14.5.0",
"version": "1.14.5.1",
"description": "The other cutest Discord client mod",
"homepage": "https://github.com/Equicord/Equicord#readme",
"bugs": {

View file

@ -0,0 +1,138 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Paragraph } from "@components/Paragraph";
import { Span } from "@components/Span";
import { fetchReposByUserId, fetchReposByUsername, fetchUserInfo, GitHubUserInfo } from "@equicordplugins/githubRepos/githubApi";
import { GitHubRepo } from "@equicordplugins/githubRepos/types";
import { classes } from "@utils/misc";
import { openModal } from "@utils/modal";
import { findCssClassesLazy } from "@webpack";
import { Clickable, React, useEffect, UserProfileStore, useState } from "@webpack/common";
import { ReposModal } from "./ReposModal";
const ProfileCardClasses = findCssClassesLazy("cardsList", "firstCardContainer", "card", "container");
const ProfileCardContainerClasses = findCssClassesLazy("innerContainer", "icons", "icon", "displayCount", "displayCountText", "displayCountTextColor", "breadcrumb");
const ProfileCardOverlayClasses = findCssClassesLazy("overlay", "isPrivate", "outer");
const LANGUAGE_MAP: Record<string, string> = {
"c++": "cplusplus",
"c#": "csharp",
"f#": "fsharp",
"q#": "qsharp",
"objective-c": "objectivec",
"visual basic": "visualbasic",
"shell": "bash",
"batchfile": "bash",
"vim script": "vim",
"dockerfile": "docker",
"gdscript": "godot",
"html": "html5",
};
function getLanguageIconUrl(language: string | null): string {
if (!language) return "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/github/github-original.svg";
const normalized = LANGUAGE_MAP[language.toLowerCase()] ?? language.toLowerCase().replace(/\s+/g, "");
return `https://cdn.jsdelivr.net/gh/devicons/devicon/icons/${normalized}/${normalized}-original.svg`;
}
export function ProfilePopoutComponent({ id, theme }: { id: string, theme: string; }) {
const [repos, setRepos] = useState<GitHubRepo[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [userInfo, setUserInfo] = useState<GitHubUserInfo | null>(null);
const openReposModal = () => {
if (!userInfo) return;
const sortedRepos = [...repos].sort((a, b) => b.stargazers_count - a.stargazers_count);
openModal(props => (
<ReposModal repos={sortedRepos} username={userInfo.username} rootProps={props} />
));
};
useEffect(() => {
const fetchData = async () => {
try {
const profile = UserProfileStore.getUserProfile(id);
if (!profile) { setLoading(false); return; }
const connections = profile.connectedAccounts;
if (!connections?.length) { setLoading(false); return; }
const githubConnection = connections.find(conn => conn.type === "github");
if (!githubConnection) { setLoading(false); return; }
const username = githubConnection.name;
const userInfoData = await fetchUserInfo(username);
if (userInfoData) setUserInfo(userInfoData);
const githubId = githubConnection.id;
const reposById = await fetchReposByUserId(githubId);
if (reposById) { setRepos(reposById); setLoading(false); return; }
const reposByUsername = await fetchReposByUsername(username);
setRepos(reposByUsername);
setLoading(false);
} catch (error) {
setError(error instanceof Error ? error.message : "Failed to fetch repositories");
setLoading(false);
}
};
fetchData();
}, [id]);
if (loading || error || !repos.length) return null;
const topRepos = repos.slice(0, 4);
return (
<section className={ProfileCardClasses.container}>
<ul className={ProfileCardClasses.cardsList} tabIndex={-1}>
<li className={ProfileCardClasses.firstCardContainer}>
<Clickable className={ProfileCardContainerClasses.breadcrumb} onClick={openReposModal}>
<div className={classes(ProfileCardOverlayClasses.overlay, ProfileCardContainerClasses.innerContainer, ProfileCardClasses.card)}>
<Paragraph size="xs" weight="medium">
GitHub Repositories
</Paragraph>
{!!repos.length && (
<div className={ProfileCardContainerClasses.icons}>
{topRepos.slice(0, 4).map((repo, idx) => {
const showCount = idx === 3 && repos.length > 4;
return (
<div className={ProfileCardContainerClasses.icon} key={repo.id}>
<img
src={getLanguageIconUrl(repo.language)}
alt={repo.language ?? "Unknown"}
className={showCount ? ProfileCardContainerClasses.displayCount : undefined}
style={!repo.language && theme !== "light" ? { filter: "invert(1)" } : undefined}
onError={e => {
(e.target as HTMLImageElement).src = "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/github/github-original.svg";
}}
/>
{showCount && (
<div className={ProfileCardContainerClasses.displayCountText}>
<Span className={ProfileCardContainerClasses.displayCountTextColor} size="xs" weight="medium">
+{repos.length - 3}
</Span>
</div>
)}
</div>
);
})}
</div>
)}
</div>
</Clickable>
</li>
</ul>
</section>
);
}

View file

@ -5,7 +5,6 @@
*/
import { BaseText } from "@components/BaseText";
import { Button, TextButton } from "@components/Button";
import { fetchReposByUserId, fetchReposByUsername, fetchUserInfo, GitHubUserInfo } from "@equicordplugins/githubRepos/githubApi";
import { GitHubRepo } from "@equicordplugins/githubRepos/types";
import { openModal } from "@utils/modal";
@ -15,12 +14,11 @@ import { cl, settings } from "..";
import { RepoCard } from "./RepoCard";
import { ReposModal } from "./ReposModal";
export function GitHubReposComponent({ id, theme, variant = "popout" }: { id: string, theme: string; variant?: "popout" | "tab"; }) {
export function ProfileTabComponent({ id }: { id: string, theme: string; }) {
const [repos, setRepos] = useState<GitHubRepo[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [userInfo, setUserInfo] = useState<GitHubUserInfo | null>(null);
const [returnJustButton, setReturnJustButton] = useState(false);
const openReposModal = () => {
if (!userInfo) return;
@ -64,8 +62,6 @@ export function GitHubReposComponent({ id, theme, variant = "popout" }: { id: st
const githubId = githubConnection.id;
if (!settings.store.showInMiniProfile && variant !== "tab") setReturnJustButton(true);
// Try to fetch by ID first, fall back to username
const reposById = await fetchReposByUserId(githubId);
if (reposById) {
@ -99,56 +95,26 @@ export function GitHubReposComponent({ id, theme, variant = "popout" }: { id: st
if (!repos.length) return null;
if (returnJustButton) {
return (
<Button
className={cl("button")}
size="small"
variant="secondary"
onClick={openReposModal}
>
Show GitHub Repositories
</Button>
);
}
const topRepos = variant === "tab" ? repos : repos.slice(0, 4);
return (
<div className={variant === "tab" ? `${cl("container")} ${cl("tab")}` : cl("container")}>
<div className={cl("container", "tab")}>
<BaseText size="xs" weight="semibold" className={cl("header")}>
GitHub Repositories
{userInfo && (
<span className={cl("count")}>
{variant === "tab"
? ` (${repos.length})`
: ` (Showing only top ${topRepos.length}/${userInfo.totalRepos})`
}
{` (${repos.length})`}
</span>
)}
</BaseText>
<div className={cl("list")}>
{topRepos.map(repo => (
{repos.map(repo => (
<RepoCard
key={repo.id}
repo={repo}
showStars={settings.store.showStars}
showLanguage={settings.store.showLanguage}
variant={variant}
/>))
}
</div>
{variant !== "tab" && (
<div className={cl("footer")}>
<TextButton
className={cl("show-more")}
color="secondary"
onClick={openReposModal}
>
Show More
</TextButton>
</div>
)}
</div>
);
}

View file

@ -12,7 +12,7 @@ import { React, Tooltip } from "@webpack/common";
import { cl } from "..";
import { Star } from "./Star";
export function RepoCard({ repo, showStars, showLanguage, variant }: RepoCardProps & { variant?: "popout" | "tab"; }) {
export function RepoCard({ repo, showStars, showLanguage }: RepoCardProps) {
const handleClick = () => window.open(repo.html_url, "_blank");
const renderStars = () => {
@ -62,8 +62,6 @@ export function RepoCard({ repo, showStars, showLanguage, variant }: RepoCardPro
);
};
const cardProps = variant === "tab" ? { onClick: handleClick } : {};
return (
<>
{repo.description ? (
@ -72,7 +70,7 @@ export function RepoCard({ repo, showStars, showLanguage, variant }: RepoCardPro
<div className={cl("card")}
onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter}
{...cardProps}
onClick={handleClick}
>
<div className={cl("header")}>
<BaseText size="sm" weight="medium" className={cl("name")}>
@ -85,7 +83,7 @@ export function RepoCard({ repo, showStars, showLanguage, variant }: RepoCardPro
)}
</Tooltip>
) : (
<div className={cl("card")} {...cardProps}>
<div className={cl("card")} onClick={handleClick}>
<div className={cl("header")}>
<BaseText size="sm" weight="medium" className={cl("name")} >
{repo.name}

View file

@ -7,7 +7,6 @@
import "./styles.css";
import { definePluginSettings } from "@api/Settings";
import { BaseText } from "@components/BaseText";
import ErrorBoundary from "@components/ErrorBoundary";
import { EquicordDevs } from "@utils/constants";
import { classNameFactory } from "@utils/css";
@ -16,7 +15,10 @@ import { User } from "@vencord/discord-types";
import { findByCodeLazy } from "@webpack";
import { React } from "@webpack/common";
import { GitHubReposComponent } from "./components/GitHubReposComponent";
import { ProfilePopoutComponent } from "./components/ProfilePopoutComponent";
import { ProfileTabComponent } from "./components/ProfileTabComponent";
const getProfileThemeProps = findByCodeLazy(".getPreviewThemeColors", "primaryColor:");
export const cl = classNameFactory("vc-github-repos-");
@ -31,11 +33,6 @@ export const settings = definePluginSettings({
description: "Show repository language",
default: true
},
showInMiniProfile: {
type: OptionType.BOOLEAN,
description: "Show full ui in the mini profile instead of just a button",
default: true
},
showRepositoryTab: {
type: OptionType.BOOLEAN,
description: "Show repositories tab in profile modal (hides button in connections when enabled)",
@ -43,40 +40,6 @@ export const settings = definePluginSettings({
},
});
const getProfileThemeProps = findByCodeLazy(".getPreviewThemeColors", "primaryColor:");
const ProfilePopoutComponent = ErrorBoundary.wrap(
(props: { user: User; displayProfile?: any; }) => {
return (
<GitHubReposComponent
{...props}
id={props.user.id}
theme={getProfileThemeProps(props).theme}
/>
);
},
{
noop: true,
fallback: () => <BaseText size="xs" weight="semibold" className="vc-github-repos-error" style={{ color: "var(--text-feedback-critical)" }}>
Error, Failed to render GithubRepos
</BaseText>
}
);
const ProfileRepositoriesTab = ErrorBoundary.wrap(
(props: { user: User; displayProfile?: any; }) => {
return (
<GitHubReposComponent
{...props}
id={props.user.id}
theme={getProfileThemeProps(props).theme}
variant="tab"
/>
);
},
{ noop: true }
);
export default definePlugin({
name: "GitHubRepos",
description: "Displays a user's public GitHub repositories in their profile",
@ -88,8 +51,8 @@ export default definePlugin({
{
find: /onOpenUserProfileModal:\i\}\),\i/,
replacement: {
match: /userId:\i\.id,guild:\i.{0,15}\}\).{0,100}(?=\])/,
replace: "$&,$self.ProfilePopoutComponent(arguments[0])"
match: /user:\i,widgets:.{0,100}?\}\),/,
replace: "$&$self.ProfileRepositoriesPopout(arguments[0]),"
}
},
// User Profile Modal v2
@ -97,7 +60,7 @@ export default definePlugin({
find: ".MODAL_V2,onClose:",
replacement: {
match: /displayProfile:(\i).*?connections:\i.{0,25}\i.\i\}\)\}\)/,
replace: "$&,$self.ProfilePopoutComponent({ user: arguments[0].user, displayProfile: $1 }),",
replace: "$&,$self.ProfileRepositoriesPopout({ user: arguments[0].user, displayProfile: $1 }),",
predicate: () => !settings.store.showRepositoryTab,
}
},
@ -119,6 +82,30 @@ export default definePlugin({
}
}
],
ProfilePopoutComponent,
ProfileRepositoriesTab
ProfileRepositoriesPopout: ErrorBoundary.wrap((props: { user: User; displayProfile?: any; }) => {
return (
<ProfilePopoutComponent
{...props}
id={props.user.id}
theme={getProfileThemeProps(props).theme}
/>
);
},
{
noop: true
}
),
ProfileRepositoriesTab: ErrorBoundary.wrap((props: { user: User; displayProfile?: any; }) => {
return (
<ProfileTabComponent
{...props}
id={props.user.id}
theme={getProfileThemeProps(props).theme}
/>
);
},
{
noop: true
}
)
});