diff --git a/package.json b/package.json index 65a2f4512..e65e1b0a7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.9.9", + "version": "1.10.1", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index b0999d8cd..2ac78d579 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -337,5 +337,6 @@ export const commonRendererPlugins = [ banImportPlugin(builtinModuleRegex, "Cannot import node inbuilt modules in browser code. You need to use a native.ts file"), banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"), banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"), + banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"), ...commonOpts.plugins ]; diff --git a/src/api/Commands/index.ts b/src/api/Commands/index.ts index ef4db171c..e5803ba02 100644 --- a/src/api/Commands/index.ts +++ b/src/api/Commands/index.ts @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { Logger } from "@utils/Logger"; import { makeCodeblock } from "@utils/text"; import { sendBotMessage } from "./commandHelpers"; @@ -46,10 +47,10 @@ export let RequiredMessageOption: Option = ReqPlaceholder; export const _init = function (cmds: Command[]) { try { BUILT_IN = cmds; - OptionalMessageOption = cmds.find(c => c.name === "shrug")!.options![0]; - RequiredMessageOption = cmds.find(c => c.name === "me")!.options![0]; + OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0]; + RequiredMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "me")!.options![0]; } catch (e) { - console.error("Failed to load CommandsApi"); + new Logger("CommandsAPI").error("Failed to load CommandsApi", e, " - cmds is", cmds); } return cmds; } as never; @@ -138,6 +139,8 @@ export function registerCommand(command: C, plugin: string) { throw new Error(`Command '${command.name}' already exists.`); command.isVencordCommand = true; + command.untranslatedName ??= command.name; + command.untranslatedDescription ??= command.description; command.id ??= `-${BUILT_IN.length + 1}`; command.applicationId ??= "-1"; // BUILT_IN; command.type ??= ApplicationCommandType.CHAT_INPUT; diff --git a/src/api/Commands/types.ts b/src/api/Commands/types.ts index bd349e250..70b73775a 100644 --- a/src/api/Commands/types.ts +++ b/src/api/Commands/types.ts @@ -93,8 +93,10 @@ export interface Command { isVencordCommand?: boolean; name: string; + untranslatedName?: string; displayName?: string; description: string; + untranslatedDescription?: string; displayDescription?: string; options?: Option[]; diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index bb9d37894..f718ab11f 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -77,8 +77,16 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) { Validator This section will tell you whether your themes can successfully be loaded
- {themeLinks.map(link => ( - { + const { label, link } = (() => { + const match = /^@(light|dark) (.*)/.exec(rawLink); + if (!match) return { label: rawLink, link: rawLink }; + + const [, mode, link] = match; + return { label: `[${mode} mode only] ${link}`, link }; + })(); + + return - {link} + {label} - - ))} + ; + })}
); @@ -296,6 +304,7 @@ function ThemesTab() { Paste links to css files here One link per line + You can prefix lines with @light or @dark to toggle them based on your Discord theme Make sure to use direct links to files (raw or github.io)! diff --git a/src/plugins/_api/badges/fixBadgeOverflow.css b/src/plugins/_api/badges/fixBadgeOverflow.css deleted file mode 100644 index 348d0ff38..000000000 --- a/src/plugins/_api/badges/fixBadgeOverflow.css +++ /dev/null @@ -1,3 +0,0 @@ -[class*="profileBadges"] { - flex: none; -} diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index cf00a0e29..c44d98b90 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -import "./fixBadgeOverflow.css"; - import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges"; import DonateButton from "@components/DonateButton"; import ErrorBoundary from "@components/ErrorBoundary"; @@ -79,7 +77,7 @@ export default definePlugin({ replace: "...$1.props,$& $1.image??" }, { - match: /(?<=text:(\i)\.description,.{0,50})children:/, + match: /(?<=text:(\i)\.description,.{0,200})children:/, replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :" }, // conditionally override their onClick with badge.onClick if it exists diff --git a/src/plugins/_api/serverList.ts b/src/plugins/_api/serverList.ts index f45bbf104..7904e78b0 100644 --- a/src/plugins/_api/serverList.ts +++ b/src/plugins/_api/serverList.ts @@ -34,7 +34,7 @@ export default definePlugin({ { find: "Messages.SERVERS,children", replacement: { - match: /(?<=Messages\.SERVERS,children:).+?default:return null\}\}\)/, + match: /(?<=Messages\.SERVERS,children:)\i\.map\(\i\)/, replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)" } } diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx index 072f4ee66..0bef0933d 100644 --- a/src/plugins/betterFolders/index.tsx +++ b/src/plugins/betterFolders/index.tsx @@ -132,8 +132,8 @@ export default definePlugin({ }, // Export the isBetterFolders variable to the folders component { - match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,)/, - replace: 'isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,' + match: /switch\(\i\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,/, + replace: '$&isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,' } ] }, diff --git a/src/plugins/clientTheme/index.tsx b/src/plugins/clientTheme/index.tsx index 358bae017..59f3d5fe2 100644 --- a/src/plugins/clientTheme/index.tsx +++ b/src/plugins/clientTheme/index.tsx @@ -12,7 +12,7 @@ import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import definePlugin, { OptionType, StartAt } from "@utils/types"; import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; -import { Button, Forms, useStateFromStores } from "@webpack/common"; +import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common"; const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)"); @@ -36,7 +36,6 @@ function setTheme(theme: string) { saveClientTheme({ theme }); } -const ThemeStore = findStoreLazy("ThemeStore"); const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore"); function ThemeSettings() { diff --git a/src/plugins/CopyFileContents/README.md b/src/plugins/copyFileContents/README.md similarity index 100% rename from src/plugins/CopyFileContents/README.md rename to src/plugins/copyFileContents/README.md diff --git a/src/plugins/CopyFileContents/index.tsx b/src/plugins/copyFileContents/index.tsx similarity index 100% rename from src/plugins/CopyFileContents/index.tsx rename to src/plugins/copyFileContents/index.tsx diff --git a/src/plugins/CopyFileContents/style.css b/src/plugins/copyFileContents/style.css similarity index 100% rename from src/plugins/CopyFileContents/style.css rename to src/plugins/copyFileContents/style.css diff --git a/src/plugins/dearrow/index.tsx b/src/plugins/dearrow/index.tsx index 5fb438256..b7e90e096 100644 --- a/src/plugins/dearrow/index.tsx +++ b/src/plugins/dearrow/index.tsx @@ -46,7 +46,7 @@ const embedUrlRe = /https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/; async function embedDidMount(this: Component) { try { const { embed } = this.props; - const { replaceElements } = settings.store; + const { replaceElements, dearrowByDefault } = settings.store; if (!embed || embed.dearrow || embed.provider?.name !== "YouTube" || !embed.video?.url) return; @@ -63,18 +63,22 @@ async function embedDidMount(this: Component) { if (!hasTitle && !hasThumb) return; + embed.dearrow = { - enabled: true + enabled: dearrowByDefault }; if (hasTitle && replaceElements !== ReplaceElements.ReplaceThumbnailsOnly) { - embed.dearrow.oldTitle = embed.rawTitle; - embed.rawTitle = titles[0].title.replace(/(^|\s)>(\S)/g, "$1$2"); - } + const replacementTitle = titles[0].title.replace(/(^|\s)>(\S)/g, "$1$2"); + embed.dearrow.oldTitle = dearrowByDefault ? embed.rawTitle : replacementTitle; + if (dearrowByDefault) embed.rawTitle = replacementTitle; + } if (hasThumb && replaceElements !== ReplaceElements.ReplaceTitlesOnly) { - embed.dearrow.oldThumb = embed.thumbnail.proxyURL; - embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`; + const replacementProxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`; + + embed.dearrow.oldThumb = dearrowByDefault ? embed.thumbnail.proxyURL : replacementProxyURL; + if (dearrowByDefault) embed.thumbnail.proxyURL = replacementProxyURL; } this.forceUpdate(); @@ -96,6 +100,7 @@ function DearrowButton({ component }: { component: Component; }) { className={"vc-dearrow-toggle-" + (embed.dearrow.enabled ? "on" : "off")} onClick={() => { const { enabled, oldThumb, oldTitle } = embed.dearrow; + settings.store.dearrowByDefault = !enabled; embed.dearrow.enabled = !enabled; if (oldTitle) { embed.dearrow.oldTitle = embed.rawTitle; @@ -153,6 +158,12 @@ const settings = definePluginSettings({ { label: "Titles", value: ReplaceElements.ReplaceTitlesOnly }, { label: "Thumbnails", value: ReplaceElements.ReplaceThumbnailsOnly }, ], + }, + dearrowByDefault: { + description: "Dearrow videos automatically", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: false } }); diff --git a/src/plugins/friendsSince/index.tsx b/src/plugins/friendsSince/index.tsx index 0399a2f72..cb5634c8e 100644 --- a/src/plugins/friendsSince/index.tsx +++ b/src/plugins/friendsSince/index.tsx @@ -7,16 +7,15 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getCurrentChannel } from "@utils/discord"; -import { Logger } from "@utils/Logger"; import definePlugin from "@utils/types"; -import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack"; -import { Heading, RelationshipStore, Text } from "@webpack/common"; +import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack"; +import { RelationshipStore, Text } from "@webpack/common"; const containerWrapper = findByPropsLazy("memberSinceWrapper"); const container = findByPropsLazy("memberSince"); const getCreatedAtDate = findByCodeLazy('month:"short",day:"numeric"'); const locale = findByPropsLazy("getLocale"); -const section = findLazy((m: any) => m.section !== void 0 && m.heading !== void 0 && Object.values(m).length === 2); +const Section = findComponentByCodeLazy('"auto":"smooth"', ".section"); export default definePlugin({ name: "FriendsSince", @@ -28,7 +27,7 @@ export default definePlugin({ find: ".PANEL}),nicknameIcons", replacement: { match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id)}\)}\)/, - replace: "$&,$self.friendsSinceNew({userId:$1,isSidebar:true})" + replace: "$&,$self.FriendsSinceComponent({userId:$1,isSidebar:true})" } }, // User Profile Modal @@ -36,34 +35,19 @@ export default definePlugin({ find: "action:\"PRESS_APP_CONNECTION\"", replacement: { match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id),.{0,100}}\)}\),/, - replace: "$&,$self.friendsSinceNew({userId:$1,isSidebar:false})," + replace: "$&,$self.FriendsSinceComponent({userId:$1,isSidebar:false})," } } ], - getFriendSince(userId: string) { - try { - if (!RelationshipStore.isFriend(userId)) return null; - - return RelationshipStore.getSince(userId); - } catch (err) { - new Logger("FriendsSince").error(err); - return null; - } - }, - - friendsSinceNew: ErrorBoundary.wrap(({ userId, isSidebar }: { userId: string; isSidebar: boolean; }) => { + FriendsSinceComponent: ErrorBoundary.wrap(({ userId, isSidebar }: { userId: string; isSidebar: boolean; }) => { if (!RelationshipStore.isFriend(userId)) return null; const friendsSince = RelationshipStore.getSince(userId); if (!friendsSince) return null; return ( -
- - Friends Since - - +
{ isSidebar ? ( @@ -91,8 +75,7 @@ export default definePlugin({ ) } - -
+
); }, { noop: true }), }); diff --git a/src/plugins/ignoreActivities/README.md b/src/plugins/ignoreActivities/README.md new file mode 100644 index 000000000..abc75720f --- /dev/null +++ b/src/plugins/ignoreActivities/README.md @@ -0,0 +1,13 @@ +# IgnoreActivities + +Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings. + +![](https://github.com/user-attachments/assets/f0c19060-0ecf-4f1c-8165-a5aa40143c82) + +![](https://github.com/user-attachments/assets/73c3fa7a-5b90-41ee-a4d6-91fa76458b74) + +![](https://github.com/user-attachments/assets/1ab3fe73-3911-48d1-8a08-e976af614b41) + +The activity stays showing as a detected game even if ignored, differently from the stock Toggle Detection button from Discord: + +![](https://github.com/user-attachments/assets/08ea60c3-3a31-42de-ae4c-7535fbf1b45a) diff --git a/src/plugins/ignoreActivities/index.tsx b/src/plugins/ignoreActivities/index.tsx index 9e6c21bdd..02261b5ba 100644 --- a/src/plugins/ignoreActivities/index.tsx +++ b/src/plugins/ignoreActivities/index.tsx @@ -237,7 +237,7 @@ function isActivityTypeIgnored(type: number, id?: string) { export default definePlugin({ name: "IgnoreActivities", authors: [Devs.Nuckyz, Devs.Kylie], - description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.", + description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below", dependencies: ["UserSettingsAPI"], settings, @@ -266,6 +266,7 @@ export default definePlugin({ replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),` } }, + // Discord has 3 different components for activities. Currently, the last is the one being used { find: ".activityTitleText,variant", replacement: { @@ -279,6 +280,13 @@ export default definePlugin({ match: /\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),/, replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),` } + }, + { + find: ".promotedLabelWrapperNonBanner,children", + replacement: { + match: /\.appDetailsHeaderContainer.+?children:\i.*?}\),(?<=application:(\i).+?)/, + replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),` + } } ], diff --git a/src/plugins/memberCount/MemberCount.tsx b/src/plugins/memberCount/MemberCount.tsx index 084e7ecc4..0a3f5e620 100644 --- a/src/plugins/memberCount/MemberCount.tsx +++ b/src/plugins/memberCount/MemberCount.tsx @@ -14,7 +14,7 @@ import { OnlineMemberCountStore } from "./OnlineMemberCountStore"; export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) { const currentChannel = useStateFromStores([SelectedChannelStore], () => getCurrentChannel()); - const guildId = isTooltip ? tooltipGuildId! : currentChannel.guild_id; + const guildId = isTooltip ? tooltipGuildId! : currentChannel?.guild_id; const totalCount = useStateFromStores( [GuildMemberCountStore], @@ -33,7 +33,7 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t const threadGroups = useStateFromStores( [ThreadMemberListStore], - () => ThreadMemberListStore.getMemberListSections(currentChannel.id) + () => ThreadMemberListStore.getMemberListSections(currentChannel?.id) ); if (!isTooltip && (groups.length >= 1 || groups[0].id !== "unknown")) { diff --git a/src/plugins/memberCount/OnlineMemberCountStore.ts b/src/plugins/memberCount/OnlineMemberCountStore.ts index 8790f5e29..d74bea2a0 100644 --- a/src/plugins/memberCount/OnlineMemberCountStore.ts +++ b/src/plugins/memberCount/OnlineMemberCountStore.ts @@ -15,8 +15,8 @@ export const OnlineMemberCountStore = proxyLazy(() => { const onlineMemberMap = new Map(); class OnlineMemberCountStore extends Flux.Store { - getCount(guildId: string) { - return onlineMemberMap.get(guildId); + getCount(guildId?: string) { + return onlineMemberMap.get(guildId!); } async _ensureCount(guildId: string) { @@ -25,8 +25,8 @@ export const OnlineMemberCountStore = proxyLazy(() => { await PrivateChannelsStore.preload(guildId, GuildChannelStore.getDefaultChannel(guildId).id); } - ensureCount(guildId: string) { - if (onlineMemberMap.has(guildId)) return; + ensureCount(guildId?: string) { + if (!guildId || onlineMemberMap.has(guildId)) return; preloadQueue.push(() => this._ensureCount(guildId) diff --git a/src/plugins/memberCount/index.tsx b/src/plugins/memberCount/index.tsx index 7e591357d..85dcc4b2d 100644 --- a/src/plugins/memberCount/index.tsx +++ b/src/plugins/memberCount/index.tsx @@ -28,12 +28,12 @@ import { FluxStore } from "@webpack/types"; import { MemberCount } from "./MemberCount"; -export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; }; +export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId?: string): number | null; }; export const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & { - getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; }; + getProps(guildId?: string, channelId?: string): { groups: { count: number; id: string; }[]; }; }; export const ThreadMemberListStore = findStoreLazy("ThreadMemberListStore") as FluxStore & { - getMemberListSections(channelId: string): { [sectionId: string]: { sectionId: string; userIds: string[]; }; }; + getMemberListSections(channelId?: string): { [sectionId: string]: { sectionId: string; userIds: string[]; }; }; }; diff --git a/src/plugins/mentionAvatars/styles.css b/src/plugins/mentionAvatars/styles.css index 64eb41416..cf6e2c14c 100644 --- a/src/plugins/mentionAvatars/styles.css +++ b/src/plugins/mentionAvatars/styles.css @@ -9,3 +9,8 @@ .vc-mentionAvatars-role-icon { margin: 0 2px 0.2rem 4px; } + +/** don't display inside the ServerInfo modal owner mention */ +.vc-gp-owner .vc-mentionAvatars-icon { + display: none; +} diff --git a/src/plugins/mutualGroupDMs/index.tsx b/src/plugins/mutualGroupDMs/index.tsx index a1e73cabf..ec52b4061 100644 --- a/src/plugins/mutualGroupDMs/index.tsx +++ b/src/plugins/mutualGroupDMs/index.tsx @@ -20,7 +20,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { isNonNullish } from "@utils/guards"; import definePlugin from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, useMemo, UserStore } from "@webpack/common"; import { Channel, User } from "discord-types/general"; @@ -28,6 +28,7 @@ const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel"); const UserUtils = findByPropsLazy("getGlobalName"); const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds"); +const ExpandableList = findComponentByCodeLazy(".mutualFriendItem]"); const GuildLabelClasses = findByPropsLazy("guildNick", "guildAvatarWithoutIcon"); function getGroupDMName(channel: Channel) { @@ -50,6 +51,29 @@ function getMutualGDMCountText(user: User) { return `${count === 0 ? "No" : count} Mutual Group${count !== 1 ? "s" : ""}`; } +function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) { + return mutualDms.map(c => ( + { + onClose(); + SelectedChannelActionCreators.selectPrivateChannel(c.id); + }} + > + + +
+
{getGroupDMName(c)}
+
{c.recipients.length + 1} Members
+
+
+ )); +} + const IS_PATCHED = Symbol("MutualGroupDMs.Patched"); export default definePlugin({ @@ -70,6 +94,13 @@ export default definePlugin({ replace: "$1==='MUTUAL_GDMS'?$self.renderMutualGDMs(arguments[0]):$&" } ] + }, + { + find: 'section:"MUTUAL_FRIENDS"', + replacement: { + match: /\.openUserProfileModal.+?\)}\)}\)(?<=(\(0,\i\.jsxs?\)\(\i\.\i,{className:(\i)\.divider}\)).+?)/, + replace: "$&,$self.renderDMPageList({user: arguments[0].user, Divider: $1, listStyle: $2.list})" + } } ], @@ -84,28 +115,9 @@ export default definePlugin({ }, renderMutualGDMs: ErrorBoundary.wrap(({ user, onClose }: { user: User, onClose: () => void; }) => { - const mutualDms = useMemo(() => getMutualGroupDms(user.id), [user.id]); + const mutualGDms = useMemo(() => getMutualGroupDms(user.id), [user.id]); - const entries = mutualDms.map(c => ( - { - onClose(); - SelectedChannelActionCreators.selectPrivateChannel(c.id); - }} - > - - -
-
{getGroupDMName(c)}
-
{c.recipients.length + 1} Members
-
-
- )); + const entries = renderClickableGDMs(mutualGDms, onClose); return ( ); + }), + + renderDMPageList: ErrorBoundary.wrap(({ user, Divider, listStyle }: { user: User, Divider: JSX.Element, listStyle: string; }) => { + const mutualGDms = getMutualGroupDms(user.id); + if (mutualGDms.length === 0) return null; + + const header = getMutualGDMCountText(user); + + return ( + <> + {Divider} + { })} + /> + + ); }) }); diff --git a/src/plugins/noServerEmojis/index.ts b/src/plugins/noServerEmojis/index.ts index ed843769c..6a39f55ca 100644 --- a/src/plugins/noServerEmojis/index.ts +++ b/src/plugins/noServerEmojis/index.ts @@ -36,7 +36,7 @@ export default definePlugin({ } ], shouldSkip(guildId: string, emoji: any) { - if (emoji.type !== "GUILD_EMOJI") { + if (emoji.type !== 1) { return false; } if (settings.store.shownEmojis === "onlyUnicode") { diff --git a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx index 963750fa3..82925d610 100644 --- a/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx +++ b/src/plugins/permissionsViewer/components/RolesAndUsersPermissions.tsx @@ -21,8 +21,10 @@ import { Flex } from "@components/Flex"; import { InfoIcon, OwnerCrownIcon } from "@components/Icons"; import { getUniqueUsername } from "@utils/discord"; import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { Clipboard, ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildStore, i18n, Menu, PermissionsBits, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common"; -import type { Guild } from "discord-types/general"; +import { findByCodeLazy } from "@webpack"; +import { Clipboard, ContextMenuApi, FluxDispatcher, GuildMemberStore, GuildStore, i18n, Menu, PermissionsBits, ScrollerThin, Text, Tooltip, useEffect, UserStore, useState, useStateFromStores } from "@webpack/common"; +import { UnicodeEmoji } from "@webpack/types"; +import type { Guild, Role, User } from "discord-types/general"; import { settings } from ".."; import { cl, getPermissionDescription, getPermissionString } from "../utils"; @@ -42,15 +44,15 @@ export interface RoleOrUserPermission { overwriteDeny?: bigint; } -function openRolesAndUsersPermissionsModal(permissions: Array, guild: Guild, header: string) { - return openModal(modalProps => ( - - )); +type GetRoleIconData = (role: Role, size: number) => { customIconSrc?: string; unicodeEmoji?: UnicodeEmoji; }; +const getRoleIconData: GetRoleIconData = findByCodeLazy("convertSurrogateToName", "customIconSrc", "unicodeEmoji"); + +function getRoleIconSrc(role: Role) { + const icon = getRoleIconData(role, 20); + if (!icon) return; + + const { customIconSrc, unicodeEmoji } = icon; + return customIconSrc ?? unicodeEmoji?.url; } function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, header }: { permissions: Array; guild: Guild; modalProps: ModalProps; header: string; }) { @@ -86,31 +88,34 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea size={ModalSize.LARGE} > - {header} permissions: + {header} permissions: - + {!selectedItem && ( -
+
No permissions to display!
)} {selectedItem && ( -
-
+
+ {permissions.map((permission, index) => { - const user = UserStore.getUser(permission.id ?? ""); - const role = roles[permission.id ?? ""]; + const user: User | undefined = UserStore.getUser(permission.id ?? ""); + const role: Role | undefined = roles[permission.id ?? ""]; + const roleIconSrc = role != null ? getRoleIconSrc(role) : undefined; return ( - +
); })} -
-
+ +
+ {Object.entries(PermissionsBits).map(([permissionName, bit]) => ( -
-
+
+
{(() => { const { permissions, overwriteAllow, overwriteDeny } = selectedItem; @@ -192,11 +199,11 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
))} -
+
)} - + ); } @@ -208,7 +215,7 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str aria-label="Role Options" > { Clipboard.copy(roleId); @@ -217,14 +224,13 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str {(settings.store as any).unsafeViewAsRole && ( { const role = GuildStore.getRole(guild.id, roleId); if (!role) return; onClose(); - FluxDispatcher.dispatch({ type: "IMPERSONATE_UPDATE", guildId: guild.id, @@ -235,15 +241,14 @@ function RoleContextMenu({ guild, roleId, onClose }: { guild: Guild; roleId: str } } }); - } - } + }} /> )} ); } -function UserContextMenu({ userId, onClose }: { userId: string; onClose: () => void; }) { +function UserContextMenu({ userId }: { userId: string; }) { return ( v aria-label="User Options" > { Clipboard.copy(userId); @@ -263,4 +268,13 @@ function UserContextMenu({ userId, onClose }: { userId: string; onClose: () => v const RolesAndUsersPermissions = ErrorBoundary.wrap(RolesAndUsersPermissionsComponent); -export default openRolesAndUsersPermissionsModal; +export default function openRolesAndUsersPermissionsModal(permissions: Array, guild: Guild, header: string) { + return openModal(modalProps => ( + + )); +} diff --git a/src/plugins/permissionsViewer/components/UserPermissions.tsx b/src/plugins/permissionsViewer/components/UserPermissions.tsx index dc2aa6fa4..7c0858f10 100644 --- a/src/plugins/permissionsViewer/components/UserPermissions.tsx +++ b/src/plugins/permissionsViewer/components/UserPermissions.tsx @@ -29,6 +29,7 @@ import openRolesAndUsersPermissionsModal, { PermissionType, type RoleOrUserPermi interface UserPermission { permission: string; + roleName: string; roleColor: string; rolePosition: number; } @@ -45,8 +46,48 @@ const { RoleRootClasses, RoleClasses, RoleBorderClasses } = proxyLazyWebpack(() return { RoleRootClasses, RoleClasses, RoleBorderClasses }; }); +interface FakeRoleProps extends React.HTMLAttributes { + text: string; + color: string; +} + +function FakeRole({ text, color, ...props }: FakeRoleProps) { + return ( +
+
+ +
+
+ + {text} + +
+
+ ); +} + +interface GrantedByTooltipProps { + roleName: string; + roleColor: string; +} + +function GrantedByTooltip({ roleName, roleColor }: GrantedByTooltipProps) { + return ( + <> + Granted By + + + ); +} + function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { guild: Guild; guildMember: GuildMember; forceOpen?: boolean; }) { - const stns = settings.use(["permissionsSortOrder"]); + const { permissionsSortOrder } = settings.use(["permissionsSortOrder"]); const [rolePermissions, userPermissions] = useMemo(() => { const userPermissions: UserPermissions = []; @@ -67,6 +108,7 @@ function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { g const OWNER = i18n.Messages.GUILD_OWNER || "Server Owner"; userPermissions.push({ permission: OWNER, + roleName: "Owner", roleColor: "var(--primary-300)", rolePosition: Infinity }); @@ -75,10 +117,11 @@ function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { g sortUserRoles(userRoles); for (const [permission, bit] of Object.entries(PermissionsBits)) { - for (const { permissions, colorString, position } of userRoles) { + for (const { permissions, colorString, position, name } of userRoles) { if ((permissions & bit) === bit) { userPermissions.push({ permission: getPermissionString(permission), + roleName: name, roleColor: colorString || "var(--primary-300)", rolePosition: position }); @@ -91,7 +134,7 @@ function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { g userPermissions.sort((a, b) => b.rolePosition - a.rolePosition); return [rolePermissions, userPermissions]; - }, [stns.permissionsSortOrder]); + }, [permissionsSortOrder]); return ( settings.store.defaultPermissionsDropdownState = !state} defaultState={settings.store.defaultPermissionsDropdownState} buttons={[ - ( + {tooltipProps => ( - +
)} - ) + ]}> {userPermissions.length > 0 && (
- {userPermissions.map(({ permission, roleColor }) => ( -
-
- -
-
- - {permission} - -
-
+ {userPermissions.map(({ permission, roleColor, roleName }) => ( + } + tooltipClassName={cl("granted-by-container")} + tooltipContentClassName={cl("granted-by-content")} + > + {tooltipProps => ( + + )} + ))}
)} diff --git a/src/plugins/permissionsViewer/index.tsx b/src/plugins/permissionsViewer/index.tsx index 7c3967a37..ca28f845f 100644 --- a/src/plugins/permissionsViewer/index.tsx +++ b/src/plugins/permissionsViewer/index.tsx @@ -26,7 +26,7 @@ import { Devs } from "@utils/constants"; import { classes } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; -import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, Menu, PermissionsBits, Popout, TooltipContainer, UserStore } from "@webpack/common"; +import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, match, Menu, PermissionsBits, Popout, TooltipContainer, UserStore } from "@webpack/common"; import type { Guild, GuildMember } from "discord-types/general"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions"; @@ -54,12 +54,12 @@ export const settings = definePluginSettings({ options: [ { label: "Highest Role", value: PermissionsSortOrder.HighestRole, default: true }, { label: "Lowest Role", value: PermissionsSortOrder.LowestRole } - ], + ] }, defaultPermissionsDropdownState: { description: "Whether the permissions dropdown on user popouts should be open by default", type: OptionType.BOOLEAN, - default: false, + default: false } }); @@ -73,14 +73,12 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) { action={() => { const guild = GuildStore.getGuild(guildId); - let permissions: RoleOrUserPermission[]; - let header: string; - - switch (type) { - case MenuItemParentType.User: { + const { permissions, header } = match(type) + .returnType<{ permissions: RoleOrUserPermission[], header: string; }>() + .with(MenuItemParentType.User, () => { const member = GuildMemberStore.getMember(guildId, id!); - permissions = getSortedRoles(guild, member) + const permissions: RoleOrUserPermission[] = getSortedRoles(guild, member) .map(role => ({ type: PermissionType.Role, ...role @@ -93,37 +91,37 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) { }); } - header = member.nick ?? UserStore.getUser(member.userId).username; - - break; - } - - case MenuItemParentType.Channel: { + return { + permissions, + header: member.nick ?? UserStore.getUser(member.userId).username + }; + }) + .with(MenuItemParentType.Channel, () => { const channel = ChannelStore.getChannel(id!); - permissions = sortPermissionOverwrites(Object.values(channel.permissionOverwrites).map(({ id, allow, deny, type }) => ({ + const permissions = sortPermissionOverwrites(Object.values(channel.permissionOverwrites).map(({ id, allow, deny, type }) => ({ type: type as PermissionType, id, overwriteAllow: allow, overwriteDeny: deny })), guildId); - header = channel.name; - - break; - } - - default: { - permissions = Object.values(GuildStore.getRoles(guild.id)).map(role => ({ + return { + permissions, + header: channel.name + }; + }) + .otherwise(() => { + const permissions = Object.values(GuildStore.getRoles(guild.id)).map(role => ({ type: PermissionType.Role, ...role })); - header = guild.name; - - break; - } - } + return { + permissions, + header: guild.name + }; + }); openRolesAndUsersPermissionsModal(permissions, guild, header); }} @@ -133,32 +131,34 @@ function MenuItem(guildId: string, id?: string, type?: MenuItemParentType) { function makeContextMenuPatch(childId: string | string[], type?: MenuItemParentType): NavContextMenuPatchCallback { return (children, props) => { - if (!props) return; - if ((type === MenuItemParentType.User && !props.user) || (type === MenuItemParentType.Guild && !props.guild) || (type === MenuItemParentType.Channel && (!props.channel || !props.guild))) + if ( + !props || + (type === MenuItemParentType.User && !props.user) || + (type === MenuItemParentType.Guild && !props.guild) || + (type === MenuItemParentType.Channel && (!props.channel || !props.guild)) + ) { return; + } const group = findGroupChildrenByChildId(childId, children); - const item = (() => { - switch (type) { - case MenuItemParentType.User: - return MenuItem(props.guildId, props.user.id, type); - case MenuItemParentType.Channel: - return MenuItem(props.guild.id, props.channel.id, type); - case MenuItemParentType.Guild: - return MenuItem(props.guild.id); - default: - return null; - } - })(); + const item = match(type) + .with(MenuItemParentType.User, () => MenuItem(props.guildId, props.user.id, type)) + .with(MenuItemParentType.Channel, () => MenuItem(props.guild.id, props.channel.id, type)) + .with(MenuItemParentType.Guild, () => MenuItem(props.guild.id)) + .otherwise(() => null); + if (item == null) return; - if (group) - group.push(item); - else if (childId === "roles" && props.guildId) - // "roles" may not be present due to the member not having any roles. In that case, add it above "Copy ID" + if (group) { + return group.push(item); + } + + // "roles" may not be present due to the member not having any roles. In that case, add it above "Copy ID" + if (childId === "roles" && props.guildId) { children.splice(-1, 0, {item}); + } }; } diff --git a/src/plugins/permissionsViewer/styles.css b/src/plugins/permissionsViewer/styles.css index 0ef961e5a..0123f86e2 100644 --- a/src/plugins/permissionsViewer/styles.css +++ b/src/plugins/permissionsViewer/styles.css @@ -1,20 +1,6 @@ /* User Permissions Component */ -.vc-permviewer-userperms-title-container { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 10px; - margin-bottom: 6px; -} - -.vc-permviewer-userperms-btns-container { - display: flex; - align-items: center; -} - -.vc-permviewer-userperms-sortorder-btn { - all: unset; +.vc-permviewer-user-sortorder-btn { cursor: pointer; display: flex; align-items: center; @@ -23,27 +9,17 @@ height: 24px; } -.vc-permviewer-userperms-permdetails-btn { - all: unset; - cursor: pointer; - display: flex; - align-items: center; -} - -.vc-permviewer-userperms-toggleperms-btn { - all: unset; - cursor: pointer; - display: flex; - align-items: center; -} - /* RolesAndUsersPermissions Component */ -.vc-permviewer-perms-title { +.vc-permviewer-modal-content { + padding: 16px 4px 16px 16px; +} + +.vc-permviewer-modal-title { flex-grow: 1; } -.vc-permviewer-perms-no-perms { +.vc-permviewer-modal-no-perms { width: 100%; height: 100%; display: flex; @@ -52,101 +28,103 @@ text-align: center; } -.vc-permviewer-perms-container { - display: grid; - grid-template-columns: 1fr 2fr; - grid-template-areas: "list permissions"; - padding: 16px 0; +.vc-permviewer-modal-container { + width: 100%; + height: 100%; + display: flex; + gap: 8px; } -.vc-permviewer-perms-list { - grid-area: list; +.vc-permviewer-modal-list { display: flex; flex-direction: column; gap: 2px; - border-right: 2px solid var(--background-modifier-active); + padding-right: 8px; + width: 200px; } -.vc-permviewer-perms-list-item-btn { - all: unset; +.vc-permviewer-modal-list-item-btn { cursor: pointer; } -.vc-permviewer-perms-list-item { +.vc-permviewer-modal-list-item { display: flex; align-items: center; - padding: 8px 5px; - cursor: pointer; - width: 230px; + gap: 8px; + padding: 8px; border-radius: 5px; } -.vc-permviewer-perms-list-item:hover { +.vc-permviewer-modal-list-item:hover { background-color: var(--background-modifier-hover); } -.vc-permviewer-perms-list-item-active { +.vc-permviewer-modal-list-item-active { background-color: var(--background-modifier-selected); } -.vc-permviewer-perms-list-item > div { +.vc-permviewer-modal-list-item > div { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } -.vc-permviewer-perms-role-circle { +.vc-permviewer-modal-role-circle { border-radius: 50%; width: 12px; height: 12px; - margin-left: 3px; - margin-right: 11px; flex-shrink: 0; } -.vc-permviewer-perms-user-img { +.vc-permviewer-modal-role-image { + width: 20px; + height: 20px; + object-fit: contain; +} + +.vc-permviewer-modal-user-img { border-radius: 50%; width: 20px; height: 20px; - margin-right: 6px; } -.vc-permviewer-perms-perms { - grid-area: permissions; +.vc-permviewer-modal-divider { + width: 2px; + background-color: var(--background-modifier-active); +} + +.vc-permviewer-modal-perms { display: flex; flex-direction: column; - margin-left: 5px; + padding-right: 8px; } -.vc-permviewer-perms-perms-item { - position: relative; +.vc-permviewer-modal-perms-item { display: flex; align-items: center; - padding: 10px; + gap: 5px; + padding: 10px 2px 10px 10px; border-bottom: 2px solid var(--background-modifier-active); } -.vc-permviewer-perms-perms-item:last-child { +.vc-permviewer-modal-perms-item:last-child { border: 0; } -.vc-permviewer-perms-perms-item-icon { +.vc-permviewer-modal-perms-item-icon { border: 1px solid var(--background-modifier-selected); width: 24px; height: 24px; - margin-right: 5px; } -.vc-permviewer-perms-perms-item .vc-info-icon { +.vc-permviewer-modal-perms-item .vc-info-icon { color: var(--interactive-muted); + margin-left: auto; cursor: pointer; - position: absolute; - right: 0; - scale: 0.9; transition: color ease-in 0.1s; } -.vc-permviewer-perms-perms-item .vc-info-icon:hover { +.vc-permviewer-modal-perms-item .vc-info-icon:hover { color: var(--interactive-active); } @@ -167,3 +145,14 @@ background: rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-6)); border-color: var(--profile-body-border-color) } + +.vc-permviewer-granted-by-container { + max-width: 300px; + width: auto; +} + +.vc-permviewer-granted-by-content { + display: flex; + align-items: center; + gap: 4px; +} diff --git a/src/plugins/pronoundb/index.ts b/src/plugins/pronoundb/index.ts index b0c5bfe6f..7dfa8cb49 100644 --- a/src/plugins/pronoundb/index.ts +++ b/src/plugins/pronoundb/index.ts @@ -57,7 +57,7 @@ export default definePlugin({ }, { match: /text:\i\.\i.Messages.USER_PROFILE_PRONOUNS/, - replace: '$&+vcHasPendingPronouns?"":` (${vcPronounSource})`' + replace: '$&+(vcHasPendingPronouns?"":` (${vcPronounSource})`)' }, { match: /(\.pronounsText.+?children:)(\i)/, diff --git a/src/plugins/reviewDB/components/ReviewModal.tsx b/src/plugins/reviewDB/components/ReviewModal.tsx index e12a98acd..71ac021f0 100644 --- a/src/plugins/reviewDB/components/ReviewModal.tsx +++ b/src/plugins/reviewDB/components/ReviewModal.tsx @@ -22,12 +22,13 @@ import { useForceUpdater } from "@utils/react"; import { Paginator, Text, useRef, useState } from "@webpack/common"; import { Auth } from "../auth"; +import { ReviewType } from "../entities"; import { Response, REVIEWS_PER_PAGE } from "../reviewDbApi"; import { cl } from "../utils"; import ReviewComponent from "./ReviewComponent"; import ReviewsView, { ReviewsInputComponent } from "./ReviewsView"; -function Modal({ modalProps, modalKey, discordId, name }: { modalProps: any; modalKey: string, discordId: string; name: string; }) { +function Modal({ modalProps, modalKey, discordId, name, type }: { modalProps: any; modalKey: string, discordId: string; name: string; type: ReviewType; }) { const [data, setData] = useState(); const [signal, refetch] = useForceUpdater(true); const [page, setPage] = useState(1); @@ -58,6 +59,7 @@ function Modal({ modalProps, modalKey, discordId, name }: { modalProps: any; mod onFetchReviews={setData} scrollToTop={() => ref.current?.scrollTo({ top: 0, behavior: "smooth" })} hideOwnReview + type={type} />
@@ -95,7 +97,7 @@ function Modal({ modalProps, modalKey, discordId, name }: { modalProps: any; mod ); } -export function openReviewsModal(discordId: string, name: string) { +export function openReviewsModal(discordId: string, name: string, type: ReviewType) { const modalKey = "vc-rdb-modal-" + Date.now(); openModal(props => ( @@ -104,6 +106,7 @@ export function openReviewsModal(discordId: string, name: string) { modalProps={props} discordId={discordId} name={name} + type={type} /> ), { modalKey }); } diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx index 76a0be475..7a7d8d020 100644 --- a/src/plugins/reviewDB/components/ReviewsView.tsx +++ b/src/plugins/reviewDB/components/ReviewsView.tsx @@ -21,7 +21,7 @@ import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpa import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common"; import { Auth, authorize } from "../auth"; -import { Review } from "../entities"; +import { Review, ReviewType } from "../entities"; import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "../reviewDbApi"; import { settings } from "../settings"; import { cl, showToast } from "../utils"; @@ -45,6 +45,7 @@ interface Props extends UserProps { page?: number; scrollToTop?(): void; hideOwnReview?: boolean; + type: ReviewType; } export default function ReviewsView({ @@ -56,6 +57,7 @@ export default function ReviewsView({ page = 1, showInput = false, hideOwnReview = false, + type, }: Props) { const [signal, refetch] = useForceUpdater(true); @@ -80,6 +82,7 @@ export default function ReviewsView({ reviews={reviewData!.reviews} hideOwnReview={hideOwnReview} profileId={discordId} + type={type} /> {showInput && ( @@ -94,7 +97,7 @@ export default function ReviewsView({ ); } -function ReviewList({ refetch, reviews, hideOwnReview, profileId }: { refetch(): void; reviews: Review[]; hideOwnReview: boolean; profileId: string; }) { +function ReviewList({ refetch, reviews, hideOwnReview, profileId, type }: { refetch(): void; reviews: Review[]; hideOwnReview: boolean; profileId: string; type: ReviewType; }) { const myId = UserStore.getCurrentUser().id; return ( @@ -111,7 +114,7 @@ function ReviewList({ refetch, reviews, hideOwnReview, profileId }: { refetch(): {reviews?.length === 0 && ( - Looks like nobody reviewed this user yet. You could be the first! + Looks like nobody reviewed this {type === ReviewType.User ? "user" : "server"} yet. You could be the first! )}
diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index caf9bacba..1164a2c5b 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -30,7 +30,7 @@ import { Guild, User } from "discord-types/general"; import { Auth, initAuth, updateAuth } from "./auth"; import { openReviewsModal } from "./components/ReviewModal"; -import { NotificationType } from "./entities"; +import { NotificationType, ReviewType } from "./entities"; import { getCurrentUserInfo, readNotification } from "./reviewDbApi"; import { settings } from "./settings"; import { showToast } from "./utils"; @@ -44,7 +44,7 @@ const guildPopoutPatch: NavContextMenuPatchCallback = (children, { guild }: { gu label="View Reviews" id="vc-rdb-server-reviews" icon={OpenExternalIcon} - action={() => openReviewsModal(guild.id, guild.name)} + action={() => openReviewsModal(guild.id, guild.name, ReviewType.Server)} /> ); }; @@ -56,7 +56,7 @@ const userContextPatch: NavContextMenuPatchCallback = (children, { user }: { use label="View Reviews" id="vc-rdb-user-reviews" icon={OpenExternalIcon} - action={() => openReviewsModal(user.id, user.username)} + action={() => openReviewsModal(user.id, user.username, ReviewType.User)} /> ); }; @@ -157,7 +157,7 @@ export default definePlugin({ return (