diff --git a/src/api/Badges.ts b/src/api/Badges.ts index b50016c5b..061bdeb8a 100644 --- a/src/api/Badges.ts +++ b/src/api/Badges.ts @@ -36,7 +36,7 @@ export interface ProfileBadge { image?: string; link?: string; /** Action to perform when you click the badge */ - onClick?(): void; + onClick?(event: React.MouseEvent, props: BadgeUserArgs): void; /** Should the user display this badge? */ shouldShow?(userInfo: BadgeUserArgs): boolean; /** Optional props (e.g. style) for the badge, ignored for component badges */ @@ -87,9 +87,7 @@ export function _getBadges(args: BadgeUserArgs) { export interface BadgeUserArgs { user: User; - profile: Profile; - premiumSince: Date; - premiumGuildSince?: Date; + guildId: string; } interface ConnectedAccount { diff --git a/src/components/PluginSettings/ContributorModal.tsx b/src/components/PluginSettings/ContributorModal.tsx index 82c230259..99a8da168 100644 --- a/src/components/PluginSettings/ContributorModal.tsx +++ b/src/components/PluginSettings/ContributorModal.tsx @@ -9,10 +9,12 @@ import "./contributorModal.css"; import { useSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; +import { Link } from "@components/Link"; import { DevsById } from "@utils/constants"; import { fetchUserProfile, getTheme, Theme } from "@utils/discord"; +import { pluralise } from "@utils/misc"; import { ModalContent, ModalRoot, openModal } from "@utils/modal"; -import { Forms, MaskedLink, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common"; +import { Forms, MaskedLink, showToast, Tooltip, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common"; import { User } from "discord-types/general"; import Plugins from "~plugins"; @@ -72,6 +74,8 @@ function ContributorModal({ user }: { user: User; }) { .sort((a, b) => Number(a.required ?? false) - Number(b.required ?? false)); }, [user.id, user.username]); + const ContributedHyperLink = contributed; + return ( <>
@@ -84,30 +88,48 @@ function ContributorModal({ user }: { user: User; }) {
{website && ( - - - + + {props => ( + + + + )} + )} {githubName && ( - - - + + {props => ( + + + + )} + )}
-
- {plugins.map(p => - showToast("Restart to apply changes!")} - /> - )} -
+ {plugins.length ? ( + + This person has {ContributedHyperLink} to {pluralise(plugins.length, "plugin")}! + + ) : ( + + This person has not made any plugins. They likely {ContributedHyperLink} to Vencord in other ways! + + )} + + {!!plugins.length && ( +
+ {plugins.map(p => + showToast("Restart to apply changes!")} + /> + )} +
+ )} ); } diff --git a/src/components/PluginSettings/contributorModal.css b/src/components/PluginSettings/contributorModal.css index 09f0103fd..ad2f1330c 100644 --- a/src/components/PluginSettings/contributorModal.css +++ b/src/components/PluginSettings/contributorModal.css @@ -25,11 +25,13 @@ display: block; position: absolute; height: 100%; - width: 16px; + width: 32px; background: var(--background-tertiary); z-index: -1; - left: -16px; + left: -32px; top: 0; + border-top-left-radius: 9999px; + border-bottom-left-radius: 9999px; } .vc-author-modal-avatar { @@ -55,4 +57,5 @@ .vc-author-modal-plugins { display: grid; gap: 0.5em; + margin-top: 0.75em; } diff --git a/src/plugins/_api/badges.tsx b/src/plugins/_api/badges.tsx index 6b1a79cd5..6cbe1feb0 100644 --- a/src/plugins/_api/badges.tsx +++ b/src/plugins/_api/badges.tsx @@ -34,14 +34,13 @@ const ContributorBadge: ProfileBadge = { description: "Vencord Contributor", image: CONTRIBUTOR_BADGE, position: BadgePosition.START, - props: { - style: { - borderRadius: "50%", - transform: "scale(0.9)" // The image is a bit too big compared to default badges - } - }, shouldShow: ({ user }) => isPluginDev(user.id), - link: "https://github.com/Vendicated/Vencord" + onClick(_, { user }) { + // circular import shenanigans + const { openContributorModal } = require("@components/PluginSettings/ContributorModal") as typeof import("@components/PluginSettings/ContributorModal"); + // setImmediate is needed to run on later tick to workaround limitation in proxyLazy + setImmediate(() => openContributorModal(user)); + } }; let DonorBadges = {} as Record>>; @@ -85,7 +84,7 @@ export default definePlugin({ // conditionally override their onClick with badge.onClick if it exists { match: /href:(\i)\.link/, - replace: "...($1.onClick && { onClick: $1.onClick }),$&" + replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, arguments[0]) }),$&" } ] } diff --git a/src/plugins/_core/noTrack.ts b/src/plugins/_core/noTrack.ts index 424e62c04..67f6c6448 100644 --- a/src/plugins/_core/noTrack.ts +++ b/src/plugins/_core/noTrack.ts @@ -16,17 +16,31 @@ * along with this program. If not, see . */ +import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; +import definePlugin, { OptionType } from "@utils/types"; + +const settings = definePluginSettings({ + disableAnalytics: { + type: OptionType.BOOLEAN, + description: "Disable Discord's tracking (analytics/'science')", + default: true, + restartNeeded: true + } +}); export default definePlugin({ name: "NoTrack", - description: "Disable Discord's tracking ('science'), metrics and Sentry crash reporting", + description: "Disable Discord's tracking (analytics/'science'), metrics and Sentry crash reporting", authors: [Devs.Cyn, Devs.Ven, Devs.Nuckyz, Devs.Arrow], required: true, + + settings, + patches: [ { find: "AnalyticsActionHandlers.handle", + predicate: () => settings.store.disableAnalytics, replacement: { match: /^.+$/, replace: "()=>{}", @@ -44,11 +58,11 @@ export default definePlugin({ replacement: [ { match: /this\._intervalId=/, - replace: "this._intervalId=undefined&&" + replace: "this._intervalId=void 0&&" }, { - match: /(increment\(\i\){)/, - replace: "$1return;" + match: /(?:increment|distribution)\(\i(?:,\i)?\){/g, + replace: "$&return;" } ] }, diff --git a/src/plugins/friendsSince/index.tsx b/src/plugins/friendsSince/index.tsx index d6b7d1072..fb431b52b 100644 --- a/src/plugins/friendsSince/index.tsx +++ b/src/plugins/friendsSince/index.tsx @@ -9,9 +9,8 @@ import { Devs } from "@utils/constants"; import { getCurrentChannel } from "@utils/discord"; import definePlugin from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { React, RelationshipStore } from "@webpack/common"; +import { Heading, React, RelationshipStore, Text } from "@webpack/common"; -const { Heading, Text } = findByPropsLazy("Heading", "Text"); const container = findByPropsLazy("memberSinceWrapper"); const { getCreatedAtDate } = findByPropsLazy("getCreatedAtDate"); const clydeMoreInfo = findByPropsLazy("clydeMoreInfo"); diff --git a/src/utils/misc.tsx b/src/utils/misc.tsx index 2b8ccf8a7..32010e59b 100644 --- a/src/utils/misc.tsx +++ b/src/utils/misc.tsx @@ -114,3 +114,7 @@ export function identity(value: T): T { export const isMobile = navigator.userAgent.includes("Mobi"); export const isPluginDev = (id: string) => Object.hasOwn(DevsById, id); + +export function pluralise(amount: number, singular: string, plural = singular + "s") { + return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`; +} diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index 24477c725..020c8fc7e 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -36,6 +36,7 @@ export let Tooltip: t.Tooltip; export let TextInput: t.TextInput; export let TextArea: t.TextArea; export let Text: t.Text; +export let Heading: t.HeadingTag; export let Select: t.Select; export let SearchableSelect: t.SearchableSelect; export let Slider: t.Slider; @@ -59,6 +60,28 @@ export const Flex = waitForComponent("Flex", ["Justify", "Align", "Wrap" export const { OAuth2AuthorizeModal } = findByPropsLazy("OAuth2AuthorizeModal"); waitFor(["FormItem", "Button"], m => { - ({ useToken, Card, Button, FormSwitch: Switch, Tooltip, TextInput, TextArea, Text, Select, SearchableSelect, Slider, ButtonLooks, TabBar, Popout, Dialog, Paginator, ScrollerThin, Clickable, Avatar, FocusLock } = m); + ({ + useToken, + Card, + Button, + FormSwitch: Switch, + Tooltip, + TextInput, + TextArea, + Text, + Select, + SearchableSelect, + Slider, + ButtonLooks, + TabBar, + Popout, + Dialog, + Paginator, + ScrollerThin, + Clickable, + Avatar, + FocusLock, + Heading + } = m); Forms = m; }); diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index 3e3ffa4bd..c51264370 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -20,23 +20,24 @@ import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttribute export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; export type FormTextTypes = Record<"DEFAULT" | "INPUT_PLACEHOLDER" | "DESCRIPTION" | "LABEL_BOLD" | "LABEL_SELECTED" | "LABEL_DESCRIPTOR" | "ERROR" | "SUCCESS", string>; -export type Heading = `h${1 | 2 | 3 | 4 | 5 | 6}`; +export type HeadingTag = `h${1 | 2 | 3 | 4 | 5 | 6}`; export type Margins = Record<"marginTop16" | "marginTop8" | "marginBottom8" | "marginTop20" | "marginBottom20", string>; export type ButtonLooks = Record<"FILLED" | "INVERTED" | "OUTLINED" | "LINK" | "BLANK", string>; export type TextProps = PropsWithChildren & { variant?: TextVariant; - tag?: "div" | "span" | "p" | "strong" | Heading; + tag?: "div" | "span" | "p" | "strong" | HeadingTag; selectable?: boolean; lineClamp?: number; }>; export type Text = ComponentType; +export type Heading = ComponentType; export type FormTitle = ComponentType & PropsWithChildren<{ /** default is h5 */ - tag?: Heading; + tag?: HeadingTag; faded?: boolean; disabled?: boolean; required?: boolean; @@ -45,7 +46,7 @@ export type FormTitle = ComponentType & PropsWithChi export type FormSection = ComponentType>; type FocusLock = ComponentType + containerRef: RefObject; }>>;