From 55cdf94320ec73d997057f20efa278c3cfccaca1 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 7 May 2026 11:59:12 -0400 Subject: [PATCH 01/74] unbrick ext workflow --- .github/workflows/build.yml | 2 +- .github/workflows/publish.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index df48d7a6f..d55249f75 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -73,7 +73,7 @@ jobs: gh release upload latest --clobber dist/release/* - name: Upload Plugin JSONs to Equibored repo - if: github.repository == 'Equicord/Equicord' && !vars.ACT == 'false' + if: github.repository == 'Equicord/Equicord' && vars.ACT == 'false' run: | git config --global user.name "GitHub-Actions" git config --global user.email actions@github.com diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bd48b13b4..d066e4882 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -83,7 +83,7 @@ jobs: ".git/*" - name: Publish extension - if: steps.check.outputs.release == 'true' && !vars.ACT == 'false' + if: steps.check.outputs.release == 'true' && vars.ACT == 'false' run: pnpx @equicord/publish-browser-extension@latest --chrome-zip dist/extension-chrome.zip --firefox-zip dist/extension-firefox.zip --firefox-sources-zip From 9c7e255c1e383b5a7f45ccda260dba942a0b4b59 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 7 May 2026 12:48:12 -0400 Subject: [PATCH 02/74] fix macos download --- README.md | 3 ++- scripts/runInstaller.mjs | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9d4992b1a..ddb34ecf9 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ Windows MacOS -- [GUI](https://github.com/Equicord/Equilotl/releases/latest/download/Equilotl.MacOS.zip) +- [X64 GUI](https://github.com/Equicord/Equilotl/releases/latest/download/Equilotl-darwin-x64.zip) +- [ARM64 GUI](https://github.com/Equicord/Equilotl/releases/latest/download/Equilotl-darwin-arm64.zip) Linux diff --git a/scripts/runInstaller.mjs b/scripts/runInstaller.mjs index 94c03dd4a..0c124b170 100644 --- a/scripts/runInstaller.mjs +++ b/scripts/runInstaller.mjs @@ -38,7 +38,14 @@ function getFilename() { case "win32": return "EquilotlCli.exe"; case "darwin": - return "Equilotl.MacOS.zip"; + switch (process.arch) { + case "x64": + return "Equilotl-darwin-x64.zip"; + case "arm64": + return "Equilotl-darwin-arm64.zip"; + default: + throw new Error("Unsupported macOS architecture: " + process.arch); + } case "linux": return "EquilotlCli-linux"; default: From 43c3682e3e8775fd7dacc9a8356037691b009266 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 7 May 2026 19:15:28 -0400 Subject: [PATCH 03/74] SongLink: Fix Dupes --- .../songLink.desktop/SongLinker.tsx | 21 +++++-- .../songLink.desktop/index.tsx | 55 +++++++++++++++---- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/equicordplugins/songLink.desktop/SongLinker.tsx b/src/equicordplugins/songLink.desktop/SongLinker.tsx index 158baad3d..decc26843 100644 --- a/src/equicordplugins/songLink.desktop/SongLinker.tsx +++ b/src/equicordplugins/songLink.desktop/SongLinker.tsx @@ -12,19 +12,28 @@ import { Button, useEffect, useState } from "@webpack/common"; import pl, { Native, settings, SongLinkResult } from "."; import { Providers } from "./Providers"; -export default function SongLinker({ url }: { url: string; }) { +interface SongLinkerProps { + url: string; + onResolved?: (url: string, result: SongLinkResult) => void; +} + +export default function SongLinker({ url, onResolved }: SongLinkerProps) { const [songData, setSongData] = useState(); useEffect(() => { async function doStuff() { - if (pl.cache[url]) return void setSongData(pl.cache[url]); - - const sd = await Native.getTrackData(url); + let sd: SongLinkResult; + if (pl.cache[url]) { + sd = pl.cache[url]; + } else { + sd = await Native.getTrackData(url); + pl.addToCache(url, sd); + } setSongData(sd); - pl.addToCache(url, sd); + onResolved?.(url, sd); } doStuff(); - }); + }, [url]); return { diff --git a/src/equicordplugins/songLink.desktop/index.tsx b/src/equicordplugins/songLink.desktop/index.tsx index f558083a0..801ab12ff 100644 --- a/src/equicordplugins/songLink.desktop/index.tsx +++ b/src/equicordplugins/songLink.desktop/index.tsx @@ -9,6 +9,7 @@ import { definePluginSettings } from "@api/Settings"; import { Devs, EquicordDevs } from "@utils/constants"; import { sendMessage } from "@utils/discord"; import definePlugin, { OptionType, PluginNative } from "@utils/types"; +import { useEffect, useState } from "@webpack/common"; import { Providers } from "./Providers"; import { Settings } from "./Settings"; @@ -83,6 +84,43 @@ function formatMessage(data: SongLinkResult): string | null { return parts.join("\n"); } +function SongLinkerList({ urls }: { urls: string[]; }) { + const [resolvedKeys, setResolvedKeys] = useState>( + () => Object.fromEntries(urls.map(u => [u, null])) + ); + + useEffect(() => { + setResolvedKeys(Object.fromEntries(urls.map(u => [u, null]))); + }, [urls.join("\n")]); + + function onResolved(url: string, result: SongLinkResult) { + const key = result.info + ? `${result.info.title}\0${result.info.artist}` + : url; + setResolvedKeys(prev => prev[url] === key ? prev : { ...prev, [url]: key }); + } + + const seenKeys = new Set(); + const dedupedUrls = urls.filter(url => { + const key = resolvedKeys[url]; + if (key === null) return true; + if (seenKeys.has(key)) return false; + seenKeys.add(key); + return true; + }); + + return
+ {dedupedUrls.map(url => ( + + ))} +
; +} + export default definePlugin({ name: "SongLink", description: "Adds streaming service buttons below song links", @@ -107,19 +145,12 @@ export default definePlugin({ /https:\/\/music\.youtube\.com\/watch\?v=[0-9A-Za-z_-]+/, // yt music /https:\/\/tidal\.com\/track\/[0-9]+\/u/ // tidal ]; - const musicLinks = content.match(new RegExp(regexes.map(r => r.source).join("|"), "g")); - if (!musicLinks?.length) return; + const allMatches = content.match(new RegExp(regexes.map(r => r.source).join("|"), "g")); + if (!allMatches?.length) return; - return
- { - musicLinks.map(item => ) - } -
; + const musicLinks = [...new Set(allMatches)]; + + return ; }, commands: [ { From 3109ff102ee8deb9f796caadacf5ebf25f195e0d Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 8 May 2026 16:00:46 +0200 Subject: [PATCH 04/74] Fix Vencord context menu options missing Also merges MenuItemDemanglerAPI into ContextMenuAPI because these patch the same module anyway and depend on each other --- src/plugins/_api/contextMenu.ts | 75 +++++++++++++++++++++++---- src/plugins/_api/menuItemDemangler.ts | 71 ------------------------- 2 files changed, 66 insertions(+), 80 deletions(-) delete mode 100644 src/plugins/_api/menuItemDemangler.ts diff --git a/src/plugins/_api/contextMenu.ts b/src/plugins/_api/contextMenu.ts index debc55273..8fe49d650 100644 --- a/src/plugins/_api/contextMenu.ts +++ b/src/plugins/_api/contextMenu.ts @@ -17,7 +17,23 @@ */ import { Devs } from "@utils/constants"; +import { canonicalizeMatch } from "@utils/patches"; import definePlugin from "@utils/types"; +import { Menu } from "@webpack/common"; + +// duplicate values have multiple branches with different types. Just include all to be safe +const nameMap = { + radio: "MenuRadioItem", + separator: "MenuSeparator", + checkbox: "MenuCheckboxItem", + groupstart: "MenuGroup", + + control: "MenuControlItem", + compositecontrol: "MenuControlItem", + + item: "MenuItem", + customitem: "MenuItem", +}; export default definePlugin({ name: "ContextMenuAPI", @@ -26,13 +42,6 @@ export default definePlugin({ required: true, patches: [ - { - find: "♫ (つ。◕‿‿◕。)つ ♪", - replacement: { - match: /(?=let{navId:)(?<=function \i\((\i)\).+?)/, - replace: "$1=Vencord.Api.ContextMenu._usePatchContextMenu($1);" - } - }, { find: "navId:", all: true, @@ -50,6 +59,54 @@ export default definePlugin({ } } ] - } - ] + }, + + { + find: "Menu API only allows Items", + replacement: [ + // Patch the central context menu handler + { + match: /(?=let{navId:)(?<=function \i\((\i)\).+?)/, + replace: "$1=Vencord.Api.ContextMenu._usePatchContextMenu($1);" + }, + + // Demangle Discord's Menu Item module + { + match: /(?<=(\(\i\.type===(\i\.\i)\).{0,50}?navigable:.+Menu API).+?)}$/s, + replace: (_, m) => { + const registerCalls = [] as string[]; + + const typeCheckRe = canonicalizeMatch(/\(\i\.type===(\i\.\i)\)/g); // if (t.type === m.MenuItem) + const pushTypeRe = /type:"(\w+)"/g; // push({type:"item"}) + + let typeMatch: RegExpExecArray | null; + while ((typeMatch = typeCheckRe.exec(m)) !== null) { + const component = typeMatch[1]; + // Set the starting index of the second regex to that of the first to start + // matching from after the if + pushTypeRe.lastIndex = typeCheckRe.lastIndex; + + // extract the first type: "..." + const type = pushTypeRe.exec(m)?.[1]; + if (type && type in nameMap) { + const name = nameMap[type]; + registerCalls.push(`$self.registerMenuItem("${name}",${component})`); + } + } + + if (registerCalls.length < 6) { + console.warn("[MenuItemDemanglerAPI] Expected to remap 6 items, only remapped", registerCalls.length); + } + + return `${registerCalls.join(";")};}`; + }, + } + ], + }, + ], + + registerMenuItem(name: string, component: any) { + Object.defineProperty(component, "name", { value: name }); + Menu[name] = component; + } }); diff --git a/src/plugins/_api/menuItemDemangler.ts b/src/plugins/_api/menuItemDemangler.ts deleted file mode 100644 index 72b8fee6d..000000000 --- a/src/plugins/_api/menuItemDemangler.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2025 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { Devs } from "@utils/constants"; -import { canonicalizeMatch } from "@utils/patches"; -import definePlugin from "@utils/types"; -import { Menu } from "@webpack/common"; - -// duplicate values have multiple branches with different types. Just include all to be safe -const nameMap = { - radio: "MenuRadioItem", - separator: "MenuSeparator", - checkbox: "MenuCheckboxItem", - groupstart: "MenuGroup", - - control: "MenuControlItem", - compositecontrol: "MenuControlItem", - - item: "MenuItem", - customitem: "MenuItem", -}; - -export default definePlugin({ - name: "MenuItemDemanglerAPI", - description: "Demangles Discord's Menu Item module", - authors: [Devs.Ven], - required: true, - patches: [ - { - find: "Menu API only allows Items", - replacement: { - match: /(?<=(\(\i\.type===(\i\.\i)\).{0,50}?navigable:.+Menu API).+?)}$/s, - replace: (_, m) => { - const registerCalls = [] as string[]; - - const typeCheckRe = canonicalizeMatch(/\(\i\.type===(\i\.\i)\)/g); // if (t.type === m.MenuItem) - const pushTypeRe = /type:"(\w+)"/g; // push({type:"item"}) - - let typeMatch: RegExpExecArray | null; - while ((typeMatch = typeCheckRe.exec(m)) !== null) { - const component = typeMatch[1]; - // Set the starting index of the second regex to that of the first to start - // matching from after the if - pushTypeRe.lastIndex = typeCheckRe.lastIndex; - - // extract the first type: "..." - const type = pushTypeRe.exec(m)?.[1]; - if (type && type in nameMap) { - const name = nameMap[type]; - registerCalls.push(`$self.registerMenuItem("${name}",${component})`); - } - } - - if (registerCalls.length < 6) { - console.warn("[MenuItemDemanglerAPI] Expected to remap 6 items, only remapped", registerCalls.length); - } - - return `${registerCalls.join(";")};}`; - }, - }, - }, - ], - - registerMenuItem(name: string, component: any) { - Object.defineProperty(component, "name", { value: name }); - Menu[name] = component; - } -}); From 7a75cd64d0909af3069460ec55dd8f5b8066a515 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 8 May 2026 16:07:31 +0200 Subject: [PATCH 05/74] fix RoleColorEverywhere --- src/plugins/roleColorEverywhere/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 2b4b3e0c7..d50596398 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -107,8 +107,8 @@ export default definePlugin({ find: 'tutorialId:"whos-online', replacement: [ { - match: /(#{intl::CHANNEL_MEMBERS_A11Y_LABEL}.+}\):null,).{0,100}?— ",\i\]\}\)\]/, - replace: (_, rest) => `${rest}$self.RoleGroupColor(arguments[0])]` + match: /(#{intl::CHANNEL_MEMBERS_A11Y_LABEL}.+}\):null,).{0,100}?(?:—|\\u2014) ",\i\]\}\)\]/, + replace: "$1$self.RoleGroupColor(arguments[0])]" }, ], predicate: () => settings.store.memberList @@ -117,7 +117,7 @@ export default definePlugin({ find: "#{intl::THREAD_BROWSER_PRIVATE}", replacement: [ { - match: /children:\[\i," — ",\i\]/, + match: /children:\[\i," (?:—|\\u2014) ",\i\]/, replace: "children:[$self.RoleGroupColor(arguments[0])]" }, ], From 697715e9c85175f9cbdadcebbf15b4063f2b1a3e Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Fri, 8 May 2026 21:52:28 +0700 Subject: [PATCH 06/74] fix NoMosaic (#4183) Co-authored-by: V --- src/plugins/noMosaic/index.ts | 53 +++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/plugins/noMosaic/index.ts diff --git a/src/plugins/noMosaic/index.ts b/src/plugins/noMosaic/index.ts new file mode 100644 index 000000000..b5fffba00 --- /dev/null +++ b/src/plugins/noMosaic/index.ts @@ -0,0 +1,53 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; + +const settings = definePluginSettings({ + inlineVideo: { + description: "Play videos without carousel modal", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: true + } +}); + +export default definePlugin({ + name: "NoMosaic", + authors: [Devs.AutumnVN], + description: "Removes Discord image mosaic", + tags: ["Media", "Appearance", "Chat"], + searchTerms: ["image", "mosaic", "media"], + + settings, + + patches: [ + { + find: '"PLAINTEXT_PREVIEW":"OTHER"', + replacement: { + match: /"IMAGE"===\i\|\|"VIDEO"===\i\|\|"CLIP"===\i/, + replace: "false" + } + }, + { + find: "return{visualMediaItems:", + replacement: { + match: /return{visualMediaItems:.+?props:(\i)(?=.{0,20}?\1\.item\.uniqueId)/, + replace: '$&,useFullWidth:["IMAGE","VIDEO","CLIP"].includes($1.item?.type)?false:undefined' + } + }, + { + find: "renderAttachments(", + predicate: () => settings.store.inlineVideo, + replacement: { + match: /url:(\i)\.url\}\);return /, + replace: "$&$1.content_type?.startsWith('image/')&&" + } + }, + ] +}); From 38340bbc2dc933cc81e52c2b38531e686940b261 Mon Sep 17 00:00:00 2001 From: AutumnVN Date: Fri, 8 May 2026 22:14:30 +0700 Subject: [PATCH 07/74] BetterFolders: fix using wrong folder icon (#4197) --- src/plugins/betterFolders/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index 97677251f..acffff289 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -197,7 +197,7 @@ export default definePlugin({ { // Modify the expanded prop to use the boolean if the above patch fails, or check if the folder is expanded from the list if it succeeds // Also export the list of expanded folders to the child folder component if the patch above succeeds, else export undefined - match: /(?<=\(0,\i\.jsxs?\)\(\i,\{folderNode:(\i),expanded:)\i(?=,)/, + match: /(?<=\.\.\.\i,folderNode:(\i),expanded:)\i(?=,)/, replace: (isExpandedOrExpandedIds, folderNote) => "" + `typeof ${isExpandedOrExpandedIds}==="boolean"?${isExpandedOrExpandedIds}:${isExpandedOrExpandedIds}.has(${folderNote}.id),` + `betterFoldersExpandedIds:${isExpandedOrExpandedIds} instanceof Set?${isExpandedOrExpandedIds}:void 0` From 909df8b8c22e377aacbab6587db10e952bf1a461 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 8 May 2026 17:50:39 +0200 Subject: [PATCH 08/74] 1.14.13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 73b79db6b..39dc4c4bb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.14.12", + "version": "1.14.13", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { From 96d4c9e2bd3d973b8f6858d341d7b73637f4fc47 Mon Sep 17 00:00:00 2001 From: creations Date: Fri, 8 May 2026 11:59:22 -0400 Subject: [PATCH 09/74] fix calltimer resetting (#1060) --- src/plugins/callTimer/index.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/plugins/callTimer/index.tsx b/src/plugins/callTimer/index.tsx index db4c0e6af..9d32ad5ec 100644 --- a/src/plugins/callTimer/index.tsx +++ b/src/plugins/callTimer/index.tsx @@ -7,7 +7,7 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs, EquicordDevs } from "@utils/constants"; -import { useTimer } from "@utils/react"; +import { useFixedTimer } from "@utils/react"; import { formatDurationMs } from "@utils/text"; import definePlugin, { OptionType } from "@utils/types"; import { PassiveUpdateState, VoiceState } from "@vencord/discord-types"; @@ -264,16 +264,17 @@ export default definePlugin({ ); }, - renderConnectionTimer(channelId: string) { + renderConnectionTimer({ channelId }: { channelId: string | undefined; }) { return ; }, - ConnectionTimer: ErrorBoundary.wrap(({ channelId }: { channelId: string; }) => { - const time = useTimer({ - deps: [channelId] - }); + ConnectionTimer: ErrorBoundary.wrap((_: { channelId: string | undefined; }) => { + const joinTime = userJoinTimes.get(UserStore.getCurrentUser().id)?.time; + const time = useFixedTimer({ initialTime: joinTime }); + + if (joinTime == null) return null; return (

