Merge branch 'dev'
This commit is contained in:
commit
d4f811962f
5 changed files with 179 additions and 90 deletions
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
)
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue