aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVen <vendicated@riseup.net>2023-01-25 03:25:29 +0100
committerGitHub <noreply@github.com>2023-01-25 03:25:29 +0100
commitf19504f8282702bc6945a3d97acbee1a1fbe1b8d (patch)
tree0a84a831dbd4e3fa040b6b1287db4d309de7c5d9 /src
parenta38ac956dfaf53711a4cddea73ae1b8cf617211a (diff)
downloadVencord-f19504f8282702bc6945a3d97acbee1a1fbe1b8d.tar.gz
Vencord-f19504f8282702bc6945a3d97acbee1a1fbe1b8d.tar.bz2
Vencord-f19504f8282702bc6945a3d97acbee1a1fbe1b8d.zip
split up webpack commons into categories & type everything (#455)
Diffstat (limited to 'src')
-rw-r--r--src/Vencord.ts4
-rw-r--r--src/components/PluginSettings/index.tsx4
-rw-r--r--src/components/VencordSettings/BackupRestoreTab.tsx2
-rw-r--r--src/components/VencordSettings/ThemesTab.tsx2
-rw-r--r--src/components/VencordSettings/index.tsx4
-rw-r--r--src/ipcMain/updater/git.ts2
-rw-r--r--src/ipcMain/updater/http.ts2
-rw-r--r--src/plugins/settings.tsx4
-rw-r--r--src/plugins/spotifyControls/SpotifyStore.ts4
-rw-r--r--src/plugins/viewRaw.tsx2
-rw-r--r--src/utils/misc.tsx28
-rw-r--r--src/utils/modal.tsx76
-rw-r--r--src/webpack/common.tsx312
-rw-r--r--src/webpack/common/components.ts53
-rw-r--r--src/webpack/common/index.ts27
-rw-r--r--src/webpack/common/internal.tsx36
-rw-r--r--src/webpack/common/menu.ts51
-rw-r--r--src/webpack/common/react.ts33
-rw-r--r--src/webpack/common/stores.ts54
-rw-r--r--src/webpack/common/types/components.d.ts284
-rw-r--r--src/webpack/common/types/fluxEvents.d.ts40
-rw-r--r--src/webpack/common/types/menu.d.ts68
-rw-r--r--src/webpack/common/types/utils.d.ts98
-rw-r--r--src/webpack/common/utils.ts112
24 files changed, 930 insertions, 372 deletions
diff --git a/src/Vencord.ts b/src/Vencord.ts
index 82d5af0..ac8579b 100644
--- a/src/Vencord.ts
+++ b/src/Vencord.ts
@@ -32,7 +32,7 @@ import { PlainSettings, Settings } from "./api/settings";
import { patches, PMLogger, startAllPlugins } from "./plugins";
import { checkForUpdates, rebuild, update, UpdateLogger } from "./utils/updater";
import { onceReady } from "./webpack";
-import { Router } from "./webpack/common";
+import { SettingsRouter } from "./webpack/common";
export let Components: any;
@@ -71,7 +71,7 @@ async function init() {
"View Update",
() => {
popNotice();
- Router.open("VencordUpdater");
+ SettingsRouter.open("VencordUpdater");
}
);
}, 10_000);
diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx
index f439753..34e6828 100644
--- a/src/components/PluginSettings/index.tsx
+++ b/src/components/PluginSettings/index.tsx
@@ -326,7 +326,9 @@ export default ErrorBoundary.wrap(function PluginSettings() {
<div className={cl("grid")}>
{plugins}
</div>
- <Forms.FormDivider />
+
+ <Forms.FormDivider className={Margins.marginTop20} />
+
<Forms.FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}>
Required Plugins
</Forms.FormTitle>
diff --git a/src/components/VencordSettings/BackupRestoreTab.tsx b/src/components/VencordSettings/BackupRestoreTab.tsx
index 546db35..2ea0452 100644
--- a/src/components/VencordSettings/BackupRestoreTab.tsx
+++ b/src/components/VencordSettings/BackupRestoreTab.tsx
@@ -45,7 +45,7 @@ function BackupRestoreTab() {
</Text>
<Flex>
<Button
- onClick={uploadSettingsBackup}
+ onClick={() => uploadSettingsBackup()}
size={Button.Sizes.SMALL}
>
Import Settings
diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx
index 69fcc29..b59590c 100644
--- a/src/components/VencordSettings/ThemesTab.tsx
+++ b/src/components/VencordSettings/ThemesTab.tsx
@@ -75,7 +75,7 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
export default ErrorBoundary.wrap(function () {
const settings = useSettings();
- const ref = React.useRef<HTMLTextAreaElement>();
+ const ref = React.useRef<HTMLTextAreaElement>(null);
function onBlur() {
settings.themeLinks = [...new Set(
diff --git a/src/components/VencordSettings/index.tsx b/src/components/VencordSettings/index.tsx
index 2ab9407..acd81c3 100644
--- a/src/components/VencordSettings/index.tsx
+++ b/src/components/VencordSettings/index.tsx
@@ -21,7 +21,7 @@ import "./settingsStyles.css";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { findByCodeLazy } from "@webpack";
-import { Forms, Router, Text } from "@webpack/common";
+import { Forms, SettingsRouter, Text } from "@webpack/common";
import BackupRestoreTab from "./BackupRestoreTab";
import PluginsTab from "./PluginsTab";
@@ -65,7 +65,7 @@ function Settings(props: SettingsProps) {
look={TabBar.Looks.BRAND}
className={cl("tab-bar")}
selectedItem={tab}
- onItemSelect={Router.open}
+ onItemSelect={SettingsRouter.open}
>
{Object.entries(SettingsTabs).map(([key, { name, component }]) => {
if (!component) return null;
diff --git a/src/ipcMain/updater/git.ts b/src/ipcMain/updater/git.ts
index e787b8f..89c2d3c 100644
--- a/src/ipcMain/updater/git.ts
+++ b/src/ipcMain/updater/git.ts
@@ -28,7 +28,7 @@ const VENCORD_SRC_DIR = join(__dirname, "..");
const execFile = promisify(cpExecFile);
-const isFlatpak = Boolean(process.env.FLATPAK_ID?.includes("discordapp") || process.env.FLATPAK_ID?.includes("Discord"));
+const isFlatpak = process.platform === "linux" && Boolean(process.env.FLATPAK_ID?.includes("discordapp") || process.env.FLATPAK_ID?.includes("Discord"));
if (process.platform === "darwin") process.env.PATH = `/usr/local/bin:${process.env.PATH}`;
diff --git a/src/ipcMain/updater/http.ts b/src/ipcMain/updater/http.ts
index 25dc5ba..cc10631 100644
--- a/src/ipcMain/updater/http.ts
+++ b/src/ipcMain/updater/http.ts
@@ -49,7 +49,7 @@ async function calculateGitChanges() {
const res = await githubGet(`/compare/${gitHash}...HEAD`);
const data = JSON.parse(res.toString("utf-8"));
- return data.commits.map(c => ({
+ return data.commits.map((c: any) => ({
// github api only sends the long sha
hash: c.sha.slice(0, 7),
author: c.author.login,
diff --git a/src/plugins/settings.tsx b/src/plugins/settings.tsx
index 36bf525..67d1f8d 100644
--- a/src/plugins/settings.tsx
+++ b/src/plugins/settings.tsx
@@ -22,7 +22,7 @@ import { Devs } from "@utils/constants";
import Logger from "@utils/Logger";
import { LazyComponent } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
-import { Router } from "@webpack/common";
+import { SettingsRouter } from "@webpack/common";
import gitHash from "~git-hash";
@@ -69,7 +69,7 @@ export default definePlugin({
}],
makeSettingsCategories({ ID }: { ID: Record<string, unknown>; }) {
- const makeOnClick = (tab: string) => () => Router.open(tab);
+ const makeOnClick = (tab: string) => () => SettingsRouter.open(tab);
const cats = [
{
diff --git a/src/plugins/spotifyControls/SpotifyStore.ts b/src/plugins/spotifyControls/SpotifyStore.ts
index 641ba1a..ceac577 100644
--- a/src/plugins/spotifyControls/SpotifyStore.ts
+++ b/src/plugins/spotifyControls/SpotifyStore.ts
@@ -76,10 +76,6 @@ export const SpotifyStore = proxyLazy(() => {
const API_BASE = "https://api.spotify.com/v1/me/player";
class SpotifyStore extends Store {
- constructor(dispatcher: any, handlers: any) {
- super(dispatcher, handlers);
- }
-
public mPosition = 0;
private start = 0;
diff --git a/src/plugins/viewRaw.tsx b/src/plugins/viewRaw.tsx
index fc7a42a..5105203 100644
--- a/src/plugins/viewRaw.tsx
+++ b/src/plugins/viewRaw.tsx
@@ -98,7 +98,7 @@ function openViewRawModal(msg: Message) {
<>
<Forms.FormTitle tag="h5">Content</Forms.FormTitle>
<CodeBlock content={msg.content} lang="" />
- <Forms.FormDivider classes={Margins.marginBottom20} />
+ <Forms.FormDivider className={Margins.marginBottom20} />
</>
)}
diff --git a/src/utils/misc.tsx b/src/utils/misc.tsx
index 0ef7ffb..c64d9e1 100644
--- a/src/utils/misc.tsx
+++ b/src/utils/misc.tsx
@@ -153,34 +153,6 @@ export function sleep(ms: number): Promise<void> {
}
/**
- * Wraps a Function into a try catch block and logs any errors caught
- * Due to the nature of this function, not all paths return a result.
- * Thus, for consistency, the returned functions will always return void or Promise<void>
- *
- * @param name Name identifying the wrapped function. This will appear in the logged errors
- * @param func Function (async or sync both work)
- * @param thisObject Optional thisObject
- * @returns Wrapped Function
- */
-export function suppressErrors<F extends Function>(name: string, func: F, thisObject?: any): F {
- return (func.constructor.name === "AsyncFunction"
- ? async function (this: any) {
- try {
- await func.apply(thisObject ?? this, arguments);
- } catch (e) {
- console.error(`Caught an Error in ${name || "anonymous"}\n`, e);
- }
- }
- : function (this: any) {
- try {
- func.apply(thisObject ?? this, arguments);
- } catch (e) {
- console.error(`Caught an Error in ${name || "anonymous"}\n`, e);
- }
- }) as any as F;
-}
-
-/**
* Wrap the text in ``` with an optional language
*/
export function makeCodeblock(text: string, language?: string) {
diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx
index 73dd009..3174cac 100644
--- a/src/utils/modal.tsx
+++ b/src/utils/modal.tsx
@@ -17,6 +17,9 @@
*/
import { filters, mapMangledModuleLazy } from "@webpack";
+import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react";
+
+import { LazyComponent } from "./misc";
export enum ModalSize {
SMALL = "small",
@@ -44,16 +47,7 @@ export interface ModalOptions {
onCloseCallback?: (() => void);
}
-interface ModalRootProps {
- transitionState: ModalTransitionState;
- children: React.ReactNode;
- size?: ModalSize;
- role?: "alertdialog" | "dialog";
- className?: string;
- onAnimationEnd?(): string;
-}
-
-type RenderFunction = (props: ModalProps) => React.ReactNode;
+type RenderFunction = (props: ModalProps) => ReactNode;
export const Modals = mapMangledModuleLazy(".closeWithCircleBackground", {
ModalRoot: filters.byCode(".root"),
@@ -61,13 +55,63 @@ export const Modals = mapMangledModuleLazy(".closeWithCircleBackground", {
ModalContent: filters.byCode(".content"),
ModalFooter: filters.byCode(".footerSeparator"),
ModalCloseButton: filters.byCode(".closeWithCircleBackground"),
-});
+}) as {
+ ModalRoot: ComponentType<PropsWithChildren<{
+ transitionState: ModalTransitionState;
+ size?: ModalSize;
+ role?: "alertdialog" | "dialog";
+ className?: string;
+ fullscreenOnMobile?: boolean;
+ "aria-label"?: string;
+ "aria-labelledby"?: string;
+ onAnimationEnd?(): string;
+ }>>;
+ ModalHeader: ComponentType<PropsWithChildren<{
+ /** Flex.Justify.START */
+ justify?: string;
+ /** Flex.Direction.HORIZONTAL */
+ direction?: string;
+ /** Flex.Align.CENTER */
+ align?: string;
+ /** Flex.Wrap.NO_WRAP */
+ wrap?: string;
+ separator?: boolean;
+
+ className?: string;
+ }>>;
+ /** This also accepts Scroller props but good luck with that */
+ ModalContent: ComponentType<PropsWithChildren<{
+ className?: string;
+ scrollerRef?: Ref<HTMLElement>;
+ [prop: string]: any;
+ }>>;
+ ModalFooter: ComponentType<PropsWithChildren<{
+ /** Flex.Justify.START */
+ justify?: string;
+ /** Flex.Direction.HORIZONTAL_REVERSE */
+ direction?: string;
+ /** Flex.Align.STRETCH */
+ align?: string;
+ /** Flex.Wrap.NO_WRAP */
+ wrap?: string;
+ separator?: boolean;
+
+ className?: string;
+ }>>;
+ ModalCloseButton: ComponentType<{
+ focusProps?: any;
+ onClick(): void;
+ withCircleBackground?: boolean;
+ hideOnFullscreen?: boolean;
+ className?: string;
+ }>;
+};
-export const ModalRoot = (props: ModalRootProps) => <Modals.ModalRoot {...props} />;
-export const ModalHeader = (props: any) => <Modals.ModalHeader {...props} />;
-export const ModalContent = (props: any) => <Modals.ModalContent {...props} />;
-export const ModalFooter = (props: any) => <Modals.ModalFooter {...props} />;
-export const ModalCloseButton = (props: any) => <Modals.ModalCloseButton {...props} />;
+export const ModalRoot = LazyComponent(() => Modals.ModalRoot);
+export const ModalHeader = LazyComponent(() => Modals.ModalHeader);
+export const ModalContent = LazyComponent(() => Modals.ModalContent);
+export const ModalFooter = LazyComponent(() => Modals.ModalFooter);
+export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton);
const ModalAPI = mapMangledModuleLazy("onCloseRequest:null!=", {
openModal: filters.byCode("onCloseRequest:null!="),
diff --git a/src/webpack/common.tsx b/src/webpack/common.tsx
deleted file mode 100644
index 562332a..0000000
--- a/src/webpack/common.tsx
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Vencord, a modification for Discord's desktop app
- * Copyright (c) 2022 Vendicated and contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
-*/
-
-import { LazyComponent } from "@utils/misc";
-import { proxyLazy } from "@utils/proxyLazy";
-import {
- _resolveReady,
- filters, findByCode, findByCodeLazy, findByPropsLazy, mapMangledModule, mapMangledModuleLazy, waitFor
-} from "@webpack";
-import type Components from "discord-types/components";
-import { User } from "discord-types/general";
-import type Other from "discord-types/other";
-import type Stores from "discord-types/stores";
-
-export const Margins = findByPropsLazy("marginTop20");
-
-export let FluxDispatcher: Other.FluxDispatcher;
-export const Flux = findByPropsLazy("connectStores");
-
-export let React: typeof import("react");
-export let useState: typeof React.useState;
-export let useEffect: typeof React.useEffect;
-export let useMemo: typeof React.useMemo;
-export let useRef: typeof React.useRef;
-
-export const ReactDOM: typeof import("react-dom") = findByPropsLazy("createPortal", "render");
-
-export const RestAPI = findByPropsLazy("getAPIBaseURL", "get");
-export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear");
-
-export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight");
-
-export const MessageStore = findByPropsLazy("getRawMessages") as Omit<Stores.MessageStore, "getMessages"> & {
- getMessages(chanId: string): any;
-};
-export const PermissionStore = findByPropsLazy("can", "getGuildPermissions");
-export const PrivateChannelsStore = findByPropsLazy("openPrivateChannel");
-export const GuildChannelStore = findByPropsLazy("getChannels");
-export const ReadStateStore = findByPropsLazy("lastMessageId");
-export const PresenceStore = findByPropsLazy("setCurrentUserOnConnectionOpen");
-export let GuildStore: Stores.GuildStore;
-export let UserStore: Stores.UserStore;
-export let SelectedChannelStore: Stores.SelectedChannelStore;
-export let SelectedGuildStore: any;
-export let ChannelStore: Stores.ChannelStore;
-export let GuildMemberStore: Stores.GuildMemberStore;
-export let RelationshipStore: Stores.RelationshipStore & {
- /** Get the date (as a string) that the relationship was created */
- getSince(userId: string): string;
-};
-
-export const Forms = {} as {
- FormTitle: Components.FormTitle;
- FormSection: any;
- FormDivider: any;
- FormText: Components.FormText;
-};
-export let Card: Components.Card;
-export let Button: any;
-export const ButtonLooks = findByPropsLazy("BLANK", "FILLED", "INVERTED") as Record<"FILLED" | "INVERTED" | "OUTLINED" | "LINK" | "BLANK", string>;
-export let Switch: any;
-export let Tooltip: Components.Tooltip;
-export let Timestamp: any;
-export let Router: any;
-export let TextInput: any;
-export let Text: (props: TextProps) => JSX.Element;
-export const TextArea = findByCodeLazy("handleSetRef", "textArea") as React.ComponentType<React.PropsWithRef<any>>;
-export const ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent") as Record<string, string>;
-
-export const Select = LazyComponent(() => findByCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems"));
-export const Slider = LazyComponent(() => findByCode("closestMarkerIndex", "stickToMarkers"));
-
-export let SnowflakeUtils: { fromTimestamp: (timestamp: number) => string, extractTimestamp: (snowflake: string) => number; };
-waitFor(["fromTimestamp", "extractTimestamp"], m => SnowflakeUtils = m);
-
-export let Parser: any;
-export let Alerts: {
- show(alert: {
- title: any;
- body: React.ReactNode;
- className?: string;
- confirmColor?: string;
- cancelText?: string;
- confirmText?: string;
- secondaryConfirmText?: string;
- onCancel?(): void;
- onConfirm?(): void;
- onConfirmSecondary?(): void;
- }): void;
- /** This is a noop, it does nothing. */
- close(): void;
-};
-const ToastType = {
- MESSAGE: 0,
- SUCCESS: 1,
- FAILURE: 2,
- CUSTOM: 3
-};
-const ToastPosition = {
- TOP: 0,
- BOTTOM: 1
-};
-
-export const Toasts = {
- Type: ToastType,
- Position: ToastPosition,
- // what's less likely than getting 0 from Math.random()? Getting it twice in a row
- genId: () => (Math.random() || Math.random()).toString(36).slice(2),
-
- // hack to merge with the following interface, dunno if there's a better way
- ...{} as {
- show(data: {
- message: string,
- id: string,
- /**
- * Toasts.Type
- */
- type: number,
- options?: {
- /**
- * Toasts.Position
- */
- position?: number;
- component?: React.ReactNode,
- duration?: number;
- };
- }): void;
- pop(): void;
- }
-};
-
-export const UserUtils = {
- fetchUser: findByCodeLazy(".USER(", "getUser") as (id: string) => Promise<User>,
-};
-
-export const Clipboard = mapMangledModuleLazy('document.queryCommandEnabled("copy")||document.queryCommandSupported("copy")', {
- copy: filters.byCode(".default.copy("),
- SUPPORTS_COPY: x => typeof x === "boolean",
-});
-
-export const NavigationRouter = mapMangledModuleLazy("Transitioning to external path", {
- transitionTo: filters.byCode("Transitioning to external path"),
- transitionToGuild: filters.byCode("transitionToGuild"),
- goBack: filters.byCode("goBack()"),
- goForward: filters.byCode("goForward()"),
-});
-
-waitFor("useState", m => {
- React = m;
- ({ useEffect, useState, useMemo, useRef } = React);
-});
-
-waitFor(["dispatch", "subscribe"], m => {
- FluxDispatcher = m;
- const cb = () => {
- m.unsubscribe("CONNECTION_OPEN", cb);
- _resolveReady();
- };
- m.subscribe("CONNECTION_OPEN", cb);
-});
-
-waitFor(["getCurrentUser", "initialize"], m => UserStore = m);
-waitFor("getSortedPrivateChannels", m => ChannelStore = m);
-waitFor("getCurrentlySelectedChannelId", m => SelectedChannelStore = m);
-waitFor("getLastSelectedGuildId", m => SelectedGuildStore = m);
-waitFor("getGuildCount", m => GuildStore = m);
-waitFor(["getMember", "initialize"], m => GuildMemberStore = m);
-waitFor("getRelationshipType", m => RelationshipStore = m);
-
-waitFor(["Hovers", "Looks", "Sizes"], m => Button = m);
-
-waitFor(filters.byCode("tooltipNote", "ringTarget"), m => Switch = m);
-
-waitFor(filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format"), m => Timestamp = m);
-
-waitFor(["Positions", "Colors"], m => Tooltip = m);
-waitFor(m => m.Types?.PRIMARY === "cardPrimary", m => Card = m);
-
-waitFor(filters.byCode("errorSeparator"), m => Forms.FormTitle = m);
-waitFor(filters.byCode("titleClassName", "sectionTitle"), m => Forms.FormSection = m);
-waitFor(m => m.Types?.INPUT_PLACEHOLDER, m => Forms.FormText = m);
-
-waitFor(m => {
- if (typeof m !== "function") return false;
- const s = m.toString();
- return s.length < 200 && s.includes(".divider");
-}, m => Forms.FormDivider = m);
-
-// This is the same module but this is easier
-waitFor(filters.byCode("currentToast?"), m => Toasts.show = m);
-waitFor(filters.byCode("currentToast:null"), m => Toasts.pop = m);
-
-waitFor(["show", "close"], m => Alerts = m);
-waitFor("parseTopic", m => Parser = m);
-
-waitFor(["open", "saveAccountChanges"], m => Router = m);
-waitFor(["defaultProps", "Sizes", "contextType"], m => TextInput = m);
-
-waitFor(m => {
- if (typeof m !== "function") return false;
- const s = m.toString();
- return (s.length < 1500 && s.includes("data-text-variant") && s.includes("always-white"));
-}, m => Text = m);
-
-export type TextProps = React.PropsWithChildren & {
- variant: TextVariant;
- style?: React.CSSProperties;
- color?: string;
- tag?: "div" | "span" | "p" | "strong" | `h${1 | 2 | 3 | 4 | 5 | 6}`;
- selectable?: boolean;
- lineClamp?: number;
- id?: string;
- className?: string;
-};
-
-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";
-
-type RC<C> = React.ComponentType<React.PropsWithChildren<C & Record<string, any>>>;
-interface Menu {
- ContextMenu: RC<{
- navId: string;
- onClose(): void;
- className?: string;
- style?: React.CSSProperties;
- hideScroller?: boolean;
- onSelect?(): void;
- }>;
- MenuSeparator: React.ComponentType;
- MenuGroup: RC<any>;
- MenuItem: RC<{
- id: string;
- label: string;
- render?: React.ComponentType;
- onChildrenScroll?: Function;
- childRowHeight?: number;
- listClassName?: string;
- }>;
- MenuCheckboxItem: RC<{
- id: string;
- }>;
- MenuRadioItem: RC<{
- id: string;
- }>;
- MenuControlItem: RC<{
- id: string;
- interactive?: boolean;
- }>;
-}
-
-/**
- * Discord's Context menu items.
- * To use anything but Menu.ContextMenu, your plugin HAS TO
- * depend on MenuItemDeobfuscatorAPI. Otherwise they will throw
- */
-export const Menu = proxyLazy(() => {
- const hasDeobfuscator = Vencord.Settings.plugins.MenuItemDeobfuscatorAPI.enabled;
- const menuItems = ["MenuSeparator", "MenuGroup", "MenuItem", "MenuCheckboxItem", "MenuRadioItem", "MenuControlItem"];
-
- const map = mapMangledModule("♫ ⊂(。◕‿‿◕。⊂) ♪", {
- ContextMenu: filters.byCode("getContainerProps"),
- ...Object.fromEntries((hasDeobfuscator ? menuItems : []).map(s => [s, (m: any) => m.name === s]))
- }) as Menu;
-
- if (!hasDeobfuscator) {
- for (const m of menuItems)
- Object.defineProperty(map, m, {
- get() {
- throw new Error("MenuItemDeobfuscator must be enabled to use this.");
- }
- });
- }
-
- return map;
-});
-
-export const ContextMenu = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN"', {
- open: filters.byCode("stopPropagation"),
- openLazy: m => m.toString().length < 50,
- close: filters.byCode("CONTEXT_MENU_CLOSE")
-}) as {
- close(): void;
- open(
- event: React.UIEvent,
- render?: Menu["ContextMenu"],
- options?: { enableSpellCheck?: boolean; },
- renderLazy?: () => Promise<Menu["ContextMenu"]>
- ): void;
- openLazy(
- event: React.UIEvent,
- renderLazy?: () => Promise<Menu["ContextMenu"]>,
- options?: { enableSpellCheck?: boolean; }
- ): void;
-};
-
-export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', {
- openUntrustedLink: filters.byCode(".apply(this,arguments)")
-});
diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts
new file mode 100644
index 0000000..be585c3
--- /dev/null
+++ b/src/webpack/common/components.ts
@@ -0,0 +1,53 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2023 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+// eslint-disable-next-line path-alias/no-relative
+import { filters, findByPropsLazy } from "../webpack";
+import { waitForComponent } from "./internal";
+import * as t from "./types/components";
+
+export const Forms = {
+ FormTitle: waitForComponent<t.FormTitle>("FormTitle", filters.byCode("errorSeparator")),
+ FormSection: waitForComponent<t.FormSection>("FormSection", filters.byCode("titleClassName", "sectionTitle")),
+ FormDivider: waitForComponent<t.FormDivider>("FormDivider", m => {
+ if (typeof m !== "function") return false;
+ const s = m.toString();
+ return s.length < 200 && s.includes(".divider");
+ }),
+ FormText: waitForComponent<t.FormText>("FormText", m => m.Types?.INPUT_PLACEHOLDER),
+};
+
+export const Card = waitForComponent<t.Card>("Card", m => m.Types?.PRIMARY === "cardPrimary");
+export const Button = waitForComponent<t.Button>("Button", ["Hovers", "Looks", "Sizes"]);
+export const Switch = waitForComponent<t.Switch>("Switch", filters.byCode("tooltipNote", "ringTarget"));
+export const Tooltip = waitForComponent<t.Tooltip>("Tooltip", ["Positions", "Colors"]);
+export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format"));
+export const TextInput = waitForComponent<t.TextInput>("TextInput", ["defaultProps", "Sizes", "contextType"]);
+export const TextArea = waitForComponent<t.TextArea>("TextArea", filters.byCode("handleSetRef", "textArea"));
+export const Text = waitForComponent<t.Text>("Text", m => {
+ if (typeof m !== "function") return false;
+ const s = m.toString();
+ return (s.length < 1500 && s.includes("data-text-variant") && s.includes("always-white"));
+});
+export const Select = waitForComponent<t.Select>("Select", filters.byCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems"));
+export const Slider = waitForComponent<t.Slider>("Slider", filters.byCode("closestMarkerIndex", "stickToMarkers"));
+export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);
+
+export const ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent") as Record<string, string>;
+export const Margins: t.Margins = findByPropsLazy("marginTop20");
+export const ButtonLooks: t.ButtonLooks = findByPropsLazy("BLANK", "FILLED", "INVERTED");
diff --git a/src/webpack/common/index.ts b/src/webpack/common/index.ts
new file mode 100644
index 0000000..dff7826
--- /dev/null
+++ b/src/webpack/common/index.ts
@@ -0,0 +1,27 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2023 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FIT