From 4f1ee5a45d6fcf771dd46201d9ea7e61977f580c Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Fri, 8 May 2026 12:00:25 -0400 Subject: [PATCH 10/74] EquibopStreamFixes: Fix Patch --- src/equicordplugins/equibopStreamFixes.equibop/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equicordplugins/equibopStreamFixes.equibop/index.tsx b/src/equicordplugins/equibopStreamFixes.equibop/index.tsx index f60c764e7..580a22092 100644 --- a/src/equicordplugins/equibopStreamFixes.equibop/index.tsx +++ b/src/equicordplugins/equibopStreamFixes.equibop/index.tsx @@ -155,7 +155,7 @@ export default definePlugin({ find: "}setDesktopEncodingOptions(", replacement: [ { - match: /setDesktopEncodingOptions\((\i),(\i),(\i)\)\{/, + match: /setDesktopEncodingOptions\((\i),(\i),(\i),\i\)\{/, replace: "$&$1=$self.getWidth();$2=$self.getHeight();$3=$self.getFps();", predicate: () => settings.store.forceEncoderSettings, }, From d8bbdc8674007a46dc96fc3a8731d98504e86fc9 Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Fri, 8 May 2026 09:09:00 -0700 Subject: [PATCH 11/74] Canary Fixes. (#1059) --- src/equicordplugins/questify/index.tsx | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index a1a6f10cf..26e85c470 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -207,15 +207,13 @@ export default definePlugin({ }, { // Hides the sponsored banner on the Quests page. - find: "resetSortingFiltering(),requestAnimationFrame", - group: true, + find: "QUEST_HOME)},[]),", predicate: () => !getQuestifySettings().disableQuestsEverything && getQuestifySettings().disableSponsoredBanner, - replacement: [ - { - match: /(?<=(\i),isLoading:(\i)}=\(0,\i.\i\)\(\);)/, - replace: "if(true){$1=null;$2=false;};" - } - ] + replacement: { + match: /(?<=(\i),isLoading:(\i)}=\(0,\i.\i\)\(\i\);)/, + replace: "if(true){$1=null;$2=false;};" + } + }, { // Hides the Quest & Orbs badges on user profiles. @@ -306,7 +304,7 @@ export default definePlugin({ ] }, { - find: "resetSortingFiltering(),requestAnimationFrame", + find: "QUEST_HOME)},[]),", group: true, predicate: () => !getQuestifySettings().disableQuestsEverything, replacement: [ @@ -329,11 +327,6 @@ export default definePlugin({ // Update the last used sort and filter choices when the toggle setting for either is changed. match: /(?<=ALL,\i.useMemo\(\(\)=>\()({sortMethod:(\i),filters:(\i))/, replace: "$self.setLastSortChoice($2),$self.setLastFilterChoices($3),$1" - }, - { - // Makes the reset action return to the Questify sort option. - match: /(?<=resetSortingFiltering:\(\)=>{\i\(\),\i\()\i.\i.SUGGESTED/, - replace: '"questify"' } ] }, @@ -419,7 +412,7 @@ export default definePlugin({ replacement: [ { // Subscribes each Quest tile to Questify's manual rerender trigger. - match: /(?=return\(0,\i\.\i\)\("div",\{id:`quest-tile-\$\{\i\.id\}`)/, + match: /(?=return\(0,\i\.\i\)\("div",\{id:)/, replace: "void $self.useQuestRerender();" }, { From a7aa4d7f1013a284c101b3917990cba20718dbc4 Mon Sep 17 00:00:00 2001 From: Jakub Bednarski <45859721+Leon135@users.noreply.github.com> Date: Fri, 8 May 2026 20:24:45 +0200 Subject: [PATCH 12/74] fix(declutter): include avatar decoration removal on dms chat (#1061) * fix(declutter): include avatar decoration removal on dms list * Update regex for avatar decoration replacement --------- Co-authored-by: thororen <78185467+thororen1234@users.noreply.github.com> --- src/equicordplugins/declutter/index.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/equicordplugins/declutter/index.tsx b/src/equicordplugins/declutter/index.tsx index 6ed8a4245..a17f9e8ab 100644 --- a/src/equicordplugins/declutter/index.tsx +++ b/src/equicordplugins/declutter/index.tsx @@ -176,6 +176,15 @@ export default definePlugin({ }, predicate: () => settings.store.removeAvatarDecoration && !isPluginEnabled(decor.name), }, + { + // Avatar decoration on dms list + find: "showCommunicationDisabledStyles", + replacement: { + match: /null==\i\|\|\i\?null:\(0,\i\.jsxs?\)\("img",\{className:\i\.\i,src:\i,alt:" ","aria-hidden":!0\}\)/, + replace: "null" + }, + predicate: () => settings.store.removeAvatarDecoration && !isPluginEnabled(decor.name), + }, { // Nameplate find: ".MINI_PREVIEW,[", From 91ceac2c3dd0d82960dabde2bfe8069561b19899 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Fri, 8 May 2026 15:14:13 -0400 Subject: [PATCH 13/74] PlatformSpoofer: Spoof Experiment again --- src/equicordplugins/platformSpoofer/index.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/equicordplugins/platformSpoofer/index.tsx b/src/equicordplugins/platformSpoofer/index.tsx index 00a773a2b..0574db893 100644 --- a/src/equicordplugins/platformSpoofer/index.tsx +++ b/src/equicordplugins/platformSpoofer/index.tsx @@ -75,12 +75,18 @@ export default definePlugin({ ] }, { - find: ".ORBS,label:", - replacement: { - match: /\{enabled:(\i).{0,25}"collectibles_shop_header_bar"\}\)/, - replace: "$1=true" - } - } + find: '"2025-01-virtual-currency-rollout"', + replacement: [ + { + match: /(?<=\}\),)(\i)/, + replace: "$1=e=>({enabled:true}),_equicord_$1" + }, + { + match: /(?<=\.getConfig\(\i\)\.enabled\},)(\i)/, + replace: "$1=e=>({enabled:true}),_equicord_$1" + } + ] + }, ], getPlatform(bypass, userId?: any) { const platform = settings.store.platform ?? "desktop"; From 747c7f23ed1b67a316fbb95db7c38a5cd5c536e5 Mon Sep 17 00:00:00 2001 From: Moowi Date: Sat, 9 May 2026 05:32:42 +0300 Subject: [PATCH 14/74] feat(Streaks): shows a streak next to a user when you exchange dms with them on consecutive days (#1062) * feat(Streaks): show DM streak badge for consecutive day messages * Apply suggestions from code review Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * bleh * ConcatenatedModules stuff * Update concatenatedModules.tsx --------- Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../_core/concatenatedModules.tsx | 11 +- src/equicordplugins/channelTabs/index.tsx | 2 +- src/equicordplugins/iconViewer/index.tsx | 2 +- src/equicordplugins/streaks/index.tsx | 144 ++++++++++++++++++ src/equicordplugins/streaks/style.css | 13 ++ 5 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 src/equicordplugins/streaks/index.tsx create mode 100644 src/equicordplugins/streaks/style.css diff --git a/src/equicordplugins/_core/concatenatedModules.tsx b/src/equicordplugins/_core/concatenatedModules.tsx index 0cde5f7be..c6b4a6662 100644 --- a/src/equicordplugins/_core/concatenatedModules.tsx +++ b/src/equicordplugins/_core/concatenatedModules.tsx @@ -14,19 +14,20 @@ export default definePlugin({ name: "ConcatenatedModules", description: "Extract modules that have been concatenated by the bundler", authors: [Devs.thororen], - - required: true, - patches: [ { find: "AngleBracketsIcon", replacement: { match: /\i\.\i\((\i)\),\i\.\i\(\i,\{AIcon/, - replace: "$self.iconsModule($1),$&" + replace: "$self.setIconsModule($1),$&" } } ], - iconsModule(value: IconsDef) { + setIconsModule(value: IconsDef) { iconsModule = value; + + // incase you dont want to use iconviewer you can do + // Vencord.Plugins.plugins.ConcatenatedModules.iconsModule instead for icons and viewing paths + this.iconsModule = value; }, }); diff --git a/src/equicordplugins/channelTabs/index.tsx b/src/equicordplugins/channelTabs/index.tsx index b536a216c..0a7d3b75f 100644 --- a/src/equicordplugins/channelTabs/index.tsx +++ b/src/equicordplugins/channelTabs/index.tsx @@ -49,7 +49,7 @@ export default definePlugin({ description: "Group your commonly visited channels in tabs, like a browser", tags: ["Appearance", "Customisation", "Organisation", "Servers"], authors: [Devs.TheSun, Devs.TheKodeToad, EquicordDevs.keifufu, Devs.Nickyux, EquicordDevs.DiabeloDEV, EquicordDevs.justjxke, EquicordDevs.keircn], - dependencies: ["ContextMenuAPI"], + dependencies: ["ContextMenuAPI", "ConcatenatedModules"], contextMenus: { "channel-mention-context": contextMenuPatch, "channel-context": contextMenuPatch, diff --git a/src/equicordplugins/iconViewer/index.tsx b/src/equicordplugins/iconViewer/index.tsx index 6df0d72f1..320979e3f 100644 --- a/src/equicordplugins/iconViewer/index.tsx +++ b/src/equicordplugins/iconViewer/index.tsx @@ -21,7 +21,7 @@ export default definePlugin({ description: "Adds a new tab to settings to preview all icons.", tags: ["Developers"], authors: [EquicordDevs.iamme], - dependencies: ["Settings"], + dependencies: ["Settings", "ConcatenatedModules"], startAt: StartAt.WebpackReady, toolboxActions: { "Open Icons Tab"() { diff --git a/src/equicordplugins/streaks/index.tsx b/src/equicordplugins/streaks/index.tsx new file mode 100644 index 000000000..3a067e18b --- /dev/null +++ b/src/equicordplugins/streaks/index.tsx @@ -0,0 +1,144 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./style.css"; + +import { DataStore } from "@api/index"; +import { DecoratorProps } from "@api/MemberListDecorators"; +import { iconsModule } from "@equicordplugins/_core/concatenatedModules"; +import { EquicordDevs } from "@utils/constants"; +import { classNameFactory } from "@utils/css"; +import definePlugin from "@utils/types"; +import { Message } from "@vencord/discord-types"; +import { ChannelStore, moment, SelectedChannelStore, Tooltip, UserStore } from "@webpack/common"; + +const cl = classNameFactory("vc-streaks-"); +const dataKey = "vc-streaks-data"; + +const Flags = { + SENT: 1, + RECEIVED: 2, + BOTH: 3 +}; + +const STREAK_THRESHOLDS = { + ELITE: 100, + DIAMOND: 60, + GOLD: 30, + SILVER: 14 +}; + +interface UserStreak { + count: number; + lastDay: string; + todayFlags: number; + todayDate: string; +} + +let streaks: Record = {}; + +const todayKey = () => moment().format("YYYY-MM-DD"); +const yesterdayKey = () => moment().subtract(1, "day").format("YYYY-MM-DD"); + +const updateStreak = (userId: string, outgoing: boolean) => { + const today = todayKey(); + const entry = streaks[userId] ??= { count: 0, lastDay: "", todayFlags: 0, todayDate: "" }; + + if (entry.todayDate !== today) { + entry.todayDate = today; + entry.todayFlags = 0; + } + + const next = entry.todayFlags | (outgoing ? Flags.SENT : Flags.RECEIVED); + if (next === entry.todayFlags) return; + entry.todayFlags = next; + + if (next === Flags.BOTH && entry.lastDay !== today) { + entry.count = entry.lastDay === yesterdayKey() ? entry.count + 1 : 1; + entry.lastDay = today; + } + + DataStore.set(dataKey, streaks); +}; + +const getStreakInfo = (userId: string) => { + const entry = streaks[userId]; + if (!entry) return null; + const today = todayKey(); + if (entry.lastDay !== today && entry.lastDay !== yesterdayKey()) return null; + return { count: entry.count, active: entry.lastDay === today }; +}; + +const colorFor = (streak: number) => { + if (streak >= STREAK_THRESHOLDS.ELITE) return "#9b39fe"; + if (streak >= STREAK_THRESHOLDS.DIAMOND) return "#f7409c"; + if (streak >= STREAK_THRESHOLDS.GOLD) return "#f75340"; + if (streak >= STREAK_THRESHOLDS.SILVER) return "#f57b0b"; + return "#f59e0b"; +}; + +const StreakBadge = ({ userId }: { userId: string; }) => { + const info = getStreakInfo(userId); + if (!info || info.count < 1) return null; + + const FireIcon = iconsModule?.FireIcon; + const color = info.active ? colorFor(info.count) : "#9ca3af"; + + return ( + + {tooltipProps => ( + + {FireIcon && } + {info.count} + + )} + + ); +}; + +export default definePlugin({ + name: "Streaks", + description: "Shows a streak next to a user when you exchange DMs with them on consecutive days.", + authors: [EquicordDevs.Moowi], + tags: ["Friends", "Fun"], + dependencies: ["MessageDecorationsAPI", "MemberListDecoratorsAPI", "ConcatenatedModules"], + + async start() { + streaks = await DataStore.get(dataKey) ?? {}; + }, + + stop() { + streaks = {}; + }, + + flux: { + async MESSAGE_CREATE({ optimistic, type, message, channelId }: { optimistic: boolean; type: string; message: Message; channelId: string; }) { + if (optimistic || type !== "MESSAGE_CREATE" || message.state === "SENDING") return; + if (message.author?.bot || channelId !== SelectedChannelStore.getChannelId()) return; + + const channel = ChannelStore.getChannel(channelId); + if (!channel.isDM()) return; + + const recipientId = channel.recipients[0]; + if (!recipientId) return; + + const me = UserStore.getCurrentUser()?.id; + if (message.author.id === me) updateStreak(recipientId, true); + else if (message.author.id === recipientId) updateStreak(recipientId, false); + }, + }, + + renderMessageDecoration({ message }) { + const userId = message?.author?.id; + if (!userId || userId === UserStore.getCurrentUser()?.id) return null; + return ; + }, + + renderMemberListDecorator({ user }: DecoratorProps) { + if (!user || user.id === UserStore.getCurrentUser()?.id) return null; + return ; + }, +}); diff --git a/src/equicordplugins/streaks/style.css b/src/equicordplugins/streaks/style.css new file mode 100644 index 000000000..fc153cd8e --- /dev/null +++ b/src/equicordplugins/streaks/style.css @@ -0,0 +1,13 @@ +.vc-streaks-badge { + display: inline-flex; + align-items: center; + gap: 2px; + margin-left: 4px; + vertical-align: middle; +} + +.vc-streaks-count { + font-size: 12px; + font-weight: 700; + line-height: 1; +} From cdfb632fb344086073bc3b7772e1368147160871 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 9 May 2026 13:20:25 -0400 Subject: [PATCH 15/74] pnpm 11 --- package.json | 25 ------------------------- pnpm-workspace.yaml | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 6dc74c3c5..dfc10ec18 100644 --- a/package.json +++ b/package.json @@ -97,31 +97,6 @@ "typescript-transform-paths": "^3.5.6", "zip-local": "^0.3.5" }, - "packageManager": "pnpm@10.33.0", - "pnpm": { - "patchedDependencies": { - "@types/less@3.0.8": "patches/@types__less@3.0.8.patch" - }, - "peerDependencyRules": { - "ignoreMissing": [ - "eslint-plugin-import", - "eslint" - ] - }, - "allowedDeprecatedVersions": { - "source-map-resolve": "*", - "resolve-url": "*", - "source-map-url": "*", - "urix": "*", - "q": "*" - }, - "onlyBuiltDependencies": [ - "esbuild" - ], - "overrides": { - "@eslint/plugin-kit@<0.3.4": ">=0.3.4" - } - }, "engines": { "node": ">=18" } diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 9bec5ba55..0e9faf0f1 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,23 @@ packages: - packages/* + +allowBuilds: + esbuild: true + +overrides: + "@eslint/plugin-kit@<0.3.4": ">=0.3.4" + +patchedDependencies: + "@types/less@3.0.8": patches/@types__less@3.0.8.patch + +peerDependencyRules: + ignoreMissing: + - eslint-plugin-import + - eslint + +allowedDeprecatedVersions: + source-map-resolve: "*" + resolve-url: "*" + source-map-url: "*" + urix: "*" + q: "*" From eeb588096f0a4bdd9ad36076747daed273c6e073 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 9 May 2026 13:25:37 -0400 Subject: [PATCH 16/74] FontLoader: Fix weird searchbar --- src/equicordplugins/fontLoader/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/equicordplugins/fontLoader/index.tsx b/src/equicordplugins/fontLoader/index.tsx index aab6f31f3..1713f6129 100644 --- a/src/equicordplugins/fontLoader/index.tsx +++ b/src/equicordplugins/fontLoader/index.tsx @@ -135,14 +135,13 @@ function GoogleFontSearch({ onSelect }: { onSelect: (font: GoogleFontMetadata) = return (

Search Google Fonts - Click on any font to apply it. + Click on any font to apply it. handleSearch(e)} placeholder="Search fonts..." disabled={loading} - className={Margins.bottom16} /> {results.length > 0 && ( From 2bd0479e6dd7f745ddf8d3c9a972999347eca082 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 9 May 2026 13:36:37 -0400 Subject: [PATCH 17/74] FontLoader preloadfont --- src/equicordplugins/fontLoader/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/equicordplugins/fontLoader/index.tsx b/src/equicordplugins/fontLoader/index.tsx index 1713f6129..244a2430e 100644 --- a/src/equicordplugins/fontLoader/index.tsx +++ b/src/equicordplugins/fontLoader/index.tsx @@ -71,7 +71,7 @@ async function searchGoogleFonts(query: string) { } const preloadFont = (family: string) => - loadFontStyle(createGoogleFontUrl(family, "&text=The quick brown fox jumps over the lazy dog")); + loadFontStyle(createGoogleFontUrl(family, ":wght@400;700")); let styleElement: HTMLStyleElement | null = null; From 02d94f797f3f0a93d66ec9e21db64cda3beee473 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 9 May 2026 14:48:50 -0400 Subject: [PATCH 18/74] packagemanager pnpm 11.0.9 --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index dfc10ec18..579fa0e79 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "typescript-transform-paths": "^3.5.6", "zip-local": "^0.3.5" }, + "packageManager": "pnpm@11.0.9", "engines": { "node": ">=18" } From 863f5e89bdf39453e5da35746affa027e0b1e5d7 Mon Sep 17 00:00:00 2001 From: Etorix <92535668+EtorixDev@users.noreply.github.com> Date: Sat, 9 May 2026 11:50:20 -0700 Subject: [PATCH 19/74] Questify: Fix Button Logic.... (#1065) * Fix Button Logic. * Fix Quest Button Logic. --- .../components/questButtonSettings.tsx | 5 +-- src/equicordplugins/questify/index.tsx | 35 ++++++++++++------- .../questify/settings/restartTracking.ts | 4 +++ 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/equicordplugins/questify/components/questButtonSettings.tsx b/src/equicordplugins/questify/components/questButtonSettings.tsx index 6378bc130..e70ca09d2 100644 --- a/src/equicordplugins/questify/components/questButtonSettings.tsx +++ b/src/equicordplugins/questify/components/questButtonSettings.tsx @@ -7,6 +7,7 @@ import { QuestRewardType, QuestTaskType } from "@vencord/discord-types/enums"; import type { JSX } from "react"; +import { enabledOnStartup } from ".."; import { getQuestifySettings, useQuestifySettings } from "../settings/access"; import { type QuestButtonAction, type QuestButtonDisplayMode, type QuestButtonIncludedTypes, type QuestButtonIndicatorMode } from "../settings/def"; import { startAutoFetchingQuests } from "../settings/fetching"; @@ -142,7 +143,7 @@ export function QuestButtonSetting(): JSX.Element { Quest Button Show a Quest Button in the server list with an optional indicator for unclaimed and unignored Quests. - + {enabledOnStartup && - + } Button Behavior diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index 26e85c470..e58d447f6 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -8,7 +8,7 @@ import "./styles.css"; import { playAudio } from "@api/AudioPlayer"; import { addServerListElement, removeServerListElement, ServerListRenderPosition } from "@api/ServerList"; -import { Settings } from "@api/Settings"; +import { PlainSettings, Settings } from "@api/Settings"; import { ErrorBoundary } from "@components/index"; import { EquicordDevs } from "@utils/constants"; import definePlugin, { StartAt } from "@utils/types"; @@ -23,7 +23,7 @@ import { getQuestifySettings } from "./settings/access"; import { resetQuestsToResume, startAutoFetchingQuests, stopAutoFetchingQuests } from "./settings/fetching"; import { validateIgnoredQuests } from "./settings/ignoredQuests"; import { rerenderQuests, useQuestRerender } from "./settings/rerender"; -import { disposeRestartTracking, initializeRestartTracking, promptToRestartIfDirty } from "./settings/restartTracking"; +import { disposeRestartTracking, initializeRestartTracking, promptToRestartIfDirty, setRestartDirty } from "./settings/restartTracking"; import { settings } from "./settings/store"; import { getSettingsModalOpen, initialQuestDataFetched, setInitialQuestDataFetched, setSettingsModalOpen } from "./state"; import { canAutoCompleteQuest, getActiveAutoCompletes, getQuestAutoCompleteProgress, getQuestButtonProps, getQuestPanelSubtitleText, hasEnabledAutoCompleteQuestTypes, processQuestForAutoComplete, resumeInterruptedAutoCompletes, setHeartbeatStackTracePatchSucceeded, setVideoProgressStackTracePatchSucceeded, stopAllAutoCompletes, stopAutoCompletesForRunningGames, stopQuestAutoComplete } from "./utils/completion"; @@ -37,6 +37,7 @@ import { formatLowerBadge, QUEST_PAGE } from "./utils/ui"; let isSwitchingAccount = false; let didAttemptAutoCompleteResume = false; const notifiedCompletedQuests = new Set(); +export const enabledOnStartup = PlainSettings.plugins.Questify?.enabled; function setOnQuestsPage(force?: boolean): void { getQuestifySettings().isOnQuestsPage = force ?? (window.location.pathname === QUEST_PAGE); @@ -381,27 +382,28 @@ export default definePlugin({ replace: "!$self.canAutoCompleteQuest(arguments[0].quest)&&" }, // If this group becomes unruly due to Discord refactoring and is unfixable, - // the 2nd, 3rd, and 4th can be commented out in favor of just the 1st at the - // expense of not seeing CTA buttons on completed but unclaimed Quests. + // the 2nd, 3rd, and 4th can be commented out in favor of just the 1st at the expense + // of not seeing CTA buttons on completed but unclaimed Quests. Also, questifyCanAutoComplete + // would need to be replaced as the 2nd patch defines it. { // Always expose the CTA button when available instead of only for videos and activities. - match: /(?<=wrap:!1,children:\[)\i&&[^?]+/, - replace: "!!arguments[0].quest.config.ctaConfig" + match: /(?<=wrap:!1,children:\[)(\i&&[^?]+)/, + replace: "((!!arguments[0].quest.config.ctaConfig&&questifyCanAutoComplete)||($1))" }, { // Let completed/claimed expired Quests with CTAs use the CTA-aware completed branch. - match: /(?<=return\()(?=\i.enabled&&\i===\i\.\i\.EXPIRED_CLAIMABLE&&\i\.\i\.has\(\i\))/, - replace: "!arguments[0].quest.config.ctaConfig&&" + match: /(return\()(?=\i.enabled&&\i===\i\.\i\.EXPIRED_CLAIMABLE&&\i\.\i\.has\(\i\))/, + replace: "const questifyCanAutoComplete=$self.canAutoCompleteQuest(arguments[0].quest);$1(questifyCanAutoComplete?!arguments[0].quest.config.ctaConfig:true)&&" }, { // Let completed/claimed expired Quests with CTAs use the CTA-aware completed branch. - match: /(?<=\):\i\?\i=)(\i)(?=\?\(0,\i\.jsx\)\(\i,\{quest:\i,sourceQuestContent:\i,onClick:\i,text:\i\}\):\(0,\i\.\i\)\(\i\)\?)/, - replace: "arguments[0].quest.config.ctaConfig||$1" + match: /(?<=\):\i\?\i=)(\i)(?=\?\(0,\i\.jsx\)\(\i,\{quest:\i,sourceQuestContent:\i,onClick:\i,text:\i\}\):)/, + replace: "((arguments[0].quest.config.ctaConfig&&questifyCanAutoComplete)||($1))" }, { // Force the CTA-aware complete branch. - match: /(?<=analyticsCtxQuestContentRowIndex:\i}\)}\):\i&&\i)(.{0,200}?fullWidth:!0}\)}\):)(\i.enabled)(.{0,50}?CLAIMED\))&&\i.\i.has\(\i\)(\?\i=)\i/, - replace: "&&false$1arguments[0].quest.config.ctaConfig&&arguments[0].quest.userStatus?.completedAt&&($2||true)$3$4true" + match: /(?<=analyticsCtxQuestContentRowIndex:\i}\)}\):\i&&\i)(.{0,200}?fullWidth:!0}\)}\):)(\i.enabled.{0,50}?CLAIMED\)&&\i.\i.has\(\i\))(\?\i=)(\i)/, + replace: "&&!questifyCanAutoComplete$1((questifyCanAutoComplete&&arguments[0].quest.config.ctaConfig&&arguments[0].quest.userStatus?.completedAt)||($2))$3(questifyCanAutoComplete||$4)" } ] }, @@ -615,8 +617,15 @@ export default definePlugin({ renderQuestifyButton: ErrorBoundary.wrap(QuestButton, { noop: true }), start() { + if (!enabledOnStartup && PlainSettings.plugins.Questify?.enabled) { + setRestartDirty(true); + } + initializeRestartTracking(settings); - addServerListElement(ServerListRenderPosition.Above, this.renderQuestifyButton); + + if (enabledOnStartup) { + addServerListElement(ServerListRenderPosition.Above, this.renderQuestifyButton); + } onceReady.then(() => { if (!getQuestifySettings().disableQuestsEverything) { diff --git a/src/equicordplugins/questify/settings/restartTracking.ts b/src/equicordplugins/questify/settings/restartTracking.ts index 19aa59c03..93acdbe99 100644 --- a/src/equicordplugins/questify/settings/restartTracking.ts +++ b/src/equicordplugins/questify/settings/restartTracking.ts @@ -48,6 +48,10 @@ export function isRestartDirty(): boolean { return restartDirty; } +export function setRestartDirty(dirty: boolean): void { + restartDirty = dirty; +} + export function promptToRestartIfDirty(): boolean { if (!restartDirty) { return false; From 6cd67134fbef6f65be3b1dc94c5deb5541fb6669 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 9 May 2026 16:58:40 -0400 Subject: [PATCH 20/74] CallTimer: Fix timer again apperantly its not resetting now --- src/plugins/callTimer/index.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/plugins/callTimer/index.tsx b/src/plugins/callTimer/index.tsx index 9d32ad5ec..cfb939c28 100644 --- a/src/plugins/callTimer/index.tsx +++ b/src/plugins/callTimer/index.tsx @@ -7,7 +7,7 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs, EquicordDevs } from "@utils/constants"; -import { useFixedTimer } from "@utils/react"; +import { useTimer } from "@utils/react"; import { formatDurationMs } from "@utils/text"; import definePlugin, { OptionType } from "@utils/types"; import { PassiveUpdateState, VoiceState } from "@vencord/discord-types"; @@ -264,17 +264,14 @@ export default definePlugin({ ); }, - renderConnectionTimer({ channelId }: { channelId: string | undefined; }) { + renderConnectionTimer({ channelId }: { channelId: string; }) { return ; }, - ConnectionTimer: ErrorBoundary.wrap((_: { channelId: string | undefined; }) => { - const joinTime = userJoinTimes.get(UserStore.getCurrentUser().id)?.time; - const time = useFixedTimer({ initialTime: joinTime }); - - if (joinTime == null) return null; + ConnectionTimer: ErrorBoundary.wrap(({ channelId }: { channelId: string; }) => { + const time = useTimer({ deps: [channelId] }); return (

From bcf87fa3828963f6d73e4cdc6cfadf2874d29e42 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 9 May 2026 17:01:12 -0400 Subject: [PATCH 21/74] Revert "CallTimer: Fix timer again apperantly its not resetting now" This reverts commit 6cd67134fbef6f65be3b1dc94c5deb5541fb6669. --- src/plugins/callTimer/index.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/plugins/callTimer/index.tsx b/src/plugins/callTimer/index.tsx index cfb939c28..9d32ad5ec 100644 --- a/src/plugins/callTimer/index.tsx +++ b/src/plugins/callTimer/index.tsx @@ -7,7 +7,7 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs, EquicordDevs } from "@utils/constants"; -import { useTimer } from "@utils/react"; +import { useFixedTimer } from "@utils/react"; import { formatDurationMs } from "@utils/text"; import definePlugin, { OptionType } from "@utils/types"; import { PassiveUpdateState, VoiceState } from "@vencord/discord-types"; @@ -264,14 +264,17 @@ export default definePlugin({ ); }, - renderConnectionTimer({ channelId }: { channelId: string; }) { + renderConnectionTimer({ channelId }: { channelId: string | undefined; }) { return ; }, - ConnectionTimer: ErrorBoundary.wrap(({ channelId }: { channelId: string; }) => { - const time = useTimer({ deps: [channelId] }); + ConnectionTimer: ErrorBoundary.wrap((_: { channelId: string | undefined; }) => { + const joinTime = userJoinTimes.get(UserStore.getCurrentUser().id)?.time; + const time = useFixedTimer({ initialTime: joinTime }); + + if (joinTime == null) return null; return (

From 9e2a1b97f9dba1dc9096b740a55d69066e470585 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 9 May 2026 17:04:33 -0400 Subject: [PATCH 22/74] CallTimer: fix mappings --- src/plugins/callTimer/index.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/plugins/callTimer/index.tsx b/src/plugins/callTimer/index.tsx index 9d32ad5ec..2924d71ed 100644 --- a/src/plugins/callTimer/index.tsx +++ b/src/plugins/callTimer/index.tsx @@ -71,7 +71,7 @@ export const settings = definePluginSettings({ }); // Save the join time of all users in a Map -type userJoinData = { channelId: string, time: number; guildId: string; }; +type userJoinData = { channelId: string, time: number; guildId: string | null; }; const userJoinTimes = new Map(); /** @@ -84,7 +84,7 @@ const userJoinTimes = new Map(); * unique identifier of the guild (server) to which the user belongs. It is used to associate the * user's join time with a specific guild within the application or platform. */ -function addUserJoinTime(userId: string, channelId: string, guildId: string) { +function addUserJoinTime(userId: string, channelId: string, guildId: string | null) { // create a random number userJoinTimes.set(userId, { channelId, time: Date.now(), guildId }); } @@ -152,11 +152,6 @@ export default definePlugin({ const { userId, channelId, guildId } = state; const isMe = userId === myId; - if (!guildId) { - // guildId is never undefined here - continue; - } - // check if the state does not actually has a `oldChannelId` property if (!("oldChannelId" in state) && !runOneTime && !settings.store.watchLargeGuilds) { // batch update triggered. This is ignored because it @@ -173,7 +168,7 @@ export default definePlugin({ if (channelId !== oldChannelId) { if (channelId) { // move or join - addUserJoinTime(userId, channelId, guildId); + addUserJoinTime(userId, channelId, guildId ?? null); } else if (oldChannelId) { // leave removeUserJoinTime(userId); From 3b9292d3bca39ec3b3b4b7f576e73a54874884c4 Mon Sep 17 00:00:00 2001 From: V Date: Sun, 10 May 2026 13:58:48 +0200 Subject: [PATCH 23/74] Migrate to Discord's new Modals (#4206) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- packages/discord-types/src/components.d.ts | 1 + .../discord-types/src/components/Modal.d.ts | 136 ++++++++++ packages/discord-types/src/index.d.ts | 2 + src/api/Notifications/notificationLog.tsx | 83 +++--- src/api/Notifications/styles.css | 6 - src/api/SettingsSync/cloudSetup.tsx | 21 +- src/components/Card.css | 2 +- src/components/settings/tabs/BaseTab.tsx | 26 +- .../tabs/plugins/ContributorModal.css | 10 - .../tabs/plugins/ContributorModal.tsx | 92 ++++--- .../settings/tabs/plugins/LinkIconButton.css | 1 + .../settings/tabs/plugins/PluginModal.css | 6 +- .../settings/tabs/plugins/PluginModal.tsx | 67 ++--- .../settings/tabs/plugins/UIElements.css | 3 +- .../settings/tabs/plugins/UIElements.tsx | 16 +- .../tabs/plugins/components/Common.tsx | 1 + .../plugins/components/ComponentSetting.tsx | 4 +- .../settings/tabs/plugins/index.tsx | 21 +- src/components/settings/tabs/styles.css | 8 - .../settings/tabs/sync/CloudTab.tsx | 20 +- .../settings/tabs/themes/CspErrorCard.tsx | 22 +- src/components/settings/tabs/themes/index.tsx | 20 +- .../settings/tabs/updater/Components.tsx | 28 +- .../settings/tabs/updater/runWithDispatch.tsx | 16 +- .../tabs/vencord/NotificationSettings.tsx | 24 +- .../settings/tabs/vencord/index.tsx | 20 +- src/plugins/_api/badges/index.tsx | 25 +- src/plugins/_core/supportHelper.tsx | 113 ++++++--- .../components/RenameButton.tsx | 2 +- .../betterSessions/components/RenameModal.tsx | 62 ++--- src/plugins/consoleShortcuts/index.ts | 5 +- src/plugins/crashHandler/index.ts | 3 +- src/plugins/customCommands/CreateTagModal.tsx | 168 ++++++------ .../decor/lib/stores/AuthorizationStore.tsx | 3 +- src/plugins/decor/settings.tsx | 20 +- .../ui/components/DecorationContextMenu.tsx | 24 +- .../decor/ui/modals/ChangeDecorationModal.tsx | 227 ++++++++--------- .../decor/ui/modals/CreateDecorationModal.tsx | 69 ++--- .../decor/ui/modals/GuidelinesModal.tsx | 78 ++---- src/plugins/decor/ui/styles.css | 2 - src/plugins/expressionCloner/index.tsx | 43 ++-- src/plugins/fakeNitro/index.tsx | 92 +++---- src/plugins/messageLogger/HistoryModal.tsx | 87 +++---- .../components/RolesAndUsersPermissions.tsx | 239 +++++++++--------- src/plugins/permissionsViewer/styles.css | 8 - .../pinDms/components/CreateCategoryModal.tsx | 98 +++---- src/plugins/pinDms/components/contextMenu.tsx | 4 +- src/plugins/pinDms/styles.css | 16 +- src/plugins/reviewDB/auth.tsx | 3 +- .../reviewDB/components/BlockedUserModal.tsx | 22 +- .../reviewDB/components/ReviewComponent.tsx | 99 ++++---- .../reviewDB/components/ReviewModal.tsx | 68 +++-- src/plugins/reviewDB/index.tsx | 32 ++- src/plugins/reviewDB/style.css | 21 +- src/plugins/sendTimestamps/index.tsx | 113 ++++----- src/plugins/sendTimestamps/styles.css | 2 +- src/plugins/serverInfo/GuildInfoModal.tsx | 63 +++-- src/plugins/serverInfo/styles.css | 14 +- src/plugins/translate/TranslateIcon.tsx | 41 +-- src/plugins/translate/TranslateModal.tsx | 43 ++-- src/plugins/translate/settings.tsx | 2 +- src/plugins/translate/styles.css | 14 +- src/plugins/viewRaw/index.tsx | 65 +++-- src/plugins/voiceMessages/index.tsx | 120 ++++----- src/plugins/voiceMessages/styles.css | 4 - src/utils/discord.tsx | 5 +- src/utils/modal.tsx | 173 +++---------- src/utils/types.ts | 2 + src/webpack/common/index.ts | 1 + src/webpack/common/modals.ts | 23 ++ 70 files changed, 1453 insertions(+), 1521 deletions(-) create mode 100644 packages/discord-types/src/components/Modal.d.ts create mode 100644 src/webpack/common/modals.ts diff --git a/packages/discord-types/src/components.d.ts b/packages/discord-types/src/components.d.ts index 88884b8d6..a8b181599 100644 --- a/packages/discord-types/src/components.d.ts +++ b/packages/discord-types/src/components.d.ts @@ -192,6 +192,7 @@ export type TextInput = ComponentType, "onChange"> & { onChange(v: string): void; inputRef?: Ref; + autosize?: boolean; }>; export interface SelectOption { diff --git a/packages/discord-types/src/components/Modal.d.ts b/packages/discord-types/src/components/Modal.d.ts new file mode 100644 index 000000000..e8586b647 --- /dev/null +++ b/packages/discord-types/src/components/Modal.d.ts @@ -0,0 +1,136 @@ +import { ComponentType, ReactNode, Ref, RefObject } from "react"; +import { LiteralUnion } from "type-fest"; + +export type ModalActionVariant = LiteralUnion<"primary" | "secondary" | "critical-primary", string>; +export type ModalSize = LiteralUnion<"sm" | "md" | "lg" | "xl" | "xxl", string>; + +export interface ModalAction { + text: string; + variant: ModalActionVariant; + onClick(): void; + loading?: boolean; + disabled?: boolean; +} + +/** + * Modal with all options: https://files.catbox.moe/c8qxt0.png + */ +export interface ModalProps extends RenderModalProps { + size?: ModalSize; + role?: "alertdialog" | "dialog"; + + title: ReactNode; + /** Optional subtitle, will render below title (duh) */ + subtitle?: ReactNode; + + /** Optional Modal content */ + children?: ReactNode; + input?: ReactNode; + preview?: ReactNode; + + listProps?: any; + onScroll?(): void; + scrollerRef?: Ref; + + /** Action buttons at the bottom of the Modal */ + actions?: ModalAction[]; + /** Custom component to show before actions, useful for e.g. "Don't show again" checkbox */ + actionBarInput?: ReactNode; + actionBarInputLayout?: "default" | "chat-input"; + + /** Shows a Notice (card) between (sub-)title and content (children) */ + notice?: { + message: string; + type: LiteralUnion<"critical", string>; + }; +} + +export interface RenderModalProps { + transitionState: number; + onClose(): void; +} + +/** Wrapper around Modal */ +export interface ConfirmModalProps extends ModalProps { + /** Variant for the confirm button, defaults to critical */ + variant?: ModalActionVariant; + confirmText: ModalAction["text"]; + /** Defaults to "Cancel" */ + cancelText?: ModalAction["text"]; + /** + * If onConfirm runs without error, the modal will close. + * To indicate an error, throw an Error. This will show the user a notice inside the Modal and keep it open. + * This notice will either be a generic message if you just throw. If you call setError, it will show that message. + * Even if you call setError, you still have to throw to keep the Modal open + */ + onConfirm?(setError: (error: string) => void): void; + /** Optional callback that runs when the user cancels the action. Whether provided or not, the Modal will close when user clicks Cancel. */ + onCancel?(): void; + onCloseCallback?(): void; + /** Checkbox that shows before the action buttons */ + checkboxProps?: { + /** Defaults to "Don't show again" */ + label?: string; + checked: boolean; + onChange(checked: boolean): void; + }; + + /** Custom Modal content */ + children?: ReactNode; +} + +export type Modal = ComponentType; +export type ConfirmModal = ComponentType; + +export type RenderModal = (props: RenderModalProps) => ReactNode; + +export interface ModalOptions { + modalKey?: string; + onCloseRequest?(): void; + onCloseCallback?(): void; +} + +export interface ModalAPI { + /** + * Wait for the render promise to resolve, then open a modal with it. + * This is usually preferable to render().then(openModal), because it shows a Loading modal + */ + openModalLazy: (renderModal: () => Promise, options?: ModalOptions & { contextKey?: string; }) => Promise; + /** + * Open a Modal with the given render function. + */ + openModal: (renderModal: RenderModal, options?: ModalOptions, contextKey?: string) => string; + /** + * Close a modal by its key + */ + closeModal: (modalKey: string, contextKey?: string) => void; + /** + * Close all open modals + */ + closeAllModals: () => void; +} + +export interface MediaModalItem { + url: string; + type: "IMAGE" | "VIDEO" | "CLIP"; + original?: string; + alt?: string; + width?: number; + height?: number; + animated?: boolean; + maxWidth?: number; + maxHeight?: number; +} + +export interface MediaModalProps { + location?: string; + contextKey?: string; + onCloseCallback?: () => void; + className?: string; + items: MediaModalItem[]; + startingIndex?: number; + onIndexChange?: (...args: any[]) => void; + fit?: string; + shouldRedactExplicitContent?: boolean; + shouldHideMediaOptions?: boolean; +} diff --git a/packages/discord-types/src/index.d.ts b/packages/discord-types/src/index.d.ts index 23ab505d8..83bbba26e 100644 --- a/packages/discord-types/src/index.d.ts +++ b/packages/discord-types/src/index.d.ts @@ -1,5 +1,7 @@ export * from "./common"; +// TODO: Splitting up components into files in the folder would be good export * from "./components"; +export * from "./components/Modal"; export * from "./flux"; export * from "./fluxEvents"; export * from "./menu"; diff --git a/src/api/Notifications/notificationLog.tsx b/src/api/Notifications/notificationLog.tsx index 5b6ef6767..7cfd55a80 100644 --- a/src/api/Notifications/notificationLog.tsx +++ b/src/api/Notifications/notificationLog.tsx @@ -18,12 +18,11 @@ import * as DataStore from "@api/DataStore"; import { Settings } from "@api/Settings"; -import { Flex } from "@components/Flex"; import { openNotificationSettingsModal } from "@components/settings/tabs/vencord/NotificationSettings"; import { classNameFactory } from "@utils/css"; -import { closeModal, ModalCloseButton, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { useAwaiter } from "@utils/react"; -import { Alerts, Button, Forms, ListScrollerThin, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; +import { RenderModalProps } from "@vencord/discord-types"; +import { ConfirmModal, Forms, ListScrollerThin, Modal,openModal, React, Timestamp, useEffect, useReducer, useState } from "@webpack/common"; import { nanoid } from "nanoid"; import type { DispatchWithoutAction } from "react"; @@ -130,7 +129,7 @@ function NotificationEntry({ data }: { data: PersistentNotificationData; }) { export function NotificationLog({ log, pending }: { log: PersistentNotificationData[], pending: boolean; }) { if (!log.length && !pending) return ( -

+
No notifications yet @@ -150,56 +149,46 @@ export function NotificationLog({ log, pending }: { log: PersistentNotificationD ); } -function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void; }) { +function LogModal(props: RenderModalProps) { const [log, pending] = useLogs(); return ( - - - Notification Log - - - -
- -
- - - - - - - - -
+ }} + /> + ); + } + } + ]} + > + + ); } export function openNotificationLogModal() { - const key = openModal(modalProps => ( - closeModal(key)} - /> - )); + openModal(props => ); } diff --git a/src/api/Notifications/styles.css b/src/api/Notifications/styles.css index e15e01a7d..1d3085e98 100644 --- a/src/api/Notifications/styles.css +++ b/src/api/Notifications/styles.css @@ -89,12 +89,6 @@ margin-bottom: 40px; } -.vc-notification-log-container { - padding: 1em; - max-height: min(750px, 75vh); - width: 100%; -} - .vc-notification-log-wrapper { height: 120px; width: 100%; diff --git a/src/api/SettingsSync/cloudSetup.tsx b/src/api/SettingsSync/cloudSetup.tsx index 8e77d5fbd..faa8b2ff6 100644 --- a/src/api/SettingsSync/cloudSetup.tsx +++ b/src/api/SettingsSync/cloudSetup.tsx @@ -8,9 +8,8 @@ import * as DataStore from "@api/DataStore"; import { showNotification } from "@api/Notifications"; import { Settings } from "@api/Settings"; import { Logger } from "@utils/Logger"; -import { openModal } from "@utils/modal"; import { relaunch } from "@utils/native"; -import { Alerts, OAuth2AuthorizeModal, UserStore } from "@webpack/common"; +import { ConfirmModal,OAuth2AuthorizeModal, openModal, UserStore } from "@webpack/common"; export const logger = new Logger("SettingsSync:CloudSetup", "#39b7e0"); @@ -29,13 +28,17 @@ export async function checkCloudUrlCsp() { const res = await VencordNative.csp.requestAddOverride(Settings.cloud.url, ["connect-src"], "Cloud Sync"); if (res === "ok") { - Alerts.show({ - title: "Cloud Integration enabled", - body: `${host} has been added to the whitelist. Please restart the app for the changes to take effect.`, - confirmText: "Restart now", - cancelText: "Later!", - onConfirm: relaunch - }); + openModal(props => ( + + )); } return false; } diff --git a/src/components/Card.css b/src/components/Card.css index 74260f37e..80ad3e4fe 100644 --- a/src/components/Card.css +++ b/src/components/Card.css @@ -4,7 +4,7 @@ } .vc-card-normal { - border: 1px var(--border-subtle); + border: 1px solid var(--border-subtle); } .vc-card-warning { diff --git a/src/components/settings/tabs/BaseTab.tsx b/src/components/settings/tabs/BaseTab.tsx index 17a2e8025..79aef5890 100644 --- a/src/components/settings/tabs/BaseTab.tsx +++ b/src/components/settings/tabs/BaseTab.tsx @@ -16,11 +16,10 @@ * along with this program. If not, see . */ -import { BaseText } from "@components/BaseText"; import ErrorBoundary from "@components/ErrorBoundary"; import { handleComponentFailed } from "@components/handleComponentFailed"; -import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { onlyOnce } from "@utils/onlyOnce"; +import { Modal,openModal } from "@webpack/common"; import type { ComponentType, PropsWithChildren } from "react"; export function SettingsTab({ children }: PropsWithChildren) { @@ -42,19 +41,18 @@ export function wrapTab(component: ComponentType, tab: string) { } export function openSettingsTabModal(Tab: ComponentType) { - try { - openModal(wrapTab((modalProps: ModalProps) => ( - - - {Tab.displayName?.replace("SettingsTab", "") || "Settings"} - - + Tab = wrapTab(Tab, Tab.displayName || "SettingsTab"); - - - - - ), Tab.displayName || "SettingsTab")); + try { + openModal(props => ( + + + + )); } catch { handleSettingsTabError(); } diff --git a/src/components/settings/tabs/plugins/ContributorModal.css b/src/components/settings/tabs/plugins/ContributorModal.css index ef3b2ddec..fd40b3888 100644 --- a/src/components/settings/tabs/plugins/ContributorModal.css +++ b/src/components/settings/tabs/plugins/ContributorModal.css @@ -1,13 +1,3 @@ -.vc-author-modal-root { - padding: 1em; -} - -.vc-author-modal-header { - display: flex; - align-items: center; - margin-bottom: 1em; -} - .vc-author-modal-name { text-transform: none; flex-grow: 0; diff --git a/src/components/settings/tabs/plugins/ContributorModal.tsx b/src/components/settings/tabs/plugins/ContributorModal.tsx index 31ed77f32..9139fe8f3 100644 --- a/src/components/settings/tabs/plugins/ContributorModal.tsx +++ b/src/components/settings/tabs/plugins/ContributorModal.tsx @@ -7,15 +7,13 @@ import "./ContributorModal.css"; import { useSettings } from "@api/Settings"; -import ErrorBoundary from "@components/ErrorBoundary"; import { Link } from "@components/Link"; import { DevsById } from "@utils/constants"; import { classNameFactory } from "@utils/css"; import { fetchUserProfile } from "@utils/discord"; import { classes, pluralise } from "@utils/misc"; -import { ModalContent, ModalRoot, openModal } from "@utils/modal"; -import { User } from "@vencord/discord-types"; -import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common"; +import { RenderModalProps, User } from "@vencord/discord-types"; +import { Forms, Modal,openModal, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common"; import Plugins from "~plugins"; @@ -25,18 +23,10 @@ import { PluginCard } from "./PluginCard"; const cl = classNameFactory("vc-author-modal-"); export function openContributorModal(user: User) { - openModal(modalProps => - - - - - - - - ); + openModal(modalProps => ); } -function ContributorModal({ user }: { user: User; }) { +function ContributorModal({ user, modalProps }: { user: User; modalProps: RenderModalProps; }) { useSettings(); const profile = useStateFromStores([UserProfileStore], () => UserProfileStore.getUserProfile(user.id)); @@ -63,41 +53,47 @@ function ContributorModal({ user }: { user: User; }) { const ContributedHyperLink = contributed; return ( - <> -
- - {user.username} + + + {user.username} -
- {website && ( - - )} - {githubName && ( - - )} +
+ {website && ( + + )} + {githubName && ( + + )} +
-
- - {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! - - )} - + } + subtitle={ + 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 => @@ -110,6 +106,6 @@ function ContributorModal({ user }: { user: User; }) { )}
)} - + ); } diff --git a/src/components/settings/tabs/plugins/LinkIconButton.css b/src/components/settings/tabs/plugins/LinkIconButton.css index 97db07802..835206381 100644 --- a/src/components/settings/tabs/plugins/LinkIconButton.css +++ b/src/components/settings/tabs/plugins/LinkIconButton.css @@ -9,4 +9,5 @@ .vc-settings-modal-links { display: flex; gap: 0.2em; + margin-left: auto; } \ No newline at end of file diff --git a/src/components/settings/tabs/plugins/PluginModal.css b/src/components/settings/tabs/plugins/PluginModal.css index 085b2bab6..19175fb02 100644 --- a/src/components/settings/tabs/plugins/PluginModal.css +++ b/src/components/settings/tabs/plugins/PluginModal.css @@ -1,5 +1,5 @@ -.vc-settings-modal-content { - padding-bottom: 24px; +.vc-plugin-modal-header { + display: flex; } .vc-plugin-modal-info { @@ -13,7 +13,7 @@ display: flex; flex-wrap: wrap; gap: 4px; - margin-block: 8px 16px; + margin-top: 8px; } .vc-plugin-modal-tag { diff --git a/src/components/settings/tabs/plugins/PluginModal.tsx b/src/components/settings/tabs/plugins/PluginModal.tsx index bd5d8e98d..0e0422c9a 100644 --- a/src/components/settings/tabs/plugins/PluginModal.tsx +++ b/src/components/settings/tabs/plugins/PluginModal.tsx @@ -21,6 +21,7 @@ import "./PluginModal.css"; import { generateId } from "@api/Commands"; import { hasAnyVisibleSettings, isSettingHidden } from "@api/PluginManager"; import { useSettings } from "@api/Settings"; +import { BaseText } from "@components/BaseText"; import ErrorBoundary from "@components/ErrorBoundary"; import { debounce } from "@shared/debounce"; import { gitRemote } from "@shared/vencordUserAgent"; @@ -28,11 +29,10 @@ import { classNameFactory } from "@utils/css"; import { proxyLazy } from "@utils/lazy"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; -import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { OptionType, Plugin, PluginTag } from "@utils/types"; -import { User } from "@vencord/discord-types"; +import { RenderModalProps, User } from "@vencord/discord-types"; import { findCssClassesLazy } from "@webpack"; -import { Clickable, FluxDispatcher, Forms, React, Text, Tooltip, useEffect, useMemo, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common"; +import { Clickable, FluxDispatcher, Forms, Modal,openModal, React, Text, Tooltip, useEffect, useMemo, UserStore, UserSummaryItem, UserUtils, useState } from "@webpack/common"; import { Constructor } from "type-fest"; import { PluginMeta } from "~plugins"; @@ -46,7 +46,7 @@ const cl = classNameFactory("vc-plugin-modal-"); const AvatarStyles = findCssClassesLazy("moreUsers", "avatar", "clickableAvatar"); const UserRecord: Constructor> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any; -interface PluginModalProps extends ModalProps { +interface PluginModalProps extends RenderModalProps { plugin: Plugin; onRestartNeeded(key: string): void; } @@ -131,6 +131,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti onChange={debounce(onChange)} pluginSettings={pluginSettings} definedSettings={settings} + closePluginSettings={onClose} /> ); @@ -166,32 +167,38 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti const pluginMeta = PluginMeta[plugin.name]; return ( - - - {plugin.name} - - - - -
-
-
- {plugin.description} - {!!plugin.tags?.length && } + + {plugin.name} + {!pluginMeta.userPlugin && ( +
+ +
- {!pluginMeta.userPlugin && ( -
- - -
- )} + )} +
+ } + subtitle={ +
+
+ {plugin.description} + {!!plugin.tags?.length && }
+
+ } + > +
+
Authors
@@ -234,8 +241,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti Settings {renderSettings()}
- - +
+ ); } diff --git a/src/components/settings/tabs/plugins/UIElements.css b/src/components/settings/tabs/plugins/UIElements.css index 30db51dbd..a362aad6a 100644 --- a/src/components/settings/tabs/plugins/UIElements.css +++ b/src/components/settings/tabs/plugins/UIElements.css @@ -18,10 +18,9 @@ } .vc-plugin-ui-elements-modal-content { - padding: 1em; display: flex; flex-direction: column; - gap: 1.5em; + gap: 2em; } .vc-plugin-ui-elements-switches { diff --git a/src/components/settings/tabs/plugins/UIElements.tsx b/src/components/settings/tabs/plugins/UIElements.tsx index 8d2b26fc8..ba33fe43d 100644 --- a/src/components/settings/tabs/plugins/UIElements.tsx +++ b/src/components/settings/tabs/plugins/UIElements.tsx @@ -17,9 +17,9 @@ import { Switch } from "@components/Switch"; import { classNameFactory } from "@utils/css"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; -import { ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { IconComponent } from "@utils/types"; -import { Clickable } from "@webpack/common"; +import { RenderModalProps } from "@vencord/discord-types"; +import { Clickable, Modal,openModal } from "@webpack/common"; const cl = classNameFactory("vc-plugin-ui-elements-"); @@ -75,7 +75,7 @@ function Section(props: { return (
- {title} + {title} {description}
@@ -90,12 +90,12 @@ function Section(props: { ); } -function UIElementsModal(props: ModalProps) { +function UIElementsModal(props: RenderModalProps) { const { uiElements } = useSettings(["uiElements.*"]); return ( - - + +
- - +
+
); } diff --git a/src/components/settings/tabs/plugins/components/Common.tsx b/src/components/settings/tabs/plugins/components/Common.tsx index ea947b8cf..de7486457 100644 --- a/src/components/settings/tabs/plugins/components/Common.tsx +++ b/src/components/settings/tabs/plugins/components/Common.tsx @@ -22,6 +22,7 @@ interface SettingBaseProps { }; id: string; definedSettings: DefinedSettings; + closePluginSettings(): void; } export type SettingProps = SettingBaseProps; diff --git a/src/components/settings/tabs/plugins/components/ComponentSetting.tsx b/src/components/settings/tabs/plugins/components/ComponentSetting.tsx index e1521f02a..d67f83be7 100644 --- a/src/components/settings/tabs/plugins/components/ComponentSetting.tsx +++ b/src/components/settings/tabs/plugins/components/ComponentSetting.tsx @@ -20,6 +20,6 @@ import { PluginSettingComponentDef } from "@utils/types"; import { ComponentSettingProps } from "./Common"; -export function ComponentSetting({ setting, onChange }: ComponentSettingProps) { - return setting.component({ setValue: onChange, option: setting }); +export function ComponentSetting({ setting, onChange, closePluginSettings }: ComponentSettingProps) { + return setting.component({ setValue: onChange, option: setting, closePluginSettings }); } diff --git a/src/components/settings/tabs/plugins/index.tsx b/src/components/settings/tabs/plugins/index.tsx index 5a70ed997..99a327bd2 100644 --- a/src/components/settings/tabs/plugins/index.tsx +++ b/src/components/settings/tabs/plugins/index.tsx @@ -35,7 +35,7 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { useAwaiter, useCleanupEffect } from "@utils/react"; import { PluginTag, PluginTags } from "@utils/types"; -import { Alerts, Button, lodash, Parser, React, SearchableSelect, Select, TextInput, Tooltip, useMemo, useRef, useState } from "@webpack/common"; +import { Button, ConfirmModal,lodash, openModal, Parser, React, SearchableSelect, Select, TextInput, Tooltip, useMemo, useRef, useState } from "@webpack/common"; import { JSX } from "react"; import Plugins, { ExcludedPlugins, PluginMeta } from "~plugins"; @@ -121,9 +121,15 @@ function PluginSettings() { useCleanupEffect(() => { if (changes.hasChanges) - Alerts.show({ - title: "Restart required", - body: ( + openModal(props => ( + location.reload()} + > <>

The following plugins require a restart:

{changes.map((s, i) => ( @@ -133,11 +139,8 @@ function PluginSettings() { ))}
- ), - confirmText: "Restart now", - cancelText: "Later!", - onConfirm: () => location.reload() - }); +
+ )); }, []); const depMap = useMemo(() => { diff --git a/src/components/settings/tabs/styles.css b/src/components/settings/tabs/styles.css index fd4aa8f46..6985a6ff7 100644 --- a/src/components/settings/tabs/styles.css +++ b/src/components/settings/tabs/styles.css @@ -59,12 +59,4 @@ .vc-cloud-button-icon { height: 1.25em; -} - -.vc-settings-modal-root { - background: var(--background-base-low); -} - -.vc-settings-modal { - padding-bottom: 1em; } \ No newline at end of file diff --git a/src/components/settings/tabs/sync/CloudTab.tsx b/src/components/settings/tabs/sync/CloudTab.tsx index 19bf33de3..114b16def 100644 --- a/src/components/settings/tabs/sync/CloudTab.tsx +++ b/src/components/settings/tabs/sync/CloudTab.tsx @@ -34,7 +34,7 @@ import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { IconComponent } from "@utils/types"; -import { Alerts, Select, Tooltip, useState } from "@webpack/common"; +import { ConfirmModal,openModal, Select, Tooltip, useState } from "@webpack/common"; function validateUrl(url: string) { try { @@ -228,14 +228,16 @@ function ResetSection() { Alerts.show({ - title: "Are you sure?", - body: "Once your data is erased, we cannot recover it. There's no going back!", - onConfirm: eraseAllCloudData, - confirmText: "Erase it!", - confirmColor: "vc-cloud-erase-data-danger-btn", - cancelText: "Nevermind" - })} + onClick={() => openModal(props => ( + + ))} Icon={DeleteIcon} > Delete your Cloud Account diff --git a/src/components/settings/tabs/themes/CspErrorCard.tsx b/src/components/settings/tabs/themes/CspErrorCard.tsx index 685b9a91a..9e3b89c67 100644 --- a/src/components/settings/tabs/themes/CspErrorCard.tsx +++ b/src/components/settings/tabs/themes/CspErrorCard.tsx @@ -12,7 +12,7 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { relaunch } from "@utils/native"; import { useForceUpdater } from "@utils/react"; -import { Alerts, Button, Forms } from "@webpack/common"; +import { Button, ConfirmModal, Forms, openModal } from "@webpack/common"; export function CspErrorCard() { if (IS_WEB) return null; @@ -38,19 +38,23 @@ export function CspErrorCard() { forceUpdate(); - Alerts.show({ - title: "Restart Required", - body: "A restart is required to apply this change", - confirmText: "Restart now", - cancelText: "Later!", - onConfirm: relaunch - }); + openModal(props => ( + + )); }; const hasImgurHtmlDomain = errors.some(isImgurHtmlDomain); return ( - + Blocked Resources Some images, styles, or fonts were blocked because they come from disallowed domains. It is highly recommended to move them to GitHub or Imgur. But you may also allow domains if you fully trust them. diff --git a/src/components/settings/tabs/themes/index.tsx b/src/components/settings/tabs/themes/index.tsx index 2bc5e81eb..4a44b7057 100644 --- a/src/components/settings/tabs/themes/index.tsx +++ b/src/components/settings/tabs/themes/index.tsx @@ -18,8 +18,12 @@ import "./styles.css"; +import { BaseText } from "@components/BaseText"; import { Card } from "@components/Card"; +import { Flex } from "@components/Flex"; import { Link } from "@components/Link"; +import { Margins } from "@components/margins"; +import { Paragraph } from "@components/Paragraph"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { getStylusWebStoreUrl } from "@utils/web"; import { Forms, React, TabBar, useState } from "@webpack/common"; @@ -59,10 +63,20 @@ function ThemesTab() { - + + - {currentTab === ThemeTab.LOCAL && } - {currentTab === ThemeTab.ONLINE && } + + Theme Performance + + Themes and custom CSS have the potential to cause major lag! If you experience performance issues, try + disabling your themes and CSS to see if they're the cause. The most common cause of lag is the :has() operator. + + + + {currentTab === ThemeTab.LOCAL && } + {currentTab === ThemeTab.ONLINE && } + ); } diff --git a/src/components/settings/tabs/updater/Components.tsx b/src/components/settings/tabs/updater/Components.tsx index 33ea34d7a..67c3b443d 100644 --- a/src/components/settings/tabs/updater/Components.tsx +++ b/src/components/settings/tabs/updater/Components.tsx @@ -12,7 +12,7 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import { relaunch } from "@utils/native"; import { changes, checkForUpdates, update, updateError } from "@utils/updater"; -import { Alerts, Button, Forms, React, Toasts, useState } from "@webpack/common"; +import { Button, ConfirmModal,Forms, openModal, React, Toasts, useState } from "@webpack/common"; import { runWithDispatch } from "./runWithDispatch"; @@ -100,17 +100,21 @@ export function Updatable(props: CommonProps) { setUpdates([]); await new Promise(r => { - Alerts.show({ - title: "Update Success!", - body: "Successfully updated. Restart now to apply the changes?", - confirmText: "Restart", - cancelText: "Not now!", - onConfirm() { - relaunch(); - r(); - }, - onCancel: r - }); + openModal(props => ( + { + relaunch(); + r(); + }} + onCancel={r} + /> + )); }); } })} diff --git a/src/components/settings/tabs/updater/runWithDispatch.tsx b/src/components/settings/tabs/updater/runWithDispatch.tsx index 719958e78..55f290ad9 100644 --- a/src/components/settings/tabs/updater/runWithDispatch.tsx +++ b/src/components/settings/tabs/updater/runWithDispatch.tsx @@ -6,7 +6,7 @@ import { ErrorCard } from "@components/ErrorCard"; import { UpdateLogger } from "@utils/updater"; -import { Alerts, Parser } from "@webpack/common"; +import { ConfirmModal,openModal, Parser } from "@webpack/common"; function getErrorMessage(e: any) { if (!e?.code || !e.cmd) @@ -33,16 +33,20 @@ export function runWithDispatch(dispatch: React.Dispatch ( + {err.split("\n").map((line, idx) =>
{Parser.parse(line)}
)}
- ) - }); +
+ )); } finally { dispatch(false); } diff --git a/src/components/settings/tabs/vencord/NotificationSettings.tsx b/src/components/settings/tabs/vencord/NotificationSettings.tsx index 9024c42bb..f8de31ca0 100644 --- a/src/components/settings/tabs/vencord/NotificationSettings.tsx +++ b/src/components/settings/tabs/vencord/NotificationSettings.tsx @@ -10,8 +10,7 @@ import { ErrorCard } from "@components/ErrorCard"; import { Flex } from "@components/Flex"; import { Margins } from "@utils/margins"; import { identity } from "@utils/misc"; -import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { Button, Forms, Select, Slider, Text } from "@webpack/common"; +import { Button, Forms, Modal,openModal, Select, Slider } from "@webpack/common"; export function NotificationSection() { return ( @@ -35,16 +34,13 @@ export function NotificationSection() { export function openNotificationSettingsModal() { openModal(props => ( - - - Notification Settings - - - - - - - + + + )); } @@ -52,7 +48,7 @@ function NotificationSettings() { const settings = useSettings(["notifications.*"]).notifications; return ( -
+ <> Notification Style {settings.useNative !== "never" && Notification?.permission === "denied" && ( @@ -122,6 +118,6 @@ function NotificationSettings() { onValueRender={v => v === 200 ? "∞" : v} onMarkerRender={v => v === 200 ? "∞" : v} /> -
+ ); } diff --git a/src/components/settings/tabs/vencord/index.tsx b/src/components/settings/tabs/vencord/index.tsx index 4459d8760..7622d67e0 100644 --- a/src/components/settings/tabs/vencord/index.tsx +++ b/src/components/settings/tabs/vencord/index.tsx @@ -32,7 +32,7 @@ import { IS_MAC, IS_WINDOWS } from "@utils/constants"; import { Margins } from "@utils/margins"; import { isPluginDev } from "@utils/misc"; import { relaunch } from "@utils/native"; -import { Alerts, Forms, React, useMemo, UserStore } from "@webpack/common"; +import { ConfirmModal,Forms, openModal, React, useMemo, UserStore } from "@webpack/common"; import { DonateButtonComponent, isDonor } from "./DonateButton"; import { VibrancySettings } from "./MacVibrancySettings"; @@ -111,13 +111,17 @@ function Switches() { settings[key] = v; if (restartRequired) { - Alerts.show({ - title: "Restart Required", - body: "A restart is required to apply this change", - confirmText: "Restart now", - cancelText: "Later!", - onConfirm: relaunch - }); + openModal(props => ( + + )); } }} /> diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index a00b47501..6e79def0e 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -29,9 +29,8 @@ import { copyWithToast } from "@utils/discord"; import { Logger } from "@utils/Logger"; import { Margins } from "@utils/margins"; import { shouldShowContributorBadge } from "@utils/misc"; -import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal"; import definePlugin from "@utils/types"; -import { ContextMenuApi, Forms, Menu, Toasts, UserStore } from "@webpack/common"; +import { ContextMenuApi, Forms, Menu, Modal,openModal, Toasts, UserStore } from "@webpack/common"; const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/emojis/1092089799109775453.png?size=64"; @@ -190,13 +189,14 @@ export default definePlugin({ ContextMenuApi.openContextMenu(event, () => ); }, onClick() { - const modalKey = openModal(props => ( + openModal(props => ( { - closeModal(modalKey); + props.onClose(); VencordNative.native.openExternal("https://github.com/sponsors/Vendicated"); }}> - - + - - + } + > +
-
- +
+
- - +
+ )); }, diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 46666b24e..05484a248 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -34,8 +34,8 @@ import { onlyOnce } from "@utils/onlyOnce"; import { makeCodeblock } from "@utils/text"; import definePlugin from "@utils/types"; import { checkForUpdates, isOutdated, update } from "@utils/updater"; -import { Channel } from "@vencord/discord-types"; -import { Alerts, Button, ChannelStore, Forms, GuildMemberStore, Parser, PermissionsBits, PermissionStore, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common"; +import { Channel, RenderModalProps } from "@vencord/discord-types"; +import { Button, ChannelStore, ConfirmModal, Forms, GuildMemberStore, openModal, Parser, PermissionsBits, PermissionStore, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common"; import { JSX } from "react"; import gitHash from "~git-hash"; @@ -136,6 +136,34 @@ const settings = definePluginSettings({}).withPrivateSettings<{ dismissedDevBuildWarning?: boolean; }>(); +function DevBuildConfirmModal(props: RenderModalProps) { + const s = settings.use(["dismissedDevBuildWarning"]); + + return ( + s.dismissedDevBuildWarning = checked + }} + > +
+ You are using a custom build of Vencord, which we do not provide support for! + + + We only provide support for official builds. + Either switch to an official build or figure your issue out yourself. + + + You will be banned from receiving support if you ignore this rule. +
+
+ ); +} + export default definePlugin({ name: "SupportHelper", required: true, @@ -180,20 +208,28 @@ export default definePlugin({ await checkForUpdatesOnce().catch(() => { }); if (isOutdated) { - return Alerts.show({ - title: "Hold on!", - body:
- You are using an outdated version of Vencord! Chances are, your issue is already fixed. - - Please first update before asking for support! - -
, - onCancel: () => openSettingsTabModal(UpdaterTab!), - cancelText: "View Updates", - confirmText: "Update & Restart Now", - onConfirm: forceUpdate, - secondaryConfirmText: "I know what I'm doing or I can't update" - }); + openModal(props => ( + openSettingsTabModal(UpdaterTab!)} + > +
+ You are using an outdated version of Vencord! Chances are, your issue is already fixed. + + Please first update before asking for support! + + + If you know what you're doing or cannot update, you can dismiss this prompt. + +
+
+ )); + return; } } @@ -201,35 +237,28 @@ export default definePlugin({ if (!roles || TrustedRolesIds.some(id => roles.includes(id))) return; if (!IS_WEB && IS_UPDATER_DISABLED) { - return Alerts.show({ - title: "Hold on!", - body:
- You are using an externally updated Vencord version, which we do not provide support for! - - Please either switch to an officially supported version of Vencord, or - contact your package maintainer for support instead. - -
- }); + openModal(props => ( + +
+ You are using an externally updated Vencord version, which we do not provide support for! + + Please either switch to an officially supported version of Vencord, or + contact your package maintainer for support instead. + +
+
+ )); + return; } if (!IS_STANDALONE && !settings.store.dismissedDevBuildWarning) { - return Alerts.show({ - title: "Hold on!", - body:
- You are using a custom build of Vencord, which we do not provide support for! - - - We only provide support for official builds. - Either switch to an official build or figure your issue out yourself. - - - You will be banned from receiving support if you ignore this rule. -
, - confirmText: "Understood", - secondaryConfirmText: "Don't show again", - onConfirmSecondary: () => settings.store.dismissedDevBuildWarning = true - }); + openModal(props => ); + return; } } }, diff --git a/src/plugins/betterSessions/components/RenameButton.tsx b/src/plugins/betterSessions/components/RenameButton.tsx index c1c24d2e2..b20649d06 100644 --- a/src/plugins/betterSessions/components/RenameButton.tsx +++ b/src/plugins/betterSessions/components/RenameButton.tsx @@ -6,7 +6,7 @@ import { Button } from "@components/Button"; import { SessionInfo } from "@plugins/betterSessions/types"; -import { openModal } from "@utils/modal"; +import { openModal } from "@webpack/common"; import { RenameModal } from "./RenameModal"; diff --git a/src/plugins/betterSessions/components/RenameModal.tsx b/src/plugins/betterSessions/components/RenameModal.tsx index e3faa2091..718a0ad3d 100644 --- a/src/plugins/betterSessions/components/RenameModal.tsx +++ b/src/plugins/betterSessions/components/RenameModal.tsx @@ -16,13 +16,14 @@ * along with this program. If not, see . */ +import { TextButton } from "@components/Button"; import { SessionInfo } from "@plugins/betterSessions/types"; import { getDefaultName, savedSessionsCache, saveSessionsToDataStore } from "@plugins/betterSessions/utils"; -import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal"; -import { Button, Forms, React, TextInput } from "@webpack/common"; +import { RenderModalProps } from "@vencord/discord-types"; +import { Forms, Modal,React, TextInput } from "@webpack/common"; import { KeyboardEvent } from "react"; -export function RenameModal({ props, session, state }: { props: ModalProps, session: SessionInfo["session"], state: [string, React.Dispatch>]; }) { +export function RenameModal({ props, session, state }: { props: RenderModalProps, session: SessionInfo["session"], state: [string, React.Dispatch>]; }) { const [title, setTitle] = state; const [value, setValue] = React.useState(savedSessionsCache.get(session.id_hash)?.name ?? ""); @@ -39,13 +40,24 @@ export function RenameModal({ props, session, state }: { props: ModalProps, sess } return ( - - - Rename - - - - New device name + props.onClose() + }, + { + text: "Save", + variant: "primary", + onClick: onSaveClick + } + ]} + > +
+ New device name - - - - -
- - -
-
- + +
+
); } diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index 51391b0b9..c9ae2a8a5 100644 --- a/src/plugins/consoleShortcuts/index.ts +++ b/src/plugins/consoleShortcuts/index.ts @@ -22,7 +22,6 @@ import { getCurrentChannel, getCurrentGuild } from "@utils/discord"; import { runtimeHashMessageKey } from "@utils/intlHash"; import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy"; import { sleep } from "@utils/misc"; -import { ModalAPI } from "@utils/modal"; import { relaunch } from "@utils/native"; import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches"; import definePlugin, { PluginNative, StartAt } from "@utils/types"; @@ -176,8 +175,8 @@ function makeShortcuts() { me: { getter: () => Common.UserStore.getCurrentUser(), preload: false }, meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false }, messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false }, - openModal: { getter: () => ModalAPI.openModal }, - openModalLazy: { getter: () => ModalAPI.openModalLazy }, + openModal: { getter: () => Common.openModal }, + openModalLazy: { getter: () => Common.openModalLazy }, Stores: { getter: () => Object.fromEntries(Webpack.fluxStores) }, diff --git a/src/plugins/crashHandler/index.ts b/src/plugins/crashHandler/index.ts index 976b29865..2b82faf68 100644 --- a/src/plugins/crashHandler/index.ts +++ b/src/plugins/crashHandler/index.ts @@ -21,11 +21,10 @@ import { showNotification } from "@api/Notifications"; import { definePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import { Logger } from "@utils/Logger"; -import { closeAllModals } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; import { maybePromptToUpdate } from "@utils/updater"; import { filters, findBulk, proxyLazyWebpack } from "@webpack"; -import { DraftType, ExpressionPickerStore, FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common"; +import { closeAllModals, DraftType, ExpressionPickerStore, FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common"; const CrashHandlerLogger = new Logger("CrashHandler"); diff --git a/src/plugins/customCommands/CreateTagModal.tsx b/src/plugins/customCommands/CreateTagModal.tsx index 652f18503..6c39d154a 100644 --- a/src/plugins/customCommands/CreateTagModal.tsx +++ b/src/plugins/customCommands/CreateTagModal.tsx @@ -4,130 +4,116 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { BaseText } from "@components/BaseText"; -import { Button } from "@components/Button"; -import { Card } from "@components/Card"; import { InlineCode } from "@components/CodeBlock"; import { ExpandableSection } from "@components/ExpandableCard"; import { Flex } from "@components/Flex"; import { HeadingSecondary } from "@components/Heading"; import { InfoIcon } from "@components/Icons"; -import { Margins } from "@components/margins"; import { Paragraph } from "@components/Paragraph"; -import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { TextArea, TextInput, useState } from "@webpack/common"; +import { RenderModalProps } from "@vencord/discord-types"; +import { Modal,openModal, TextArea, TextInput, useState } from "@webpack/common"; import { parseTagArguments } from "."; import { addTag, getTag, Tag } from "./settings"; export function openCreateTagModal(initialValue: Tag = { name: "", message: "" }) { openModal(modalProps => ( - + )); } const EXAMPLE_RESPONSE = "Hello {{user}}! I am feeling {{mood = great}}."; -function Modal({ initialValue, modalProps }: { initialValue: Tag; modalProps: ModalProps; }) { +function CreateTagDialog({ initialValue, modalProps }: { initialValue: Tag; modalProps: RenderModalProps; }) { const [name, setName] = useState(initialValue.name); const [message, setMessage] = useState(initialValue.message.replaceAll("\\n", "\n")); + const isEdit = Boolean(initialValue.name); + const detectedArguments = parseTagArguments(message); const hasReservedEphemeral = detectedArguments.some(arg => arg.name === "ephemeral"); const nameAlreadyExists = name !== initialValue.name && getTag(name); + const notice = hasReservedEphemeral + ? 'The argument name "ephemeral" is reserved and cannot be used.' + : nameAlreadyExists + ? `A tag with the name "${name}" already exists and will be overwritten.` + : undefined; + return ( - - - Create Tag - - + { + const tag = { name, message }; + addTag(tag); + modalProps.onClose(); + }, + disabled: !name || !message || hasReservedEphemeral + } + ]} + notice={notice ? { message: notice, type: "critical" } : undefined} + > + +
+ Name + +
- - - Create a new tag which will be registered as a slash command. - -
- Name - -
+
+ Response +