From 702a71dee8ce29d78ffbdb14edbdf2c0947c9b25 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 23 Jan 2026 17:51:30 +0100 Subject: [PATCH] fix plugin settings UI --- .../settings/tabs/plugins/PluginModal.tsx | 4 +- src/utils/modal.tsx | 6 +-- src/utils/text.ts | 5 +++ src/webpack/common/components.ts | 2 +- src/webpack/webpack.ts | 43 ++++++++++++++++++- 5 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/components/settings/tabs/plugins/PluginModal.tsx b/src/components/settings/tabs/plugins/PluginModal.tsx index 46c9132c..fd76c200 100644 --- a/src/components/settings/tabs/plugins/PluginModal.tsx +++ b/src/components/settings/tabs/plugins/PluginModal.tsx @@ -31,7 +31,7 @@ import { classes, isObjectEmpty } from "@utils/misc"; import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { OptionType, Plugin } from "@utils/types"; import { User } from "@vencord/discord-types"; -import { findByPropsLazy } from "@webpack"; +import { findCssClassesLazy } from "@webpack"; import { Clickable, FluxDispatcher, Forms, React, Text, Tooltip, useEffect, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common"; import { Constructor } from "type-fest"; @@ -43,7 +43,7 @@ import { GithubButton, WebsiteButton } from "./LinkIconButton"; const cl = classNameFactory("vc-plugin-modal-"); -const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); +const AvatarStyles = findCssClassesLazy("moreUsers", "avatar", "clickableAvatar"); const UserRecord: Constructor> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any; interface PluginModalProps extends ModalProps { diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx index 17bf3987..7396d151 100644 --- a/src/utils/modal.tsx +++ b/src/utils/modal.tsx @@ -104,9 +104,9 @@ interface Modals { export const Modals: Modals = mapMangledModuleLazy(':"thin")', { ModalRoot: filters.componentByCode('.MODAL,"aria-labelledby":'), ModalHeader: filters.componentByCode(",id:"), - ModalContent: filters.componentByCode(".content,"), - ModalFooter: filters.componentByCode(".footer,"), - ModalCloseButton: filters.componentByCode(".close]:") + ModalContent: filters.componentByCode("scrollbarType:"), + ModalFooter: filters.componentByCode(".HORIZONTAL_REVERSE,"), + ModalCloseButton: filters.componentByCode(".withCircleBackground") }); export const ModalRoot = LazyComponent(() => Modals.ModalRoot); diff --git a/src/utils/text.ts b/src/utils/text.ts index 220b3129..6dc5b008 100644 --- a/src/utils/text.ts +++ b/src/utils/text.ts @@ -146,3 +146,8 @@ export const ZWSP = "\u200b"; export function toInlineCode(s: string) { return "``" + ZWSP + s.replaceAll("`", ZWSP + "`" + ZWSP) + ZWSP + "``"; } + +// @ts-expect-error Missing RegExp.escape +export const escapeRegExp: (s: string) => string = RegExp.escape ?? function (s: string) { + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +}; diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index 867da299..e388a8a1 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -60,7 +60,7 @@ export const TooltipContainer = TooltipContainerComponent as never; export const TextInput = waitForComponent("TextInput", filters.componentByCode("#{intl::MAXIMUM_LENGTH_ERROR}", '"input"')); export const TextArea = waitForComponent("TextArea", filters.componentByCode("this.getPaddingRight()},id:")); -export const Select = waitForComponent("Select", filters.componentByCode('"Select"', ".newOptionLabel")); +export const Select = waitForComponent("Select", filters.componentByCode('"Select"')); export const SearchableSelect = waitForComponent("SearchableSelect", filters.componentByCode('"SearchableSelect"')); export const Slider = waitForComponent("Slider", filters.componentByCode('"markDash".concat(')); export const Popout = waitForComponent("Popout", filters.componentByCode("ref:this.ref,", "renderPopout:this.renderPopout,")); diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index a76adcb2..1dcb43a9 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -20,6 +20,7 @@ import { makeLazy, proxyLazy } from "@utils/lazy"; import { LazyComponent } from "@utils/lazyReact"; import { Logger } from "@utils/Logger"; import { canonicalizeMatch } from "@utils/patches"; +import { escapeRegExp } from "@utils/text"; import type { FluxStore } from "@vencord/discord-types"; import type { ModuleExports, ModuleFactory, WebpackRequire } from "@vencord/discord-types/webpack"; @@ -53,6 +54,10 @@ export const stringMatches = (s: string, filter: CodeFilter) => : (f.global && (f.lastIndex = 0), f.test(s)) ); +export function makeClassNameRegex(className: string) { + return new RegExp(`(?:\\b|_)${escapeRegExp(className)}(?:\\b|_)`); +} + export const filters = { byProps: (...props: PropsFilter): FilterFn => props.length === 1 @@ -90,6 +95,17 @@ export const filters = { filter.$$vencordProps = [...code]; return filter; + }, + + byClassNames: (...classes: string[]): FilterFn => { + const regexes = classes.map(makeClassNameRegex); + + return (m: any) => { + if (typeof m !== "object") return false; + + const values = Object.values(m); + return regexes.every(cls => values.some(v => typeof v === "string" && cls.test(v))); + }; } }; @@ -209,7 +225,7 @@ export function handleModuleNotFound(method: string, ...filter: unknown[]) { /** * Find the first module that matches the filter */ -export const find = traceFunction("find", function find(filter: FilterFn, { isIndirect = false, isWaitFor = false }: { isIndirect?: boolean; isWaitFor?: boolean; } = {}) { +export const find = traceFunction("find", function find(filter: FilterFn, { isIndirect = false, isWaitFor = false, topLevelOnly = false }: { isIndirect?: boolean; isWaitFor?: boolean; topLevelOnly?: boolean; } = {}) { if (IS_ANTI_CRASH_TEST) return null; if (typeof filter !== "function") @@ -223,7 +239,7 @@ export const find = traceFunction("find", function find(filter: FilterFn, { isIn return isWaitFor ? [mod.exports, key] : mod.exports; } - if (typeof mod.exports !== "object") continue; + if (typeof mod.exports !== "object" || topLevelOnly) continue; for (const nestedMod in mod.exports) { const nested = mod.exports[nestedMod]; @@ -562,6 +578,29 @@ export function findExportedComponentLazy(...props: Prop }); } +export function findCssClasses(...classes: S[]): Record { + const res = find(filters.byClassNames(...classes), { isIndirect: true, topLevelOnly: true }); + + if (!res) + handleModuleNotFound("findCssClasses", ...classes); + + const values = Object.values(res); + const mapped = {} as Record; + + for (const cls of classes) { + const re = makeClassNameRegex(cls); + mapped[cls] = values.find(v => typeof v === "string" && re.test(v)) as string; + } + + return mapped; +} + +export function findCssClassesLazy(...classes: S[]) { + if (IS_REPORTER) lazyWebpackSearchHistory.push(["findCssClasses", classes]); + + return proxyLazy(() => findCssClasses(...classes)); +} + function getAllPropertyNames(object: Record, includeNonEnumerable: boolean) { const names = new Set();