diff options
Diffstat (limited to 'src/webpack')
| -rw-r--r-- | src/webpack/common.tsx | 312 | ||||
| -rw-r--r-- | src/webpack/common/components.ts | 53 | ||||
| -rw-r--r-- | src/webpack/common/index.ts | 27 | ||||
| -rw-r--r-- | src/webpack/common/internal.tsx | 36 | ||||
| -rw-r--r-- | src/webpack/common/menu.ts | 51 | ||||
| -rw-r--r-- | src/webpack/common/react.ts | 33 | ||||
| -rw-r--r-- | src/webpack/common/stores.ts | 54 | ||||
| -rw-r--r-- | src/webpack/common/types/components.d.ts | 284 | ||||
| -rw-r--r-- | src/webpack/common/types/fluxEvents.d.ts | 40 | ||||
| -rw-r--r-- | src/webpack/common/types/menu.d.ts | 68 | ||||
| -rw-r--r-- | src/webpack/common/types/utils.d.ts | 98 | ||||
| -rw-r--r-- | src/webpack/common/utils.ts | 112 |
12 files changed, 856 insertions, 312 deletions
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 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/>. +*/ + +export * from "./components"; +export * from "./menu"; +export * from "./react"; +export * from "./stores"; +export * as ComponentTypes from "./types/components.d"; +export * as MenuTypes from "./types/menu.d"; +export * as UtilTypes from "./types/utils.d"; +export * from "./utils"; + diff --git a/src/webpack/common/internal.tsx b/src/webpack/common/internal.tsx new file mode 100644 index 0000000..df768f7 --- /dev/null +++ b/src/webpack/common/internal.tsx @@ -0,0 +1,36 @@ +/* + * 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/>. +*/ + +import { LazyComponent } from "@utils/misc"; + +// eslint-disable-next-line path-alias/no-relative +import { FilterFn, waitFor } from "../webpack"; + +export function waitForComponent<T extends React.ComponentType<any> = React.ComponentType<any> & Record<string, any>>(name: string, filter: FilterFn | string | string[]): T { + let myValue: T = function () { + throw new Error(`Vencord could not find the ${name} Component`); + } as any; + + const lazyComponent = LazyComponent(() => myValue) as T; + waitFor(filter, (v: any) => { + myValue = v; + Object.assign(lazyComponent, v); + }); + + return lazyComponent; +} diff --git a/src/webpack/common/menu.ts b/src/webpack/common/menu.ts new file mode 100644 index 0000000..6ecd754 --- /dev/null +++ b/src/webpack/common/menu.ts @@ -0,0 +1,51 @@ +/* + * 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/>. +*/ + +import { proxyLazy } from "@utils/proxyLazy"; + +// eslint-disable-next-line path-alias/no-relative +import { filters, mapMangledModule, mapMangledModuleLazy } from "../webpack"; +import type * as t from "./types/menu"; + +export const Menu: t.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 t.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: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN"', { + open: filters.byCode("stopPropagation"), + openLazy: m => m.toString().length < 50, + close: filters.byCode("CONTEXT_MENU_CLOSE") +}); + diff --git a/src/webpack/common/react.ts b/src/webpack/common/react.ts new file mode 100644 index 0000000..455f39b --- /dev/null +++ b/src/webpack/common/react.ts @@ -0,0 +1,33 @@ +/* + * 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 { findByPropsLazy, waitFor } from "../webpack"; + +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"); + +waitFor("useState", m => { + React = m; + ({ useEffect, useState, useMemo, useRef } = React); +}); diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts new file mode 100644 index 0000000..bcd26b1 --- /dev/null +++ b/src/webpack/common/stores.ts @@ -0,0 +1,54 @@ +/* + * 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/>. +*/ + +import type * as Stores from "discord-types/stores"; + +// eslint-disable-next-line path-alias/no-relative +import { filters, findByPropsLazy, mapMangledModuleLazy, waitFor } from "../webpack"; + +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 MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', { + openUntrustedLink: filters.byCode(".apply(this,arguments)") +}); + +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); diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts new file mode 100644 index 0000000..3f76c22 --- /dev/null +++ b/src/webpack/common/types/components.d.ts @@ -0,0 +1,284 @@ +/* + * 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/>. +*/ + +import type { Moment } from "moment"; +import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react"; + +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 Margins = Record<"marginTop16" | "marginTop8" | "marginBottom8" | "marginTop20" | "marginBottom20", string>; +export type ButtonLooks = Record<"FILLED" | "INVERTED" | "OUTLINED" | "LINK" | "BLANK", string>; + +export type TextProps = PropsWithChildren<HtmlHTMLAttributes<HTMLDivElement> & { + variant?: TextVariant; + tag?: "div" | "span" | "p" | "strong" | Heading; + selectable?: boolean; + lineClamp?: number; +}>; + +export type Text = ComponentType<TextProps>; + +export type FormTitle = ComponentType<HTMLProps<HTMLTitleElement> & PropsWithChildren<{ + /** default is h5 */ + tag?: Heading; + faded?: boolean; + disabled?: boolean; + required?: boolean; + error?: ReactNode; +}>>; + +export type FormSection = ComponentType<PropsWithChildren<{ + /** default is h5 */ + tag?: Heading; + className?: string; + titleClassName?: string; + titleId?: string; + title?: ReactNode; + disabled?: boolean; + htmlFor?: unknown; +}>>; + +export type FormDivider = ComponentType<{ + className?: string; + style?: CSSProperties; +}>; + + +export type FormText = ComponentType<PropsWithChildren<{ + disabled?: boolean; + selectable?: boolean; + /** defaults to FormText.Types.DEFAULT */ + type?: string; +}> & TextProps> & { Types: FormTextTypes; }; + +export type Tooltip = ComponentType<{ + text: ReactNode; + children: FunctionComponent<{ + onClick(): void; + onMouseEnter(): void; + onMouseLeave(): void; + onContextMenu(): void; + onFocus(): void; + onBlur(): void; + "aria-label"?: string; + }>; + "aria-label"?: string; + + allowOverflow?: boolean; + forceOpen?: boolean; + hide?: boolean; + hideOnClick?: boolean; + shouldShow?: boolean; + spacing?: number; + + /** Tooltip.Colors.BLACK */ + color?: string; + /** Tooltip.Positions.TOP */ + position?: string; + + tooltipClassName?: string; + tooltipContentClassName?: string; +}> & { + Positions: Record<"BOTTOM" | "CENTER" | "LEFT" | "RIGHT" | "TOP" | "WINDOW_CENTER", string>; + Colors: Record<"BLACK" | "BRAND" | "CUSTOM" | "GREEN" | "GREY" | "PRIMARY" | "RED" | "YELLOW", string>; +}; + +export type Card = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement> & { + editable?: boolean; + outline?: boolean; + /** Card.Types.PRIMARY */ + type?: string; +}>> & { + Types: Record<"BRAND" | "CUSTOM" | "DANGER" | "PRIMARY" | "SUCCESS" | "WARNING", string>; +}; + +export type Button = ComponentType<PropsWithChildren<Omit<HTMLProps<HTMLButtonElement>, "size"> & { + /** Button.Looks.FILLED */ + look?: string; + /** Button.Colors.BRAND */ + color?: string; + /** Button.Sizes.MEDIUM */ + size?: string; + /** Button.BorderColors.BLACK */ + borderColor?: string; + + wrapperClassName?: string; + className?: string; + innerClassName?: string; + + buttonRef?: Ref<HTMLButtonElement>; + focusProps?: any; + + submittingStartedLabel?: string; + submittingFinishedLabel?: string; +}>> & { + BorderColors: Record<"BLACK" | "BRAND" | "BRAND_NEW" | "GREEN" | "LINK" | "PRIMARY" | "RED" | "TRANSPARENT" | "WHITE" | "YELLOW", string>; + Colors: Record<"BRAND" | "RED" | "GREEN" | "YELLOW" | "PRIMARY" | "LINK" | "WHITE" | "BLACK" | "TRANSPARENT" | "BRAND_NEW" | "CUSTOM", string>; + Hovers: Record<"DEFAULT" | "BRAND" | "RED" | "GREEN" | "YELLOW" | "PRIMARY" | "LINK" | "WHITE" | "BLACK" | "TRANSPARENT", string>; + Looks: Record<"FILLED" | "INVERTED" | "OUTLINED" | "LINK" | "BLANK", string>; + Sizes: Record<"NONE" | "TINY" | "SMALL" | "MEDIUM" | "LARGE" | "XLARGE" | "MIN" | "MAX" | "ICON", string>; + + Link: any; +}; + +export type Switch = ComponentType<PropsWithChildren<{ + value: boolean; + onChange(value: boolean): void; + + disabled?: boolean; + hideBorder?: boolean; + className?: string; + style?: CSSProperties; + + note?: ReactNode; + tooltipNote?: ReactNode; +}>>; + +export type Timestamp = ComponentType<PropsWithChildren<{ + timestamp: Moment; + isEdited?: boolean; + + className?: string; + id?: string; + + cozyAlt?: boolean; + compact?: boolean; + isInline?: boolean; + isVisibleOnlyOnHover?: boolean; +}>>; + +export type TextInput = ComponentType<PropsWithChildren<{ + name?: string; + onChange?(value: string, name?: string): void; + placeholder?: string; + editable?: boolean; + maxLength?: number; + error?: string; + + inputClassName?: string; + inputPrefix?: string; + inputRef?: Ref<HTMLInputElement>; + prefixElement?: ReactNode; + + focusProps?: any; + + /** TextInput.Sizes.DEFAULT */ + size?: string; +} & Omit<HTMLProps<HTMLInputElement>, "onChange">>> & { + Sizes: Record<"DEFAULT" | "MINI", string>; +}; + +export type TextArea = ComponentType<PropsWithRef<HTMLProps<HTMLTextAreaElement>>>; + +interface SelectOption { + disabled?: boolean; + value: any; + label: string; + key?: React.Key; + default?: boolean; +} + +export type Select = ComponentType<PropsWithChildren<{ + placeholder?: string; + options: ReadonlyArray<SelectOption>; // TODO + + /** + * - 0 ~ Filled + * - 1 ~ Custom + */ + look?: 0 | 1; + className?: string; + popoutClassName?: string; + popoutPosition?: "top" | "left" | "right" | "bottom" | "center" | "window_center"; + optionClassName?: string; + + autoFocus?: boolean; + isDisabled?: boolean; + clearable?: boolean; + closeOnSelect?: boolean; + hideIcon?: boolean; + + select?(value: any): void; + isSelected?(value: any): boolean; + serialize?(value: any): string; + clear?(): void; + + maxVisibleItems?: number; + popoutWidth?: number; + + onClose?(): void; + onOpen?(): void; + + renderOptionLabel?(option: SelectOption): ReactNode; + /** discord stupid this gets all options instead of one yeah */ + renderOptionValue?(option: SelectOption[]): ReactNode; + + "aria-label"?: boolean; + "aria-labelledby"?: boolean; +}>>; + +export type Slider = ComponentType<PropsWithChildren<{ + initialValue: number; + defaultValue?: number; + keyboardStep?: number; + maxValue?: number; + minValue?: number; + markers?: number[]; + stickToMarkers?: boolean; + + /** 0 above, 1 below */ + markerPosition?: 0 | 1; + orientation?: "horizontal" | "vertical"; + + getAriaValueText?(currentValue: number): string; + renderMarker?(marker: number): ReactNode; + onMarkerRender?(marker: number): ReactNode; + onValueRender?(value: number): ReactNode; + onValueChange?(value: number): void; + asValueChanges?(value: number): void; + + className?: string; + disab |
