Merge remote-tracking branch 'upstream/dev' into managed-styles-rewrite (css lag fix)

This commit is contained in:
Sqaaakoi 2025-02-02 13:59:46 +13:00
commit 330532c0d4
No known key found for this signature in database
116 changed files with 1074 additions and 1152 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.11.0", "version": "1.11.3",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": { "bugs": {

View file

@ -9,7 +9,7 @@ import "./ChatButton.css";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { waitFor } from "@webpack"; import { waitFor } from "@webpack";
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common"; import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common";
import { Channel } from "discord-types/general"; import { Channel } from "discord-types/general";
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react"; import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
@ -110,7 +110,7 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
<Button <Button
aria-label={props.tooltip} aria-label={props.tooltip}
size="" size=""
look={ButtonLooks.BLANK} look={Button.Looks.BLANK}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`} innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}

View file

@ -122,7 +122,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
} }
interface ContextMenuProps { interface ContextMenuProps {
contextMenuApiArguments?: Array<any>; contextMenuAPIArguments?: Array<any>;
navId: string; navId: string;
children: Array<ReactElement<any> | null>; children: Array<ReactElement<any> | null>;
"aria-label": string; "aria-label": string;
@ -136,7 +136,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
children: cloneMenuChildren(props.children), children: cloneMenuChildren(props.children),
}; };
props.contextMenuApiArguments ??= []; props.contextMenuAPIArguments ??= [];
const contextMenuPatches = navPatches.get(props.navId); const contextMenuPatches = navPatches.get(props.navId);
if (!Array.isArray(props.children)) props.children = [props.children]; if (!Array.isArray(props.children)) props.children = [props.children];
@ -144,7 +144,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
if (contextMenuPatches) { if (contextMenuPatches) {
for (const patch of contextMenuPatches) { for (const patch of contextMenuPatches) {
try { try {
patch(props.children, ...props.contextMenuApiArguments); patch(props.children, ...props.contextMenuAPIArguments);
} catch (err) { } catch (err) {
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err); ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
} }
@ -153,7 +153,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
for (const patch of globalPatches) { for (const patch of globalPatches) {
try { try {
patch(props.navId, props.children, ...props.contextMenuApiArguments); patch(props.navId, props.children, ...props.contextMenuAPIArguments);
} catch (err) { } catch (err) {
ContextMenuLogger.error("Global patch errored,", err); ContextMenuLogger.error("Global patch errored,", err);
} }

View file

@ -220,6 +220,17 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
} }
} }
export function migratePluginSetting(pluginName: string, oldSetting: string, newSetting: string) {
const settings = SettingsStore.plain.plugins[pluginName];
if (!settings) return;
if (!Object.hasOwn(settings, oldSetting) || Object.hasOwn(settings, newSetting)) return;
settings[newSetting] = settings[oldSetting];
delete settings[oldSetting];
SettingsStore.markAsChanged();
}
export function definePluginSettings< export function definePluginSettings<
Def extends SettingsDefinition, Def extends SettingsDefinition,
Checks extends SettingsChecks<Def>, Checks extends SettingsChecks<Def>,

View file

@ -37,6 +37,7 @@ import { Constructor } from "type-fest";
import { PluginMeta } from "~plugins"; import { PluginMeta } from "~plugins";
import { import {
ISettingCustomElementProps,
ISettingElementProps, ISettingElementProps,
SettingBooleanComponent, SettingBooleanComponent,
SettingCustomComponent, SettingCustomComponent,
@ -74,7 +75,7 @@ function makeDummyUser(user: { username: string; id?: string; avatar?: string; }
return newUser; return newUser;
} }
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any>>> = { const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any> | ISettingCustomElementProps<any>>> = {
[OptionType.STRING]: SettingTextComponent, [OptionType.STRING]: SettingTextComponent,
[OptionType.NUMBER]: SettingNumericComponent, [OptionType.NUMBER]: SettingNumericComponent,
[OptionType.BIGINT]: SettingNumericComponent, [OptionType.BIGINT]: SettingNumericComponent,

View file

@ -18,8 +18,8 @@
import { PluginOptionComponent } from "@utils/types"; import { PluginOptionComponent } from "@utils/types";
import { ISettingElementProps } from "."; import { ISettingCustomElementProps } from ".";
export function SettingCustomComponent({ option, onChange, onError }: ISettingElementProps<PluginOptionComponent>) { export function SettingCustomComponent({ option, onChange, onError }: ISettingCustomElementProps<PluginOptionComponent>) {
return option.component({ setValue: onChange, setError: onError, option }); return option.component({ setValue: onChange, setError: onError, option });
} }

View file

@ -18,7 +18,7 @@
import { DefinedSettings, PluginOptionBase } from "@utils/types"; import { DefinedSettings, PluginOptionBase } from "@utils/types";
export interface ISettingElementProps<T extends PluginOptionBase> { interface ISettingElementPropsBase<T> {
option: T; option: T;
onChange(newValue: any): void; onChange(newValue: any): void;
pluginSettings: { pluginSettings: {
@ -30,6 +30,9 @@ export interface ISettingElementProps<T extends PluginOptionBase> {
definedSettings?: DefinedSettings; definedSettings?: DefinedSettings;
} }
export type ISettingElementProps<T extends PluginOptionBase> = ISettingElementPropsBase<T>;
export type ISettingCustomElementProps<T extends Omit<PluginOptionBase, "description" | "placeholder">> = ISettingElementPropsBase<T>;
export * from "../../Badge"; export * from "../../Badge";
export * from "./SettingBooleanComponent"; export * from "./SettingBooleanComponent";
export * from "./SettingCustomComponent"; export * from "./SettingCustomComponent";

View file

@ -69,7 +69,7 @@ function ReloadRequiredCard({ required }: { required: boolean; }) {
<Forms.FormText className={cl("dep-text")}> <Forms.FormText className={cl("dep-text")}>
Restart now to apply new plugins and their settings Restart now to apply new plugins and their settings
</Forms.FormText> </Forms.FormText>
<Button onClick={() => location.reload()}> <Button onClick={() => location.reload()} className={cl("restart-button")}>
Restart Restart
</Button> </Button>
</> </>
@ -158,8 +158,8 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
className={classes(ButtonClasses.button, cl("info-button"))} className={classes(ButtonClasses.button, cl("info-button"))}
> >
{plugin.options && !isObjectEmpty(plugin.options) {plugin.options && !isObjectEmpty(plugin.options)
? <CogWheel /> ? <CogWheel className={cl("info-icon")} />
: <InfoIcon />} : <InfoIcon className={cl("info-icon")} />}
</button> </button>
} }
/> />

View file

@ -63,10 +63,7 @@
height: 8em; height: 8em;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} gap: 0.25em;
.vc-plugins-info-card div {
line-height: 32px;
} }
.vc-plugins-restart-card { .vc-plugins-restart-card {
@ -76,11 +73,11 @@
color: var(--info-warning-text); color: var(--info-warning-text);
} }
.vc-plugins-restart-card button { .vc-plugins-restart-button {
margin-top: 0.5em; margin-top: 0.5em;
background: var(--info-warning-foreground) !important; background: var(--info-warning-foreground) !important;
} }
.vc-plugins-info-button svg:not(:hover, :focus) { .vc-plugins-info-icon:not(:hover, :focus) {
color: var(--text-muted); color: var(--text-muted);
} }

View file

@ -62,14 +62,21 @@ async function runReporter() {
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail"); if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
} catch (e) { } catch (e) {
let logMessage = searchType; let logMessage = searchType;
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`; if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`; if (args[0].$$vencordProps != null) {
else if (method === "mapMangledModule") { logMessage += `(${args[0].$$vencordProps.map(arg => `"${arg}"`).join(", ")})`;
} else {
logMessage += `(${args[0].toString().slice(0, 147)}...)`;
}
} else if (method === "extractAndLoadChunks") {
logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
} else if (method === "mapMangledModule") {
const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null); const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null);
logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`; logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`;
} else {
logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
} }
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
ReporterLogger.log("Webpack Find Fail:", logMessage); ReporterLogger.log("Webpack Find Fail:", logMessage);
} }

View file

@ -28,7 +28,7 @@ import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { isPluginDev } from "@utils/misc"; import { isPluginDev } from "@utils/misc";
import { closeModal, Modals, openModal } from "@utils/modal"; import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Forms, Toasts, UserStore } from "@webpack/common"; import { Forms, Toasts, UserStore } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
@ -144,8 +144,8 @@ export default definePlugin({
closeModal(modalKey); closeModal(modalKey);
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated"); VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
}}> }}>
<Modals.ModalRoot {...props}> <ModalRoot {...props}>
<Modals.ModalHeader> <ModalHeader>
<Flex style={{ width: "100%", justifyContent: "center" }}> <Flex style={{ width: "100%", justifyContent: "center" }}>
<Forms.FormTitle <Forms.FormTitle
tag="h2" tag="h2"
@ -159,8 +159,8 @@ export default definePlugin({
Vencord Donor Vencord Donor
</Forms.FormTitle> </Forms.FormTitle>
</Flex> </Flex>
</Modals.ModalHeader> </ModalHeader>
<Modals.ModalContent> <ModalContent>
<Flex> <Flex>
<img <img
role="presentation" role="presentation"
@ -183,13 +183,13 @@ export default definePlugin({
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!! Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
</Forms.FormText> </Forms.FormText>
</div> </div>
</Modals.ModalContent> </ModalContent>
<Modals.ModalFooter> <ModalFooter>
<Flex style={{ width: "100%", justifyContent: "center" }}> <Flex style={{ width: "100%", justifyContent: "center" }}>
<DonateButton /> <DonateButton />
</Flex> </Flex>
</Modals.ModalFooter> </ModalFooter>
</Modals.ModalRoot> </ModalRoot>
</ErrorBoundary> </ErrorBoundary>
)); ));
}, },

View file

@ -12,11 +12,16 @@ export default definePlugin({
description: "API to add buttons to the chat input", description: "API to add buttons to the chat input",
authors: [Devs.Ven], authors: [Devs.Ven],
patches: [{ patches: [
find: '"sticker")', {
replacement: { find: '"sticker")',
match: /return\(!\i\.\i&&(?=\(\i\.isDM.+?(\i)\.push\(.{0,50}"gift")/, replacement: {
replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)&&" // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
match: /return\((!)?\i\.\i(?:\|\||&&)(?=\(\i\.isDM.+?(\i)\.push)/,
replace: (m, not, children) => not
? `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),true)&&`
: `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),false)||`
}
} }
}] ]
}); });

View file

@ -34,12 +34,22 @@ export default definePlugin({
} }
}, },
{ {
find: ".Menu,{", find: "navId:",
all: true, all: true,
replacement: { noWarn: true,
match: /Menu,{(?<=\.jsxs?\)\(\i\.Menu,{)/g, replacement: [
replace: "$&contextMenuApiArguments:typeof arguments!=='undefined'?arguments:[]," {
} match: /navId:(?=.+?([,}].*?\)))/g,
replace: (m, rest) => {
// Check if this navId: match is a destructuring statement, ignore it if it is
const destructuringMatch = rest.match(/}=.+/);
if (destructuringMatch == null) {
return `contextMenuAPIArguments:typeof arguments!=='undefined'?arguments:[],${m}`;
}
return m;
}
}
]
} }
] ]
}); });

View file

@ -0,0 +1,68 @@
/*
* 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";
// 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',
replacement: {
match: /function.{0,80}type===(\i\.\i)\).{0,50}navigable:.+?Menu API/s,
replace: (m, mod) => {
const nameAssignments = [] as string[];
// if (t.type === m.MenuItem)
const typeCheckRe = canonicalizeMatch(/\(\i\.type===(\i\.\i)\)/g);
// push({type:"item"})
const pushTypeRe = /type:"(\w+)"/g;
let typeMatch: RegExpExecArray | null;
// for each if (t.type === ...)
while ((typeMatch = typeCheckRe.exec(m)) !== null) {
// extract the current menu item
const item = 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];
nameAssignments.push(`Object.defineProperty(${item},"name",{value:"${name}"})`);
}
}
if (nameAssignments.length < 6) {
console.warn("[MenuItemDemanglerAPI] Expected to at least remap 6 items, only remapped", nameAssignments.length);
}
// Merge all our redefines with the actual module
return `${nameAssignments.join(";")};${m}`;
},
},
},
],
});

View file

@ -37,12 +37,9 @@ export default definePlugin({
{ {
find: ".handleSendMessage,onResize", find: ".handleSendMessage,onResize",
replacement: { replacement: {
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); // https://regex101.com/r/hBlXpl/1
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid) match: /let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);(?<=\)\(({.+?})\)\.then.+?)/,
match: /(\{openWarningPopout:.{0,100}type:this.props.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);)(?<=\)\(({.+?})\)\.then.+?)/, replace: (m, parsedMessage, channel, replyOptions, extra) => m +
// props.chatInputType...then((async function(isMessageValid)... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); if(await Vencord.api...) return { shoudClear:true, shouldRefocus:true };
replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" +
`${rest1}async ${rest2}` +
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` + `if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
"return{shouldClear:false,shouldRefocus:true};" "return{shouldClear:false,shouldRefocus:true};"
} }
@ -52,8 +49,7 @@ export default definePlugin({
replacement: { replacement: {
match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/, match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
replace: (m, message, channel, event) => replace: (m, message, channel, event) =>
// the message param is shadowed by the event param, so need to alias them `const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg,vcChan,${event});`
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg, vcChan, ${event});`
} }
} }
] ]

View file

@ -65,7 +65,8 @@ export default definePlugin({
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}` replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
}, },
{ {
match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/, // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?(?:function\(\){return |\(\)=>))\2/,
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})` replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
} }
] ]

View file

@ -43,8 +43,8 @@ export default definePlugin({
// Status emojis // Status emojis
find: "#{intl::GUILD_OWNER}),children:", find: "#{intl::GUILD_OWNER}),children:",
replacement: { replacement: {
match: /(?<=\.activityEmoji,.+?animate:)\i/, match: /(\.CUSTOM_STATUS.+?animate:)\i/,
replace: "!0" replace: (_, rest) => `${rest}!0`
} }
}, },
{ {

View file

@ -17,14 +17,13 @@
*/ */
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { useStateFromStores } from "@webpack/common"; import { Animations, useStateFromStores } from "@webpack/common";
import type { CSSProperties } from "react"; import type { CSSProperties } from "react";
import { ExpandedGuildFolderStore, settings } from "."; import { ExpandedGuildFolderStore, settings } from ".";
const ChannelRTCStore = findStoreLazy("ChannelRTCStore"); const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
const Animations = findByPropsLazy("a", "animated", "useTransition");
const GuildsBar = findComponentByCodeLazy('("guildsnav")'); const GuildsBar = findComponentByCodeLazy('("guildsnav")');
export default ErrorBoundary.wrap(guildsBarProps => { export default ErrorBoundary.wrap(guildsBarProps => {

View file

@ -173,8 +173,8 @@ export default definePlugin({
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar // Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
{ {
predicate: () => !settings.store.keepIcons, predicate: () => !settings.store.keepIcons,
match: /(?<=#{intl::SERVER_FOLDER_PLACEHOLDER}.+?useTransition\)\()/, match: /(?=,\{from:\{height)/,
replace: "$self.shouldShowTransition(arguments[0])&&" replace: "&&$self.shouldShowTransition(arguments[0])"
}, },
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded // If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
{ {

View file

@ -83,7 +83,7 @@ export default definePlugin({
if (!role) return; if (!role) return;
if (role.colorString) { if (role.colorString) {
children.push( children.unshift(
<Menu.MenuItem <Menu.MenuItem
id="vc-copy-role-color" id="vc-copy-role-color"
label="Copy Role Color" label="Copy Role Color"
@ -93,6 +93,20 @@ export default definePlugin({
); );
} }
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
children.unshift(
<Menu.MenuItem
id="vc-edit-role"
label="Edit Role"
action={async () => {
await GuildSettingsActions.open(guild.id, "ROLES");
GuildSettingsActions.selectRole(id);
}}
icon={PencilIcon}
/>
);
}
if (role.icon) { if (role.icon) {
children.push( children.push(
<Menu.MenuItem <Menu.MenuItem
@ -110,20 +124,6 @@ export default definePlugin({
); );
} }
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
children.push(
<Menu.MenuItem
id="vc-edit-role"
label="Edit Role"
action={async () => {
await GuildSettingsActions.open(guild.id, "ROLES");
GuildSettingsActions.selectRole(id);
}}
icon={PencilIcon}
/>
);
}
} }
} }
}); });

View file

@ -21,7 +21,7 @@ import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findExportedComponentLazy, findStoreLazy } from "@webpack"; import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Constants, React, RestAPI, Tooltip } from "@webpack/common"; import { Constants, React, RestAPI, Tooltip } from "@webpack/common";
import { RenameButton } from "./components/RenameButton"; import { RenameButton } from "./components/RenameButton";
@ -34,7 +34,7 @@ const UserSettingsModal = findByPropsLazy("saveAccountChanges", "open");
const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer"); const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer");
const SessionIconClasses = findByPropsLazy("sessionIcon"); const SessionIconClasses = findByPropsLazy("sessionIcon");
const BlobMask = findExportedComponentLazy("BlobMask"); const BlobMask = findComponentByCodeLazy("!1,lowerBadgeSize:");
const settings = definePluginSettings({ const settings = definePluginSettings({
backgroundCheck: { backgroundCheck: {

View file

@ -101,8 +101,8 @@ export default definePlugin({
find: 'minimal:"contentColumnMinimal"', find: 'minimal:"contentColumnMinimal"',
replacement: [ replacement: [
{ {
match: /\(0,\i\.useTransition\)\((\i)/, match: /(?=\(0,\i\.\i\)\((\i),\{from:\{position:"absolute")/,
replace: "(_cb=>_cb(void 0,$1))||$&" replace: "(_cb=>_cb(void 0,$1))||"
}, },
{ {
match: /\i\.animated\.div/, match: /\i\.animated\.div/,

View file

@ -41,7 +41,7 @@ export default definePlugin({
settings: definePluginSettings({ settings: definePluginSettings({
blurAmount: { blurAmount: {
type: OptionType.NUMBER, type: OptionType.NUMBER,
description: "Blur Amount", description: "Blur Amount (in pixels)",
default: 10, default: 10,
onChange(v) { onChange(v) {
setStyleVariables(style, { blurAmount: v }); setStyleVariables(style, { blurAmount: v });

View file

@ -1,10 +1,9 @@
.vc-nsfw-img [class^="imageWrapper"] img, .vc-nsfw-img [class^="imageContainer"],
.vc-nsfw-img [class^="wrapperPaused"] video { .vc-nsfw-img [class^="wrapperPaused"] {
filter: blur([--blur-amount]px); filter: blur([--blur-amount]px);
transition: filter 0.2s; transition: filter 0.2s;
}
.vc-nsfw-img [class^="imageWrapper"]:hover img, &:hover {
.vc-nsfw-img [class^="wrapperPaused"]:hover video { filter: blur(0);
filter: unset; }
} }

View file

@ -1,29 +1,29 @@
.client-theme-settings { .vc-clientTheme-settings {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.client-theme-container { .vc-clientTheme-container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
} }
.client-theme-settings-labels { .vc-clientTheme-labels {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
} }
.client-theme-container > [class^="colorSwatch"] > [class^="swatch"] { .vc-clientTheme-container [class^="swatch"] {
border: thin solid var(--background-modifier-accent) !important; border: thin solid var(--background-modifier-accent) !important;
} }
.client-theme-warning * { .vc-clientTheme-warning-text {
color: var(--text-danger); color: var(--text-danger);
} }
.client-theme-contrast-warning { .vc-clientTheme-contrast-warning {
background-color: var(--background-primary); background-color: var(--background-primary);
padding: 0.5rem; padding: 0.5rem;
border-radius: .5rem; border-radius: .5rem;

View file

@ -7,7 +7,7 @@
import "./clientTheme.css"; import "./clientTheme.css";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { createStyle, deleteStyle } from "@api/Styles"; import { classNameFactory, createStyle, deleteStyle } from "@api/Styles";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
@ -15,6 +15,8 @@ import definePlugin, { OptionType, StartAt } from "@utils/types";
import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common"; import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common";
const cl = classNameFactory("vc-clientTheme-");
const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)"); const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
const colorPresets = [ const colorPresets = [
@ -61,9 +63,9 @@ function ThemeSettings() {
} }
return ( return (
<div className="client-theme-settings"> <div className={cl("settings")}>
<div className="client-theme-container"> <div className={cl("container")}>
<div className="client-theme-settings-labels"> <div className={cl("settings-labels")}>
<Forms.FormTitle tag="h3">Theme Color</Forms.FormTitle> <Forms.FormTitle tag="h3">Theme Color</Forms.FormTitle>
<Forms.FormText>Add a color to your Discord client theme</Forms.FormText> <Forms.FormText>Add a color to your Discord client theme</Forms.FormText>
</div> </div>
@ -77,10 +79,10 @@ function ThemeSettings() {
{(contrastWarning || nitroThemeEnabled) && (<> {(contrastWarning || nitroThemeEnabled) && (<>
<Forms.FormDivider className={classes(Margins.top8, Margins.bottom8)} /> <Forms.FormDivider className={classes(Margins.top8, Margins.bottom8)} />
<div className={`client-theme-contrast-warning ${contrastWarning ? (isLightTheme ? "theme-dark" : "theme-light") : ""}`}> <div className={`client-theme-contrast-warning ${contrastWarning ? (isLightTheme ? "theme-dark" : "theme-light") : ""}`}>
<div className="client-theme-warning"> <div className={cl("warning")}>
<Forms.FormText>Warning, your theme won't look good:</Forms.FormText> <Forms.FormText className={cl("warning-text")}>Warning, your theme won't look good:</Forms.FormText>
{contrastWarning && <Forms.FormText>Selected color won't contrast well with text</Forms.FormText>} {contrastWarning && <Forms.FormText className={cl("warning-text")}>Selected color won't contrast well with text</Forms.FormText>}
{nitroThemeEnabled && <Forms.FormText>Nitro themes aren't supported</Forms.FormText>} {nitroThemeEnabled && <Forms.FormText className={cl("warning-text")}>Nitro themes aren't supported</Forms.FormText>}
</div> </div>
{(contrastWarning && fixableContrast) && <Button onClick={() => setTheme(oppositeTheme)} color={Button.Colors.RED}>Switch to {oppositeTheme} mode</Button>} {(contrastWarning && fixableContrast) && <Button onClick={() => setTheme(oppositeTheme)} color={Button.Colors.RED}>Switch to {oppositeTheme} mode</Button>}
{(nitroThemeEnabled) && <Button onClick={() => setTheme(theme)} color={Button.Colors.RED}>Disable Nitro Theme</Button>} {(nitroThemeEnabled) && <Button onClick={() => setTheme(theme)} color={Button.Colors.RED}>Disable Nitro Theme</Button>}
@ -92,15 +94,12 @@ function ThemeSettings() {
const settings = definePluginSettings({ const settings = definePluginSettings({
color: { color: {
description: "Color your Discord client theme will be based around. Light mode isn't supported",
type: OptionType.COMPONENT, type: OptionType.COMPONENT,
default: "313338", default: "313338",
component: () => <ThemeSettings /> component: ThemeSettings
}, },
resetColor: { resetColor: {
description: "Reset Theme Color",
type: OptionType.COMPONENT, type: OptionType.COMPONENT,
default: "313338",
component: () => ( component: () => (
<Button onClick={() => onPickColor(0x313338)}> <Button onClick={() => onPickColor(0x313338)}>
Reset Theme Color Reset Theme Color

View file

@ -69,8 +69,8 @@ export default definePlugin({
{ {
find: "https://github.com/highlightjs/highlight.js/issues/2277", find: "https://github.com/highlightjs/highlight.js/issues/2277",
replacement: { replacement: {
match: /(?<=&&\()console.log\(`Deprecated.+?`\),/, match: /\(console.log\(`Deprecated.+?`\),/,
replace: "" replace: "("
} }
}, },
{ {
@ -95,10 +95,9 @@ export default definePlugin({
} }
}, },
{ {
find: 'console.warn("[DEPRECATED] Please use `subscribeWithSelector` middleware");', find: '"AppCrashedFatalReport: getLastCrash not supported."',
all: true,
replacement: { replacement: {
match: /console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\);/, match: /console\.log\("AppCrashedFatalReport: getLastCrash not supported\."\);/,
replace: "" replace: ""
} }
}, },

View file

@ -63,7 +63,7 @@ function makeShortcuts() {
default: default:
const uniqueMatches = [...new Set(matches)]; const uniqueMatches = [...new Set(matches)];
if (uniqueMatches.length > 1) if (uniqueMatches.length > 1)
console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches); console.warn(`Warning: This filter matches ${uniqueMatches.length} exports. Make it more specific!\n`, uniqueMatches);
return matches[0]; return matches[0];
} }
@ -165,11 +165,38 @@ function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) {
const currentVal = val.getter(); const currentVal = val.getter();
if (!currentVal || val.preload === false) return currentVal; if (!currentVal || val.preload === false) return currentVal;
const value = currentVal[SYM_LAZY_GET] function unwrapProxy(value: any) {
? forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED] if (value[SYM_LAZY_GET]) {
: currentVal; forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED];
} else if (value.$$vencordInternal) {
return forceLoad ? value.$$vencordInternal() : value;
}
if (value) define(window.shortcutList, key, { value }); return value;
}
const value = unwrapProxy(currentVal);
if (typeof value === "object" && value !== null) {
const descriptors = Object.getOwnPropertyDescriptors(value);
for (const propKey in descriptors) {
if (value[propKey] == null) continue;
const descriptor = descriptors[propKey];
if (descriptor.writable === true || descriptor.set != null) {
const currentValue = value[propKey];
const newValue = unwrapProxy(currentValue);
if (newValue != null && currentValue !== newValue) {
value[propKey] = newValue;
}
}
}
}
if (value != null) {
define(window.shortcutList, key, { value });
define(window, key, { value });
}
return value; return value;
} }

View file

@ -42,10 +42,11 @@ export default definePlugin({
// Only one of the two patches will be at effect; Discord often updates to switch between them. // Only one of the two patches will be at effect; Discord often updates to switch between them.
// See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673 // See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673
{ {
find: ".ENTER&&(!", find: ".selectPreviousCommandOption(",
replacement: { replacement: {
match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/, // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
replace: "$self.shouldSubmit($1, $2)" match: /(?<=(\i)\.which(?:!==|===)\i\.\i.ENTER(\|\||&&)).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=(?:\|\||&&)\(\i\.preventDefault)/,
replace: (_, event, condition, codeblock) => `${condition === "||" ? "!" : ""}$self.shouldSubmit(${event},${codeblock})`
} }
}, },
{ {

View file

@ -121,6 +121,7 @@ function DearrowButton({ component }: { component: Component<Props>; }) {
height="24px" height="24px"
viewBox="0 0 36 36" viewBox="0 0 36 36"
aria-label="Toggle Dearrow" aria-label="Toggle Dearrow"
className="vc-dearrow-icon"
> >
<path <path
fill="#1213BD" fill="#1213BD"

View file

@ -1,4 +1,4 @@
.vc-dearrow-toggle-off svg { .vc-dearrow-toggle-off .vc-dearrow-icon {
filter: grayscale(1); filter: grayscale(1);
} }

View file

@ -17,7 +17,6 @@ import DecorSection from "./ui/components/DecorSection";
export const settings = definePluginSettings({ export const settings = definePluginSettings({
changeDecoration: { changeDecoration: {
type: OptionType.COMPONENT, type: OptionType.COMPONENT,
description: "Change your avatar decoration",
component() { component() {
if (!Vencord.Plugins.plugins.Decor.started) return <Forms.FormText> if (!Vencord.Plugins.plugins.Decor.started) return <Forms.FormText>
Enable Decor and restart your client to change your avatar decoration. Enable Decor and restart your client to change your avatar decoration.

View file

@ -235,7 +235,7 @@ export default definePlugin({
} }
}, },
{ {
find: ".PREMIUM_LOCKED;", find: ".GUILD_SUBSCRIPTION_UNAVAILABLE;",
group: true, group: true,
predicate: () => settings.store.enableEmojiBypass, predicate: () => settings.store.enableEmojiBypass,
replacement: [ replacement: [
@ -256,8 +256,11 @@ export default definePlugin({
}, },
{ {
// Disallow the emoji for premium locked if the intention doesn't allow it // Disallow the emoji for premium locked if the intention doesn't allow it
match: /!\i\.\i\.canUseEmojisEverywhere\(\i\)/, // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
replace: m => `(${m}&&!${IS_BYPASSEABLE_INTENTION})` match: /(!)?(\i\.\i\.canUseEmojisEverywhere\(\i\))/,
replace: (m, not) => not
? `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
: `(${m}||${IS_BYPASSEABLE_INTENTION})`
}, },
{ {
// Allow animated emojis to be used if the intention allows it // Allow animated emojis to be used if the intention allows it

View file

@ -22,11 +22,11 @@ import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord"; import { getIntlMessage } from "@utils/discord";
import { NoopComponent } from "@utils/react"; import { NoopComponent } from "@utils/react";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { filters, findByPropsLazy, waitFor } from "@webpack"; import { filters, findByCodeLazy, waitFor } from "@webpack";
import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common"; import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common";
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
const { useMessageMenu } = findByPropsLazy("useMessageMenu"); const useMessageMenu = findByCodeLazy(".MESSAGE,commandTargetId:");
interface CopyIdMenuItemProps { interface CopyIdMenuItemProps {
id: string; id: string;

View file

@ -8,6 +8,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findComponentByCodeLazy } from "@webpack"; import { findComponentByCodeLazy } from "@webpack";
import { UserStore, useStateFromStores } from "@webpack/common";
import { ReactNode } from "react"; import { ReactNode } from "react";
const UserMentionComponent = findComponentByCodeLazy(".USER_MENTION)"); const UserMentionComponent = findComponentByCodeLazy(".USER_MENTION)");
@ -34,14 +35,19 @@ export default definePlugin({
} }
], ],
UserMentionComponent: ErrorBoundary.wrap((props: UserMentionComponentProps) => ( UserMentionComponent: ErrorBoundary.wrap((props: UserMentionComponentProps) => {
<UserMentionComponent const user = useStateFromStores([UserStore], () => UserStore.getUser(props.id));
if (user == null) {
return props.originalComponent();
}
return <UserMentionComponent
// This seems to be constant // This seems to be constant
className="mention" className="mention"
userId={props.id} userId={props.id}
channelId={props.channelId} channelId={props.channelId}
/> />;
), { }, {
fallback: ({ wrappedProps: { originalComponent } }) => originalComponent() fallback: ({ wrappedProps: { originalComponent } }) => originalComponent()
}) })
}); });

View file

@ -25,7 +25,7 @@ import { findComponentByCodeLazy } from "@webpack";
import style from "./style.css?managed"; import style from "./style.css?managed";
const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:"); const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON");
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!; const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;

View file

@ -16,78 +16,92 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import "./styles.css";
import { get, set } from "@api/DataStore"; import { get, set } from "@api/DataStore";
import { deleteStyle, setStyle } from "@api/Styles"; import { updateMessage } from "@api/MessageUpdater";
import { migratePluginSettings } from "@api/Settings";
import { ImageInvisible, ImageVisible } from "@components/Icons"; import { ImageInvisible, ImageVisible } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { ChannelStore } from "@webpack/common"; import { ChannelStore } from "@webpack/common";
import { MessageSnapshot } from "@webpack/types";
let style: HTMLStyleElement;
const KEY = "HideAttachments_HiddenIds"; const KEY = "HideAttachments_HiddenIds";
let hiddenMessages: Set<string> = new Set(); let hiddenMessages = new Set<string>();
const getHiddenMessages = () => get(KEY).then(set => {
hiddenMessages = set ?? new Set<string>(); async function getHiddenMessages() {
hiddenMessages = await get(KEY) ?? new Set();
return hiddenMessages; return hiddenMessages;
}); }
const saveHiddenMessages = (ids: Set<string>) => set(KEY, ids); const saveHiddenMessages = (ids: Set<string>) => set(KEY, ids);
migratePluginSettings("HideMedia", "HideAttachments");
export default definePlugin({ export default definePlugin({
name: "HideAttachments", name: "HideMedia",
description: "Hide attachments and Embeds for individual messages via hover button", description: "Hide attachments and embeds for individual messages via hover button",
authors: [Devs.Ven], authors: [Devs.Ven],
dependencies: ["MessageUpdaterAPI"],
patches: [{
find: "this.renderAttachments(",
replacement: {
match: /(?<=\i=)this\.render(?:Attachments|Embeds|StickersAccessories)\((\i)\)/g,
replace: "$self.shouldHide($1?.id)?null:$&"
}
}],
renderMessagePopoverButton(msg) { renderMessagePopoverButton(msg) {
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length) return null; // @ts-ignore - discord-types lags behind discord.
const hasAttachmentsInShapshots = msg.messageSnapshots.some(
(snapshot: MessageSnapshot) => snapshot?.message.attachments.length
);
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length && !hasAttachmentsInShapshots) return null;
const isHidden = hiddenMessages.has(msg.id); const isHidden = hiddenMessages.has(msg.id);
return { return {
label: isHidden ? "Show Attachments" : "Hide Attachments", label: isHidden ? "Show Media" : "Hide Media",
icon: isHidden ? ImageVisible : ImageInvisible, icon: isHidden ? ImageVisible : ImageInvisible,
message: msg, message: msg,
channel: ChannelStore.getChannel(msg.channel_id), channel: ChannelStore.getChannel(msg.channel_id),
onClick: () => this.toggleHide(msg.id) onClick: () => this.toggleHide(msg.channel_id, msg.id)
}; };
}, },
renderMessageAccessory({ message }) {
if (!this.shouldHide(message.id)) return null;
return (
<span className={classes("vc-hideAttachments-accessory", !message.content && "vc-hideAttachments-no-content")}>
Media Hidden
</span>
);
},
async start() { async start() {
await getHiddenMessages(); await getHiddenMessages();
await this.buildCss();
}, },
stop() { stop() {
deleteStyle("HideAttachments");
hiddenMessages.clear(); hiddenMessages.clear();
}, },
async buildCss() { shouldHide(messageId: string) {
const elements = [...hiddenMessages].map(id => `#message-accessories-${id}`).join(","); return hiddenMessages.has(messageId);
setStyle({
name: "HideAttachments",
source: `
:is(${elements}) :is([class*="embedWrapper"], [class*="clickableSticker"]) {
/* important is not necessary, but add it to make sure bad themes won't break it */
display: none !important;
}
:is(${elements})::after {
content: "Attachments hidden";
color: var(--text-muted);
font-size: 80%;
}
`,
enabled: hiddenMessages.size > 0
});
}, },
async toggleHide(id: string) { async toggleHide(channelId: string, messageId: string) {
const ids = await getHiddenMessages(); const ids = await getHiddenMessages();
if (!ids.delete(id)) if (!ids.delete(messageId))
ids.add(id); ids.add(messageId);
await saveHiddenMessages(ids); await saveHiddenMessages(ids);
await this.buildCss(); updateMessage(channelId, messageId);
} }
}); });

View file

@ -0,0 +1,10 @@
.vc-hideAttachments-accessory {
color: var(--text-muted);
margin-top: 0.5em;
font-style: italic;
font-weight: 400;
}
.vc-hideAttachments-no-content {
margin-top: 0;
}

View file

@ -27,7 +27,7 @@ export default definePlugin({
{ {
find: "hasFlag:{writable", find: "hasFlag:{writable",
replacement: { replacement: {
match: /if\((\i)<=(?:1<<30|1073741824)\)return/, match: /if\((\i)<=(?:0x40000000|(?:1<<30|1073741824))\)return/,
replace: "if($1===(1<<20))return false;$&", replace: "if($1===(1<<20))return false;$&",
}, },
}, },

View file

@ -73,8 +73,6 @@ function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>
const ignoredActivityIndex = settings.store.ignoredActivities.findIndex(act => act.id === activity.id); const ignoredActivityIndex = settings.store.ignoredActivities.findIndex(act => act.id === activity.id);
if (ignoredActivityIndex === -1) settings.store.ignoredActivities.push(activity); if (ignoredActivityIndex === -1) settings.store.ignoredActivities.push(activity);
else settings.store.ignoredActivities.splice(ignoredActivityIndex, 1); else settings.store.ignoredActivities.splice(ignoredActivityIndex, 1);
recalculateActivities();
} }
function recalculateActivities() { function recalculateActivities() {
@ -149,8 +147,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) {
const settings = definePluginSettings({ const settings = definePluginSettings({
importCustomRPC: { importCustomRPC: {
type: OptionType.COMPONENT, type: OptionType.COMPONENT,
description: "", component: ImportCustomRPCComponent
component: () => <ImportCustomRPCComponent />
}, },
listMode: { listMode: {
type: OptionType.SELECT, type: OptionType.SELECT,
@ -170,7 +167,6 @@ const settings = definePluginSettings({
}, },
idsList: { idsList: {
type: OptionType.COMPONENT, type: OptionType.COMPONENT,
description: "",
default: "", default: "",
onChange(newValue: string) { onChange(newValue: string) {
const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean)); const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean));
@ -245,7 +241,7 @@ export default definePlugin({
find: '"LocalActivityStore"', find: '"LocalActivityStore"',
replacement: [ replacement: [
{ {
match: /HANG_STATUS.+?(?=!\i\(\)\(\i,\i\)&&)(?<=(\i)\.push.+?)/, match: /\.LISTENING.+?(?=!?\i\(\)\(\i,\i\))(?<=(\i)\.push.+?)/,
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);` replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
} }
] ]

View file

@ -195,6 +195,7 @@ export const Magnifier = ErrorBoundary.wrap<MagnifierProps>(({ instance, size: i
/> />
) : ( ) : (
<img <img
className={cl("image")}
ref={imageRef} ref={imageRef}
style={{ style={{
position: "absolute", position: "absolute",

View file

@ -80,7 +80,12 @@ export const settings = definePluginSettings({
}); });
const imageContextMenuPatch: NavContextMenuPatchCallback = children => { const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
// Discord re-uses the image context menu for links to for the copy and open buttons
if ("href" in props) return;
// emojis in user statuses
if (props.target?.classList?.contains("emoji")) return;
const { square, nearestNeighbour } = settings.use(["square", "nearestNeighbour"]); const { square, nearestNeighbour } = settings.use(["square", "nearestNeighbour"]);
children.push( children.push(

View file

@ -18,7 +18,7 @@
border-radius: 0; border-radius: 0;
} }
.vc-imgzoom-nearest-neighbor>img { .vc-imgzoom-nearest-neighbor > .vc-imgzoom-image {
image-rendering: pixelated; image-rendering: pixelated;
/* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */ /* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */

View file

@ -50,9 +50,9 @@ export default definePlugin({
{ {
find: "#{intl::FRIENDS_SECTION_ONLINE}", find: "#{intl::FRIENDS_SECTION_ONLINE}",
replacement: { replacement: {
match: /(\(0,\i\.jsx\)\(\i\.TabBar\.Item,\{id:\i\.\i)\.BLOCKED,className:([^\s]+?)\.item,children:\i\.\i\.string\(\i\.\i#{intl::BLOCKED}\)\}\)/, match: /,{id:(\i\.\i)\.BLOCKED,show:.+?className:(\i\.item)/,
replace: "$1.IMPLICIT,className:$2.item,children:\"Implicit\"}),$&" replace: (rest, relationShipTypes, className) => `,{id:${relationShipTypes}.IMPLICIT,show:true,className:${className},content:"Implicit"}${rest}`
}, }
}, },
// Sections content // Sections content
{ {

View file

@ -142,14 +142,21 @@ for (const p of neededApiPlugins) {
for (const p of pluginsValues) { for (const p of pluginsValues) {
if (p.settings) { if (p.settings) {
p.settings.pluginName = p.name;
p.options ??= {}; p.options ??= {};
for (const [name, def] of Object.entries(p.settings.def)) {
p.settings.pluginName = p.name;
for (const name in p.settings.def) {
const def = p.settings.def[name];
const checks = p.settings.checks?.[name]; const checks = p.settings.checks?.[name];
p.options[name] = { ...def, ...checks }; p.options[name] = { ...def, ...checks };
}
}
if (def.onChange != null) { if (p.options) {
SettingsStore.addChangeListener(`plugins.${p.name}.${name}`, def.onChange); for (const name in p.options) {
const opt = p.options[name];
if (opt.onChange != null) {
SettingsStore.addChangeListener(`plugins.${p.name}.${name}`, opt.onChange);
} }
} }
} }

View file

@ -0,0 +1,17 @@
# IrcColors
Makes username colors in chat unique, like in IRC clients
![Chat with IrcColors and Compact++ enabled](https://github.com/Vendicated/Vencord/assets/33988779/88e05c0b-a60a-4d10-949e-8b46e1d7226c)
Improves chat readability by assigning every user an unique nickname color,
making distinguishing between different users easier. Inspired by the feature
in many IRC clients, such as HexChat or WeeChat.
Keep in mind this overrides role colors in chat, so if you wish to know
someone's role color without checking their profile, enable the role dot: go to
**User Settings**, **Accessibility** and switch **Role Colors** to **Show role
colors next to names**.
Created for use with the [Compact++](https://gitlab.com/Grzesiek11/compactplusplus-discord-theme)
theme.

View file

@ -0,0 +1,109 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { hash as h64 } from "@intrnl/xxhash64";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { useMemo } from "@webpack/common";
// Calculate a CSS color string based on the user ID
function calculateNameColorForUser(id?: string) {
const { lightness } = settings.use(["lightness"]);
const idHash = useMemo(() => id ? h64(id) : null, [id]);
return idHash && `hsl(${idHash % 360n}, 100%, ${lightness}%)`;
}
const settings = definePluginSettings({
lightness: {
description: "Lightness, in %. Change if the colors are too light or too dark",
type: OptionType.NUMBER,
default: 70,
},
memberListColors: {
description: "Replace role colors in the member list",
restartNeeded: true,
type: OptionType.BOOLEAN,
default: true
},
applyColorOnlyToUsersWithoutColor: {
description: "Apply colors only to users who don't have a predefined color",
restartNeeded: false,
type: OptionType.BOOLEAN,
default: false
},
applyColorOnlyInDms: {
description: "Apply colors only in direct messages; do not apply colors in servers.",
restartNeeded: false,
type: OptionType.BOOLEAN,
default: false
}
});
export default definePlugin({
name: "IrcColors",
description: "Makes username colors in chat unique, like in IRC clients",
authors: [Devs.Grzesiek11, Devs.jamesbt365],
settings,
patches: [
{
find: '="SYSTEM_TAG"',
replacement: {
match: /(?<=className:\i\.username,style:.{0,50}:void 0,)/,
replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])},"
}
},
{
find: "#{intl::GUILD_OWNER}),children:",
replacement: {
match: /(?<=\.MEMBER_LIST}\),\[\]\),)(.+?color:)null!=.{0,50}?(?=,)/,
replace: (_, rest) => `ircColor=$self.calculateNameColorForListContext(arguments[0]),${rest}ircColor`
},
predicate: () => settings.store.memberListColors
}
],
calculateNameColorForMessageContext(context: any) {
const id = context?.message?.author?.id;
const colorString = context?.author?.colorString;
const color = calculateNameColorForUser(id);
if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) {
return colorString;
}
return (!settings.store.applyColorOnlyToUsersWithoutColor || !colorString)
? color
: colorString;
},
calculateNameColorForListContext(context: any) {
const id = context?.user?.id;
const colorString = context?.colorString;
const color = calculateNameColorForUser(id);
if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) {
return colorString;
}
return (!settings.store.applyColorOnlyToUsersWithoutColor || !colorString)
? color
: colorString;
}
});

View file

@ -86,7 +86,7 @@ const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f";
const logger = new Logger("LastFMRichPresence"); const logger = new Logger("LastFMRichPresence");
const presenceStore = findByPropsLazy("getLocalPresence"); const PresenceStore = findByPropsLazy("getLocalPresence");
async function getApplicationAsset(key: string): Promise<string> { async function getApplicationAsset(key: string): Promise<string> {
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0]; return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
@ -124,6 +124,11 @@ const settings = definePluginSettings({
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
default: true, default: true,
}, },
hideWithActivity: {
description: "Hide Last.fm presence if you have any other presence",
type: OptionType.BOOLEAN,
default: false,
},
statusName: { statusName: {
description: "custom status text", description: "custom status text",
type: OptionType.STRING, type: OptionType.STRING,
@ -274,12 +279,16 @@ export default definePlugin({
}, },
async getActivity(): Promise<Activity | null> { async getActivity(): Promise<Activity | null> {
if (settings.store.hideWithActivity) {
if (PresenceStore.getActivities().some(a => a.application_id !== applicationId)) {
return null;
}
}
if (settings.store.hideWithSpotify) { if (settings.store.hideWithSpotify) {
for (const activity of presenceStore.getActivities()) { if (PresenceStore.getActivities().some(a => a.type === ActivityType.LISTENING && a.application_id !== applicationId)) {
if (activity.type === ActivityType.LISTENING && activity.application_id !== applicationId) { // there is already music status because of Spotify or richerCider (probably more)
// there is already music status because of Spotify or richerCider (probably more) return null;
return null;
}
} }
} }

View file

@ -57,7 +57,7 @@ export default definePlugin({
{ {
find: ".ROLE_MENTION)", find: ".ROLE_MENTION)",
replacement: { replacement: {
match: /children:\[\i&&.{0,50}\.RoleDot.{0,300},\i(?=\])/, match: /children:\[\i&&.{0,100}className:\i.roleDot,.{0,200},\i(?=\])/,
replace: "$&,$self.renderRoleIcon(arguments[0])" replace: "$&,$self.renderRoleIcon(arguments[0])"
} }
}], }],

View file

@ -9,7 +9,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { isNonNullish } from "@utils/guards"; import { isNonNullish } from "@utils/guards";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findExportedComponentLazy } from "@webpack"; import { findComponentByCodeLazy } from "@webpack";
import { SnowflakeUtils, Tooltip } from "@webpack/common"; import { SnowflakeUtils, Tooltip } from "@webpack/common";
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
@ -26,7 +26,7 @@ interface Diff {
} }
const DISCORD_KT_DELAY = 1471228928; const DISCORD_KT_DELAY = 1471228928;
const HiddenVisually = findExportedComponentLazy("HiddenVisually"); const HiddenVisually = findComponentByCodeLazy(".hiddenVisually]:");
export default definePlugin({ export default definePlugin({
name: "MessageLatency", name: "MessageLatency",
@ -162,7 +162,7 @@ export default definePlugin({
</> </>
} }
</Tooltip>; </Tooltip>;
}); }, { noop: true });
}, },
Icon({ delta, fill, props }: { Icon({ delta, fill, props }: {

View file

@ -120,11 +120,11 @@ const settings = definePluginSettings({
}, },
clearMessageCache: { clearMessageCache: {
type: OptionType.COMPONENT, type: OptionType.COMPONENT,
description: "Clear the linked message cache", component: () => (
component: () =>
<Button onClick={() => messageCache.clear()}> <Button onClick={() => messageCache.clear()}>
Clear the linked message cache Clear the linked message cache
</Button> </Button>
)
} }
}); });

View file

@ -1,24 +1,8 @@
/* Message content highlighting */ .messagelogger-deleted {
.messagelogger-deleted [class*="contents"] > :is(div, h1, h2, h3, p) { --text-normal: var(--status-danger, #f04747);
color: var(--status-danger, #f04747) !important; --interactive-normal: var(--status-danger, #f04747);
} --text-muted: var(--status-danger, #f04747);
--embed-title: var(--red-460, #be3535);
/* Markdown title highlighting */ --text-link: var(--red-460, #be3535);
.messagelogger-deleted [class*="contents"] :is(h1, h2, h3) { --header-primary: var(--red-460, #be3535);
color: var(--status-danger, #f04747) !important;
}
/* Bot "thinking" text highlighting */
.messagelogger-deleted [class*="colorStandard"] {
color: var(--status-danger, #f04747) !important;
}
/* Embed highlighting */
.messagelogger-deleted article :is(div, span, h1, h2, h3, p) {
color: var(--status-danger, #f04747) !important;
}
.messagelogger-deleted a {
color: var(--red-460, #be3535) !important;
text-decoration: underline;
} }

View file

@ -211,7 +211,8 @@ export default definePlugin({
collapseDeleted: { collapseDeleted: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Whether to collapse deleted messages, similar to blocked messages", description: "Whether to collapse deleted messages, similar to blocked messages",
default: false default: false,
restartNeeded: true,
}, },
logEdits: { logEdits: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
@ -441,15 +442,10 @@ export default definePlugin({
{ {
// Attachment renderer // Attachment renderer
find: ".removeMosaicItemHoverButton", find: ".removeMosaicItemHoverButton",
group: true,
replacement: [ replacement: [
{ {
match: /(className:\i,item:\i),/, match: /\[\i\.obscured\]:.+?,(?<=item:(\i).+?)/,
replace: "$1,item: deleted," replace: '$&"messagelogger-deleted-attachment":$1.originalItem?.deleted,'
},
{
match: /\[\i\.obscured\]:.+?,/,
replace: "$& 'messagelogger-deleted-attachment': deleted,"
} }
] ]
}, },
@ -500,7 +496,7 @@ export default definePlugin({
{ {
// Message context base menu // Message context base menu
find: "useMessageMenu:", find: ".MESSAGE,commandTargetId:",
replacement: [ replacement: [
{ {
// Remove the first section if message is deleted // Remove the first section if message is deleted

View file

@ -4,12 +4,12 @@
.messagelogger-deleted .messagelogger-deleted
:is( :is(
video, .messagelogger-deleted-attachment,
.emoji, .emoji,
[data-type="sticker"], [data-type="sticker"],
iframe, [class*="embedIframe"],
.messagelogger-deleted-attachment, [class*="embedSpotify"],
[class|="inlineMediaEmbed"] [class*="imageContainer"]
) { ) {
filter: grayscale(1) !important; filter: grayscale(1) !important;
transition: 150ms filter ease-in-out; transition: 150ms filter ease-in-out;
@ -17,18 +17,14 @@
&[class*="hiddenMosaicItem_"] { &[class*="hiddenMosaicItem_"] {
filter: grayscale(1) blur(var(--custom-message-attachment-spoiler-blur-radius, 44px)) !important; filter: grayscale(1) blur(var(--custom-message-attachment-spoiler-blur-radius, 44px)) !important;
} }
&:hover {
filter: grayscale(0) !important;
}
} }
.messagelogger-deleted .messagelogger-deleted [class*="spoilerWarning"] {
:is( color: var(--status-danger);
video,
.emoji,
[data-type="sticker"],
iframe,
.messagelogger-deleted-attachment,
[class|="inlineMediaEmbed"]
):hover {
filter: grayscale(0) !important;
} }
.theme-dark .messagelogger-edited { .theme-dark .messagelogger-edited {

View file

@ -89,7 +89,7 @@ export default definePlugin({
settings, settings,
async start() { async start() {
// TODO: Remove DataStore tags migration once enough time has passed // TODO(OptionType.CUSTOM Related): Remove DataStore tags migration once enough time has passed
const oldTags = await DataStore.get<Tag[]>(DATA_KEY); const oldTags = await DataStore.get<Tag[]>(DATA_KEY);
if (oldTags != null) { if (oldTags != null) {
// @ts-ignore // @ts-ignore

View file

@ -1,372 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findLazy } from "@webpack";
import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip } from "@webpack/common";
import type { Permissions, RC } from "@webpack/types";
import type { Channel, Guild, Message, User } from "discord-types/general";
interface Tag {
// name used for identifying, must be alphanumeric + underscores
name: string;
// name shown on the tag itself, can be anything probably; automatically uppercase'd
displayName: string;
description: string;
permissions?: Permissions[];
condition?(message: Message | null, user: User, channel: Channel): boolean;
}
interface TagSetting {
text: string;
showInChat: boolean;
showInNotChat: boolean;
}
interface TagSettings {
WEBHOOK: TagSetting,
OWNER: TagSetting,
ADMINISTRATOR: TagSetting,
MODERATOR_STAFF: TagSetting,
MODERATOR: TagSetting,
VOICE_MODERATOR: TagSetting,
TRIAL_MODERATOR: TagSetting,
[k: string]: TagSetting;
}
// PermissionStore.computePermissions will not work here since it only gets permissions for the current user
const computePermissions: (options: {
user?: { id: string; } | string | null;
context?: Guild | Channel | null;
overwrites?: Channel["permissionOverwrites"] | null;
checkElevated?: boolean /* = true */;
excludeGuildPermissions?: boolean /* = false */;
}) => bigint = findByCodeLazy(".getCurrentUser()", ".computeLurkerPermissionsAllowList()");
const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; };
const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot();
const tags: Tag[] = [
{
name: "WEBHOOK",
displayName: "Webhook",
description: "Messages sent by webhooks",
condition: isWebhook
}, {
name: "OWNER",
displayName: "Owner",
description: "Owns the server",
condition: (_, user, channel) => GuildStore.getGuild(channel?.guild_id)?.ownerId === user.id
}, {
name: "ADMINISTRATOR",
displayName: "Admin",
description: "Has the administrator permission",
permissions: ["ADMINISTRATOR"]
}, {
name: "MODERATOR_STAFF",
displayName: "Staff",
description: "Can manage the server, channels or roles",
permissions: ["MANAGE_GUILD", "MANAGE_CHANNELS", "MANAGE_ROLES"]
}, {
name: "MODERATOR",
displayName: "Mod",
description: "Can manage messages or kick/ban people",
permissions: ["MANAGE_MESSAGES", "KICK_MEMBERS", "BAN_MEMBERS"]
}, {
name: "VOICE_MODERATOR",
displayName: "VC Mod",
description: "Can manage voice chats",
permissions: ["MOVE_MEMBERS", "MUTE_MEMBERS", "DEAFEN_MEMBERS"]
}, {
name: "CHAT_MODERATOR",
displayName: "Chat Mod",
description: "Can timeout people",
permissions: ["MODERATE_MEMBERS"]
}
];
const defaultSettings = Object.fromEntries(
tags.map(({ name, displayName }) => [name, { text: displayName, showInChat: true, showInNotChat: true }])
) as TagSettings;
function SettingsComponent() {
const tagSettings = settings.store.tagSettings ??= defaultSettings;
return (
<Flex flexDirection="column">
{tags.map(t => (
<Card key={t.name} style={{ padding: "1em 1em 0" }}>
<Forms.FormTitle style={{ width: "fit-content" }}>
<Tooltip text={t.description}>
{({ onMouseEnter, onMouseLeave }) => (
<div
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{t.displayName} Tag <Tag type={Tag.Types[t.name]} />
</div>
)}
</Tooltip>
</Forms.FormTitle>
<TextInput
type="text"
value={tagSettings[t.name]?.text ?? t.displayName}
placeholder={`Text on tag (default: ${t.displayName})`}
onChange={v => tagSettings[t.name].text = v}
className={Margins.bottom16}
/>
<Switch
value={tagSettings[t.name]?.showInChat ?? true}
onChange={v => tagSettings[t.name].showInChat = v}
hideBorder
>
Show in messages
</Switch>
<Switch
value={tagSettings[t.name]?.showInNotChat ?? true}
onChange={v => tagSettings[t.name].showInNotChat = v}
hideBorder
>
Show in member list and profiles
</Switch>
</Card>
))}
</Flex>
);
}
const settings = definePluginSettings({
dontShowForBots: {
description: "Don't show extra tags for bots (excluding webhooks)",
type: OptionType.BOOLEAN
},
dontShowBotTag: {
description: "Only show extra tags for bots / Hide [BOT] text",
type: OptionType.BOOLEAN
},
tagSettings: {
type: OptionType.COMPONENT,
component: SettingsComponent,
description: "fill me"
}
});
export default definePlugin({
name: "MoreUserTags",
description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)",
authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN],
settings,
patches: [
// add tags to the tag list
{
find: ".ORIGINAL_POSTER=",
replacement: {
match: /(?=(\i)\[\i\.BOT)/,
replace: "$self.genTagTypes($1);"
}
},
{
find: "#{intl::DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP_OFFICIAL}",
replacement: [
// make the tag show the right text
{
match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=(.{0,40}#{intl::APP_TAG}\))/,
replace: (_, origSwitch, variant, tags, displayedText, originalText) =>
`${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}],${originalText})}`
},
// show OP tags correctly
{
match: /(\i)=(\i)===\i(?:\.\i)?\.ORIGINAL_POSTER/,
replace: "$1=$self.isOPTag($2)"
},
// add HTML data attributes (for easier theming)
{
match: /.botText,children:(\i)}\)]/,
replace: "$&,'data-tag':$1.toLowerCase()"
}
],
},
// in messages
{
find: ".Types.ORIGINAL_POSTER",
replacement: {
match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)/,
replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null"
}
},
// in the member list
{
find: "#{intl::GUILD_OWNER}),children:",
replacement: {
match: /(?<type>\i)=\(null==.{0,100}\.BOT;return null!=(?<user>\i)&&\i\.bot/,
replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }); return typeof $<type> === 'number'"
}
},
// pass channel id down props to be used in profiles
{
find: ".hasAvatarForGuild(null==",
replacement: {
match: /(?=usernameIcon:)/,
replace: "moreTags_channelId:arguments[0].channelId,"
}
},
{
find: "#{intl::USER_PROFILE_PRONOUNS}",
replacement: {
match: /(?=,hideBotTag:!0)/,
replace: ",moreTags_channelId:arguments[0].moreTags_channelId"
}
},
// in profiles
{
find: ",overrideDiscriminator:",
group: true,
replacement: [
{
// prevent channel id from getting ghosted
// it's either this or extremely long lookbehind
match: /user:\i,nick:\i,/,
replace: "$&moreTags_channelId,"
}, {
match: /,botType:(\i),botVerified:(\i),(?!discriminatorClass:)(?<=user:(\i).+?)/g,
replace: ",botType:$self.getTag({user:$3,channelId:moreTags_channelId,origType:$1,location:'not-chat'}),botVerified:$2,"
}
]
},
],
start() {
settings.store.tagSettings ??= defaultSettings;
// newly added field might be missing from old users
settings.store.tagSettings.CHAT_MODERATOR ??= {
text: "Chat Mod",
showInChat: true,
showInNotChat: true
};
},
getPermissions(user: User, channel: Channel): string[] {
const guild = GuildStore.getGuild(channel?.guild_id);
if (!guild) return [];
const permissions = computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
return Object.entries(PermissionsBits)
.map(([perm, permInt]) =>
permissions & permInt ? perm : ""
)
.filter(Boolean);
},
genTagTypes(obj) {
let i = 100;
tags.forEach(({ name }) => {
obj[name] = ++i;
obj[i] = name;
obj[`${name}-BOT`] = ++i;
obj[i] = `${name}-BOT`;
obj[`${name}-OP`] = ++i;
obj[i] = `${name}-OP`;
});
},
isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]),
getTagText(passedTagName: string, originalText: string) {
try {
const [tagName, variant] = passedTagName.split("-");
if (!passedTagName) return getIntlMessage("APP_TAG");
const tag = tags.find(({ name }) => tagName === name);
if (!tag) return getIntlMessage("APP_TAG");
if (variant === "BOT" && tagName !== "WEBHOOK" && this.settings.store.dontShowForBots) return getIntlMessage("APP_TAG");
const tagText = settings.store.tagSettings?.[tag.name]?.text || tag.displayName;
switch (variant) {
case "OP":
return `${getIntlMessage("BOT_TAG_FORUM_ORIGINAL_POSTER")}${tagText}`;
case "BOT":
return `${getIntlMessage("APP_TAG")}${tagText}`;
default:
return tagText;
}
} catch {
return originalText;
}
},
getTag({
message, user, channelId, origType, location, channel
}: {
message?: Message,
user: User & { isClyde(): boolean; },
channel?: Channel & { isForumPost(): boolean; isMediaPost(): boolean; },
channelId?: string;
origType?: number;
location: "chat" | "not-chat";
}): number | null {
if (!user)
return null;
if (location === "chat" && user.id === "1")
return Tag.Types.OFFICIAL;
if (user.isClyde())
return Tag.Types.AI;
let type = typeof origType === "number" ? origType : null;
channel ??= ChannelStore.getChannel(channelId!) as any;
if (!channel) return type;
const settings = this.settings.store;
const perms = this.getPermissions(user, channel);
for (const tag of tags) {
if (location === "chat" && !settings.tagSettings[tag.name].showInChat) continue;
if (location === "not-chat" && !settings.tagSettings[tag.name].showInNotChat) continue;
// If the owner tag is disabled, and the user is the owner of the guild,
// avoid adding other tags because the owner will always match the condition for them
if (
tag.name !== "OWNER" &&
GuildStore.getGuild(channel?.guild_id)?.ownerId === user.id &&
(location === "chat" && !settings.tagSettings.OWNER.showInChat) ||
(location === "not-chat" && !settings.tagSettings.OWNER.showInNotChat)
) continue;
if (
tag.permissions?.some(perm => perms.includes(perm)) ||
(tag.condition?.(message!, user, channel))
) {
if ((channel.isForumPost() || channel.isMediaPost()) && channel.ownerId === user.id)
type = Tag.Types[`${tag.name}-OP`];
else if (user.bot && !isWebhook(message!, user) && !settings.dontShowBotTag)
type = Tag.Types[`${tag.name}-BOT`];
else
type = Tag.Types[tag.name];
break;
}
}
return type;
}
});

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings"; import { definePluginSettings, migratePluginSetting } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { runtimeHashMessageKey } from "@utils/intlHash"; import { runtimeHashMessageKey } from "@utils/intlHash";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
@ -32,10 +32,29 @@ interface MessageDeleteProps {
collapsedReason: () => any; collapsedReason: () => any;
} }
// Remove this migration once enough time has passed
migratePluginSetting("NoBlockedMessages", "ignoreBlockedMessages", "ignoreMessages");
const settings = definePluginSettings({
ignoreMessages: {
description: "Completely ignores incoming messages from blocked and ignored (if enabled) users",
type: OptionType.BOOLEAN,
default: false,
restartNeeded: true
},
applyToIgnoredUsers: {
description: "Additionally apply to 'ignored' users",
type: OptionType.BOOLEAN,
default: true,
restartNeeded: false
}
});
export default definePlugin({ export default definePlugin({
name: "NoBlockedMessages", name: "NoBlockedMessages",
description: "Hides all blocked messages from chat completely.", description: "Hides all blocked/ignored messages from chat completely",
authors: [Devs.rushii, Devs.Samu], authors: [Devs.rushii, Devs.Samu, Devs.jamesbt365],
settings,
patches: [ patches: [
{ {
find: "#{intl::BLOCKED_MESSAGES_HIDE}", find: "#{intl::BLOCKED_MESSAGES_HIDE}",
@ -51,38 +70,40 @@ export default definePlugin({
'"ReadStateStore"' '"ReadStateStore"'
].map(find => ({ ].map(find => ({
find, find,
predicate: () => Settings.plugins.NoBlockedMessages.ignoreBlockedMessages === true, predicate: () => settings.store.ignoreMessages,
replacement: [ replacement: [
{ {
match: /(?<=function (\i)\((\i)\){)(?=.*MESSAGE_CREATE:\1)/, match: /(?<=function (\i)\((\i)\){)(?=.*MESSAGE_CREATE:\1)/,
replace: (_, _funcName, props) => `if($self.isBlocked(${props}.message))return;` replace: (_, _funcName, props) => `if($self.shouldIgnoreMessage(${props}.message))return;`
} }
] ]
})) }))
], ],
options: {
ignoreBlockedMessages: {
description: "Completely ignores (recent) incoming messages from blocked users (locally).",
type: OptionType.BOOLEAN,
default: false,
restartNeeded: true,
},
},
isBlocked(message: Message) { shouldIgnoreMessage(message: Message) {
try { try {
return RelationshipStore.isBlocked(message.author.id); if (RelationshipStore.isBlocked(message.author.id)) {
return true;
}
return settings.store.applyToIgnoredUsers && RelationshipStore.isIgnored(message.author.id);
} catch (e) { } catch (e) {
new Logger("NoBlockedMessages").error("Failed to check if user is blocked:", e); new Logger("NoBlockedMessages").error("Failed to check if user is blocked or ignored:", e);
return false;
} }
}, },
shouldHide(props: MessageDeleteProps) { shouldHide(props: MessageDeleteProps): boolean {
try { try {
return props.collapsedReason() === i18n.t[runtimeHashMessageKey("BLOCKED_MESSAGE_COUNT")](); const collapsedReason = props.collapsedReason();
const blockedReason = i18n.t[runtimeHashMessageKey("BLOCKED_MESSAGE_COUNT")]();
const ignoredReason = settings.store.applyToIgnoredUsers
? i18n.t[runtimeHashMessageKey("IGNORED_MESSAGE_COUNT")]()
: null;
return collapsedReason === blockedReason || collapsedReason === ignoredReason;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
return false;
} }
return false;
} }
}); });

View file

@ -1,42 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { getUserSettingLazy } from "@api/UserSettings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
const DisableStreamPreviews = getUserSettingLazy<boolean>("voiceAndVideo", "disableStreamPreviews")!;
// @TODO: Delete this plugin in the future
export default definePlugin({
name: "NoScreensharePreview",
description: "Disables screenshare previews from being sent.",
authors: [Devs.Nuckyz],
start() {
if (!DisableStreamPreviews.getSetting()) {
DisableStreamPreviews.updateSetting(true);
}
},
stop() {
if (DisableStreamPreviews.getSetting()) {
DisableStreamPreviews.updateSetting(false);
}
}
});

View file

@ -25,9 +25,9 @@ export default definePlugin({
settings, settings,
patches: [ patches: [
{ {
find: "_ensureAudio(){", find: "ensureAudio(){",
replacement: { replacement: {
match: /(?=Math\.min\(\i\.\i\.getOutputVolume\(\)\/100)/, match: /(?=Math\.min\(\i\.\i\.getOutputVolume\(\)\/100)/g,
replace: "$self.settings.store.notificationVolume/100*" replace: "$self.settings.store.notificationVolume/100*"
}, },
}, },

View file

@ -100,8 +100,9 @@ export default definePlugin({
replace: "true" replace: "true"
}, },
{ {
match: /!\(0,\i\.isDesktop\)\(\)/, // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
replace: "false" match: /(!)?\(0,\i\.isDesktop\)\(\)/,
replace: (_, not) => not ? "false" : "true"
} }
] ]
}, },

View file

@ -46,8 +46,9 @@ export default definePlugin({
find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}", find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}",
replacement: [ replacement: [
{ {
match: /{(\i:function\(\){return \i},?){2}}/, // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
replace: m => m.replaceAll(canonicalizeMatch(/return \i/g), "return ()=>Promise.resolve(true)") match: /{(?:\i:(?:function\(\){return |\(\)=>)\i}?,?){2}}/,
replace: m => m.replaceAll(canonicalizeMatch(/(function\(\){return |\(\)=>)\i/g), "$1()=>Promise.resolve(true)")
} }
], ],
predicate: () => settings.store.onboarding predicate: () => settings.store.onboarding

View file

@ -157,7 +157,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
src={user.getAvatarURL(void 0, void 0, false)} src={user.getAvatarURL(void 0, void 0, false)}
/> />
)} )}
<Text variant="text-md/normal"> <Text variant="text-md/normal" className={cl("modal-list-item-text")}>
{ {
permission.type === PermissionType.Role permission.type === PermissionType.Role
? role?.name ?? "Unknown Role" ? role?.name ?? "Unknown Role"

View file

@ -73,7 +73,7 @@
background-color: var(--background-modifier-selected); background-color: var(--background-modifier-selected);
} }
.vc-permviewer-modal-list-item > div { .vc-permviewer-modal-list-item-text {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;

View file

@ -6,7 +6,7 @@
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal"; import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal";
import { extractAndLoadChunksLazy, findComponentByCodeLazy, findExportedComponentLazy } from "@webpack"; import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
import { Button, Forms, Text, TextInput, Toasts, useMemo, useState } from "@webpack/common"; import { Button, Forms, Text, TextInput, Toasts, useMemo, useState } from "@webpack/common";
import { DEFAULT_COLOR, SWATCHES } from "../constants"; import { DEFAULT_COLOR, SWATCHES } from "../constants";
@ -30,7 +30,7 @@ interface ColorPickerWithSwatchesProps {
} }
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)"); const ColorPicker = findComponentByCodeLazy<ColorPickerProps>("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
const ColorPickerWithSwatches = findExportedComponentLazy<ColorPickerWithSwatchesProps>("ColorPicker", "CustomColorPicker"); const ColorPickerWithSwatches = findComponentByCodeLazy<ColorPickerWithSwatchesProps>('id:"color-picker"');
export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/); export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/);

View file

@ -155,7 +155,7 @@ export function moveChannel(channelId: string, direction: -1 | 1) {
swapElementsInArray(category.channels, a, b); swapElementsInArray(category.channels, a, b);
} }
// TODO: Remove DataStore PinnedDms migration once enough time has passed // TODO(OptionType.CUSTOM Related): Remove DataStore PinnedDms migration once enough time has passed
async function migrateData() { async function migrateData() {
if (Settings.plugins.PinDMs.dmSectioncollapsed != null) { if (Settings.plugins.PinDMs.dmSectioncollapsed != null) {
settings.store.dmSectionCollapsed = Settings.plugins.PinDMs.dmSectioncollapsed; settings.store.dmSectionCollapsed = Settings.plugins.PinDMs.dmSectioncollapsed;

View file

@ -25,7 +25,7 @@ import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findStoreLazy } from "@webpack"; import { filters, findStoreLazy, mapMangledModuleLazy } from "@webpack";
import { PresenceStore, Tooltip, UserStore } from "@webpack/common"; import { PresenceStore, Tooltip, UserStore } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
@ -70,7 +70,9 @@ const Icons = {
}; };
type Platform = keyof typeof Icons; type Platform = keyof typeof Icons;
const StatusUtils = findByPropsLazy("useStatusFillColor", "StatusTypes"); const { useStatusFillColor } = mapMangledModuleLazy(".concat(.5625*", {
useStatusFillColor: filters.byCode(".hex")
});
const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: string; small: boolean; }) => { const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: string; small: boolean; }) => {
const tooltip = platform === "embedded" const tooltip = platform === "embedded"
@ -79,7 +81,7 @@ const PlatformIcon = ({ platform, status, small }: { platform: Platform, status:
const Icon = Icons[platform] ?? Icons.desktop; const Icon = Icons[platform] ?? Icons.desktop;
return <Icon color={StatusUtils.useStatusFillColor(status)} tooltip={tooltip} small={small} />; return <Icon color={useStatusFillColor(status)} tooltip={tooltip} small={small} />;
}; };
function ensureOwnStatus(user: User) { function ensureOwnStatus(user: User) {

View file

@ -196,7 +196,7 @@ function nextReply(isUp: boolean) {
channel, channel,
message, message,
shouldMention: shouldMention(message), shouldMention: shouldMention(message),
showMentionToggle: channel.isPrivate() && message.author.id !== meId, showMentionToggle: !channel.isPrivate() && message.author.id !== meId,
_isQuickReply: true _isQuickReply: true
}); });
ComponentDispatch.dispatchToLastSubscribed("TEXTAREA_FOCUS"); ComponentDispatch.dispatchToLastSubscribed("TEXTAREA_FOCUS");

View file

@ -7,15 +7,12 @@
import { DataStore } from "@api/index"; import { DataStore } from "@api/index";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { openModal } from "@utils/modal"; import { openModal } from "@utils/modal";
import { findByPropsLazy } from "@webpack"; import { OAuth2AuthorizeModal, showToast, Toasts, UserStore } from "@webpack/common";
import { showToast, Toasts, UserStore } from "@webpack/common";
import { ReviewDBAuth } from "./entities"; import { ReviewDBAuth } from "./entities";
const DATA_STORE_KEY = "rdb-auth"; const DATA_STORE_KEY = "rdb-auth";
const { OAuth2AuthorizeModal } = findByPropsLazy("OAuth2AuthorizeModal");
export let Auth: ReviewDBAuth = {}; export let Auth: ReviewDBAuth = {};
export async function initAuth() { export async function initAuth() {

View file

@ -39,7 +39,7 @@ function BlockedUser({ user, isBusy, setIsBusy }: { user: ReviewDBUser; isBusy:
return ( return (
<div className={cl("block-modal-row")}> <div className={cl("block-modal-row")}>
<img src={user.profilePhoto} alt="" /> <img className={cl("block-modal-avatar")} src={user.profilePhoto} alt="" />
<Forms.FormText className={cl("block-modal-username")}>{user.username}</Forms.FormText> <Forms.FormText className={cl("block-modal-username")}>{user.username}</Forms.FormText>
<UnblockButton <UnblockButton
onClick={isBusy ? undefined : async () => { onClick={isBusy ? undefined : async () => {

View file

@ -65,7 +65,7 @@ function Modal({ modalProps, modalKey, discordId, name, type }: { modalProps: an
</ModalContent> </ModalContent>
<ModalFooter className={cl("modal-footer")}> <ModalFooter className={cl("modal-footer")}>
<div> <div className={cl("modal-footer-wrapper")}>
{ownReview && ( {ownReview && (
<ReviewComponent <ReviewComponent
refetch={refetch} refetch={refetch}

View file

@ -27,7 +27,6 @@ import { cl } from "./utils";
export const settings = definePluginSettings({ export const settings = definePluginSettings({
authorize: { authorize: {
type: OptionType.COMPONENT, type: OptionType.COMPONENT,
description: "Authorize with ReviewDB",
component: () => ( component: () => (
<Button onClick={() => authorize()}> <Button onClick={() => authorize()}>
Authorize with ReviewDB Authorize with ReviewDB
@ -56,7 +55,6 @@ export const settings = definePluginSettings({
}, },
buttons: { buttons: {
type: OptionType.COMPONENT, type: OptionType.COMPONENT,
description: "ReviewDB buttons",
component: () => ( component: () => (
<div className={cl("button-grid")} > <div className={cl("button-grid")} >
<Button onClick={openBlockModal}>Manage Blocked Users</Button> <Button onClick={openBlockModal}>Manage Blocked Users</Button>

View file

@ -16,16 +16,11 @@
border: 1px solid var(--profile-message-input-border-color); border: 1px solid var(--profile-message-input-border-color);
} }
.vc-rdb-modal-footer > div { .vc-rdb-modal-footer-wrapper {
width: 100%; width: 100%;
margin: 6px 16px; margin: 6px 16px;
} }
/* When input becomes disabled(while sending review), input adds unneccesary padding to left, this prevents it */
.vc-rdb-input > div > div {
padding-left: 0 !important;
}
.vc-rdb-placeholder { .vc-rdb-placeholder {
margin-bottom: 4px; margin-bottom: 4px;
font-weight: bold; font-weight: bold;
@ -69,7 +64,7 @@
border-radius: 8px; border-radius: 8px;
} }
.vc-rdb-review-comment img { .vc-rdb-review-comment [class*="avatar"] {
vertical-align: text-top; vertical-align: text-top;
} }
@ -117,13 +112,13 @@
align-items: center; align-items: center;
} }
.vc-rdb-block-modal-row img { .vc-rdb-block-modal-avatar {
border-radius: 50%; border-radius: 50%;
height: 2em; height: 2em;
width: 2em; width: 2em;
} }
.vc-rdb-block-modal img::before { .vc-rdb-block-modal-avatar::before {
content: ""; content: "";
display: block; display: block;
width: 100%; width: 100%;

View file

@ -68,15 +68,16 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi
return ( return (
<ModalRoot {...rootProps}> <ModalRoot {...rootProps}>
<ModalHeader className={cl("modal-header")}> <ModalHeader className={cl("modal-header")}>
<Forms.FormTitle tag="h2"> <Forms.FormTitle tag="h2" className={cl("modal-title")}>
Timestamp Picker Timestamp Picker
</Forms.FormTitle> </Forms.FormTitle>
<ModalCloseButton onClick={close} /> <ModalCloseButton onClick={close} className={cl("modal-close-button")} />
</ModalHeader> </ModalHeader>
<ModalContent className={cl("modal-content")}> <ModalContent className={cl("modal-content")}>
<input <input
className={cl("date-picker")}
type="datetime-local" type="datetime-local"
value={value} value={value}
onChange={e => setValue(e.currentTarget.value)} onChange={e => setValue(e.currentTarget.value)}
@ -86,23 +87,25 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi
/> />
<Forms.FormTitle>Timestamp Format</Forms.FormTitle> <Forms.FormTitle>Timestamp Format</Forms.FormTitle>
<Select <div className={cl("format-select")}>
options={ <Select
Formats.map(m => ({ options={
label: m, Formats.map(m => ({
value: m label: m,
})) value: m
} }))
isSelected={v => v === format} }
select={v => setFormat(v)} isSelected={v => v === format}
serialize={v => v} select={v => setFormat(v)}
renderOptionLabel={o => ( serialize={v => v}
<div className={cl("format-label")}> renderOptionLabel={o => (
{Parser.parse(formatTimestamp(time, o.value))} <div className={cl("format-label")}>
</div> {Parser.parse(formatTimestamp(time, o.value))}
)} </div>
renderOptionValue={() => rendered} )}
/> renderOptionValue={() => rendered}
/>
</div>
<Forms.FormTitle className={Margins.bottom8}>Preview</Forms.FormTitle> <Forms.FormTitle className={Margins.bottom8}>Preview</Forms.FormTitle>
<Forms.FormText className={cl("preview-text")}> <Forms.FormText className={cl("preview-text")}>

View file

@ -1,4 +1,4 @@
.vc-st-modal-content input { .vc-st-date-picker {
background-color: var(--input-background); background-color: var(--input-background);
color: var(--text-normal); color: var(--text-normal);
width: 95%; width: 95%;
@ -12,35 +12,28 @@
font-size: 100%; font-size: 100%;
} }
.vc-st-format-label, .vc-st-format-select {
.vc-st-format-label span {
background-color: transparent;
}
.vc-st-modal-content [class|="select"] {
margin-bottom: 1em; margin-bottom: 1em;
--background-modifier-accent: transparent;
} }
.vc-st-modal-content [class|="select"] span { .vc-st-format-label {
background-color: var(--input-background); --background-modifier-accent: transparent;
} }
.vc-st-modal-header { .vc-st-modal-header {
place-content: center space-between; place-content: center space-between;
} }
.vc-st-modal-header h1 { .vc-st-modal-title {
margin: 0; margin: 0;
} }
.vc-st-modal-header button { .vc-st-modal-close-button {
padding: 0; padding: 0;
} }
.vc-st-preview-text { .vc-st-preview-text {
margin-bottom: 1em; margin-bottom: 1em;
} }
.vc-st-button svg {
transform: scale(1.1) translateY(1px);
}

View file

@ -31,7 +31,8 @@ export function openGuildInfoModal(guild: Guild) {
const enum Tabs { const enum Tabs {
ServerInfo, ServerInfo,
Friends, Friends,
BlockedUsers BlockedUsers,
IgnoredUsers
} }
interface GuildProps { interface GuildProps {
@ -44,7 +45,8 @@ interface RelationshipProps extends GuildProps {
const fetched = { const fetched = {
friends: false, friends: false,
blocked: false blocked: false,
ignored: false
}; };
function renderTimestamp(timestamp: number) { function renderTimestamp(timestamp: number) {
@ -56,10 +58,12 @@ function renderTimestamp(timestamp: number) {
function GuildInfoModal({ guild }: GuildProps) { function GuildInfoModal({ guild }: GuildProps) {
const [friendCount, setFriendCount] = useState<number>(); const [friendCount, setFriendCount] = useState<number>();
const [blockedCount, setBlockedCount] = useState<number>(); const [blockedCount, setBlockedCount] = useState<number>();
const [ignoredCount, setIgnoredCount] = useState<number>();
useEffect(() => { useEffect(() => {
fetched.friends = false; fetched.friends = false;
fetched.blocked = false; fetched.blocked = false;
fetched.ignored = false;
}, []); }, []);
const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo); const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo);
@ -90,6 +94,7 @@ function GuildInfoModal({ guild }: GuildProps) {
<div className={cl("header")}> <div className={cl("header")}>
{iconUrl {iconUrl
? <img ? <img
className={cl("icon")}
src={iconUrl} src={iconUrl}
alt="" alt=""
onClick={() => openImageModal({ onClick={() => openImageModal({
@ -132,12 +137,19 @@ function GuildInfoModal({ guild }: GuildProps) {
> >
Blocked Users{blockedCount !== undefined ? ` (${blockedCount})` : ""} Blocked Users{blockedCount !== undefined ? ` (${blockedCount})` : ""}
</TabBar.Item> </TabBar.Item>
<TabBar.Item
className={cl("tab", { selected: currentTab === Tabs.IgnoredUsers })}
id={Tabs.IgnoredUsers}
>
Ignored Users{ignoredCount !== undefined ? ` (${ignoredCount})` : ""}
</TabBar.Item>
</TabBar> </TabBar>
<div className={cl("tab-content")}> <div className={cl("tab-content")}>
{currentTab === Tabs.ServerInfo && <ServerInfoTab guild={guild} />} {currentTab === Tabs.ServerInfo && <ServerInfoTab guild={guild} />}
{currentTab === Tabs.Friends && <FriendsTab guild={guild} setCount={setFriendCount} />} {currentTab === Tabs.Friends && <FriendsTab guild={guild} setCount={setFriendCount} />}
{currentTab === Tabs.BlockedUsers && <BlockedUsersTab guild={guild} setCount={setBlockedCount} />} {currentTab === Tabs.BlockedUsers && <BlockedUsersTab guild={guild} setCount={setBlockedCount} />}
{currentTab === Tabs.IgnoredUsers && <IgnoredUserTab guild={guild} setCount={setIgnoredCount} />}
</div> </div>
</div> </div>
); );
@ -159,6 +171,7 @@ function Owner(guildId: string, owner: User) {
return ( return (
<div className={cl("owner")}> <div className={cl("owner")}>
<img <img
className={cl("owner-avatar")}
src={ownerAvatarUrl} src={ownerAvatarUrl}
alt="" alt=""
onClick={() => openImageModal({ onClick={() => openImageModal({
@ -211,7 +224,13 @@ function BlockedUsersTab({ guild, setCount }: RelationshipProps) {
return UserList("blocked", guild, blockedIds, setCount); return UserList("blocked", guild, blockedIds, setCount);
} }
function UserList(type: "friends" | "blocked", guild: Guild, ids: string[], setCount: (count: number) => void) { function IgnoredUserTab({ guild, setCount }: RelationshipProps) {
const ignoredIds = Object.keys(RelationshipStore.getRelationships()).filter(id => RelationshipStore.isIgnored(id));
return UserList("ignored", guild, ignoredIds, setCount);
}
function UserList(type: "friends" | "blocked" | "ignored", guild: Guild, ids: string[], setCount: (count: number) => void) {
const missing = [] as string[]; const missing = [] as string[];
const members = [] as string[]; const members = [] as string[];

View file

@ -21,7 +21,7 @@
margin: 0.5em; margin: 0.5em;
} }
.vc-gp-header img { .vc-gp-icon {
width: 48px; width: 48px;
height: 48px; height: 48px;
cursor: pointer; cursor: pointer;
@ -82,7 +82,7 @@
gap: 0.2em; gap: 0.2em;
} }
.vc-gp-owner img { .vc-gp-owner-avatar {
height: 20px; height: 20px;
border-radius: 50%; border-radius: 50%;
cursor: pointer; cursor: pointer;

View file

@ -84,9 +84,9 @@ export const Code = ({
} }
const codeTableRows = lines.map((line, i) => ( const codeTableRows = lines.map((line, i) => (
<tr key={i}> <tr className={cl("table-row")} key={i}>
<td style={{ color: theme.plainColor }}>{i + 1}</td> <td className={cl("table-cell")} style={{ color: theme.plainColor }}>{i + 1}</td>
<td>{line}</td> <td className={cl("table-cell")}>{line}</td>
</tr> </tr>
)); ));

View file

@ -102,7 +102,7 @@ export const Highlighter = ({
color: themeBase.plainColor, color: themeBase.plainColor,
}} }}
> >
<code> <code className={cl("code")}>
<Header <Header
langName={langName} langName={langName}
useDevIcon={useDevIcon} useDevIcon={useDevIcon}

View file

@ -1,13 +1,13 @@
.shiki-container { .vc-shiki-container {
border: 4px; border: 4px;
background-color: var(--background-secondary); background-color: var(--background-secondary);
} }
.shiki-root { .vc-shiki-root {
border-radius: 4px; border-radius: 4px;
} }
.shiki-root code { .vc-shiki-root .vc-shiki-code {
display: block; display: block;
overflow-x: auto; overflow-x: auto;
padding: 0.5em; padding: 0.5em;
@ -20,16 +20,16 @@
border: none; border: none;
} }
.shiki-devicon { .vc-shiki-devicon {
margin-right: 8px; margin-right: 8px;
user-select: none; user-select: none;
} }
.shiki-plain code { .vc-shiki-plain .vc-shiki-code {
padding-top: 8px; padding-top: 8px;
} }
.shiki-btns { .vc-shiki-btns {
font-size: 1em; font-size: 1em;
position: absolute; position: absolute;
right: 0; right: 0;
@ -37,25 +37,25 @@
opacity: 0; opacity: 0;
} }
.shiki-root:hover .shiki-btns { .vc-shiki-root:hover .vc-shiki-btns {
opacity: 1; opacity: 1;
} }
.shiki-btn { .vc-shiki-btn {
border-radius: 4px 4px 0 0; border-radius: 4px 4px 0 0;
padding: 4px 8px; padding: 4px 8px;
user-select: none; user-select: none;
} }
.shiki-btn ~ .shiki-btn { .vc-shiki-btn ~ .vc-shiki-btn {
margin-left: 4px; margin-left: 4px;
} }
.shiki-btn:last-child { .vc-shiki-btn:last-child {
border-radius: 4px 0; border-radius: 4px 0;
} }
.shiki-spinner-container { .vc-shiki-spinner-container {
align-items: center; align-items: center;
background-color: rgb(0 0 0 / 60%); background-color: rgb(0 0 0 / 60%);
display: flex; display: flex;
@ -64,11 +64,11 @@
inset: 0; inset: 0;
} }
.shiki-preview { .vc-shiki-preview {
margin-bottom: 2em; margin-bottom: 2em;
} }
.shiki-lang { .vc-shiki-lang {
padding: 0 5px; padding: 0 5px;
margin-bottom: 6px; margin-bottom: 6px;
font-weight: bold; font-weight: bold;
@ -77,24 +77,24 @@
align-items: center; align-items: center;
} }
.shiki-table { .vc-shiki-table {
border-collapse: collapse; border-collapse: collapse;
width: 100%; width: 100%;
} }
.shiki-table tr { .vc-shiki-table-row {
height: 19px; height: 19px;
width: 100%; width: 100%;
} }
.shiki-root td:first-child { .vc-shiki-root .vc-shiki-table-cell:first-child {
border-right: 1px solid transparent; border-right: 1px solid transparent;
padding-left: 5px; padding-left: 5px;
padding-right: 8px; padding-right: 8px;
user-select: none; user-select: none;
} }
.shiki-root td:last-child { .vc-shiki-root .vc-shiki-table-cell:last-child {
padding-left: 8px; padding-left: 8px;
word-break: break-word; word-break: break-word;
width: 100%; width: 100%;

View file

@ -23,7 +23,7 @@ import { resolveLang } from "../api/languages";
import { HighlighterProps } from "../components/Highlighter"; import { HighlighterProps } from "../components/Highlighter";
import { HljsSetting } from "../types"; import { HljsSetting } from "../types";
export const cl = classNameFactory("shiki-"); export const cl = classNameFactory("vc-shiki-");
export const shouldUseHljs = ({ export const shouldUseHljs = ({
lang, lang,

View file

@ -33,6 +33,7 @@ export function VerifiedIcon() {
forcedIconColor={forcedIconColor} forcedIconColor={forcedIconColor}
size={16} size={16}
tooltipText={getIntlMessage("CONNECTION_VERIFIED")} tooltipText={getIntlMessage("CONNECTION_VERIFIED")}
className="vc-sc-tooltip-icon"
/> />
); );
} }

View file

@ -125,7 +125,7 @@ function CompactConnectionComponent({ connection, theme }: { connection: Connect
<span className="vc-sc-tooltip"> <span className="vc-sc-tooltip">
<span className="vc-sc-connection-name">{connection.name}</span> <span className="vc-sc-connection-name">{connection.name}</span>
{connection.verified && <VerifiedIcon />} {connection.verified && <VerifiedIcon />}
<TooltipIcon height={16} width={16} /> <TooltipIcon height={16} width={16} className="vc-sc-tooltip-icon" />
</span> </span>
} }
key={connection.id} key={connection.id}

View file

@ -14,6 +14,6 @@
word-break: break-all; word-break: break-all;
} }
.vc-sc-tooltip svg { .vc-sc-tooltip-icon {
min-width: 16px; min-width: 16px;
} }

View file

@ -18,6 +18,7 @@
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { classes } from "@utils/misc";
import { formatDuration } from "@utils/text"; import { formatDuration } from "@utils/text";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common"; import { EmojiStore, FluxDispatcher, GuildMemberStore, GuildStore, Parser, PermissionsBits, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip, useEffect, useState } from "@webpack/common";
@ -25,7 +26,7 @@ import type { Channel } from "discord-types/general";
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions"; import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "../../permissionsViewer/components/RolesAndUsersPermissions";
import { sortPermissionOverwrites } from "../../permissionsViewer/utils"; import { sortPermissionOverwrites } from "../../permissionsViewer/utils";
import { settings } from ".."; import { cl, settings } from "..";
const enum SortOrderTypes { const enum SortOrderTypes {
LATEST_ACTIVITY = 0, LATEST_ACTIVITY = 0,
@ -168,19 +169,19 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
}, [channelId]); }, [channelId]);
return ( return (
<div className={ChatScrollClasses.auto + " " + ChatScrollClasses.customTheme + " " + ChatClasses.chatContent + " " + "shc-lock-screen-outer-container"}> <div className={classes(ChatScrollClasses.auto, ChatScrollClasses.customTheme, ChatScrollClasses.managedReactiveScroller)}>
<div className="shc-lock-screen-container"> <div className={cl("container")}>
<img className="shc-lock-screen-logo" src={HiddenChannelLogo} /> <img className={cl("logo")} src={HiddenChannelLogo} />
<div className="shc-lock-screen-heading-container"> <div className={cl("heading-container")}>
<Text variant="heading-xxl/bold">This is a {!PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) ? "hidden" : "locked"} {ChannelTypesToChannelNames[type]} channel.</Text> <Text variant="heading-xxl/bold">This is a {!PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) ? "hidden" : "locked"} {ChannelTypesToChannelNames[type]} channel</Text>
{channel.isNSFW() && {channel.isNSFW() &&
<Tooltip text="NSFW"> <Tooltip text="NSFW">
{({ onMouseLeave, onMouseEnter }) => ( {({ onMouseLeave, onMouseEnter }) => (
<svg <svg
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
className="shc-lock-screen-heading-nsfw-icon" className={cl("heading-nsfw-icon")}
width="32" width="32"
height="32" height="32"
viewBox="0 0 48 48" viewBox="0 0 48 48"
@ -202,7 +203,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
)} )}
{channel.isForumChannel() && topic && topic.length > 0 && ( {channel.isForumChannel() && topic && topic.length > 0 && (
<div className="shc-lock-screen-topic-container"> <div className={cl("topic-container")}>
{Parser.parseTopic(topic, false, { channelId })} {Parser.parseTopic(topic, false, { channelId })}
</div> </div>
)} )}
@ -213,7 +214,6 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
<Timestamp timestamp={new Date(SnowflakeUtils.extractTimestamp(lastMessageId))} /> <Timestamp timestamp={new Date(SnowflakeUtils.extractTimestamp(lastMessageId))} />
</Text> </Text>
} }
{lastPinTimestamp && {lastPinTimestamp &&
<Text variant="text-md/normal">Last message pin: <Timestamp timestamp={new Date(lastPinTimestamp)} /></Text> <Text variant="text-md/normal">Last message pin: <Timestamp timestamp={new Date(lastPinTimestamp)} /></Text>
} }
@ -247,7 +247,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
<Text variant="text-md/normal">Default sort order: {SortOrderTypesToNames[defaultSortOrder]}</Text> <Text variant="text-md/normal">Default sort order: {SortOrderTypesToNames[defaultSortOrder]}</Text>
} }
{defaultReactionEmoji != null && {defaultReactionEmoji != null &&
<div className="shc-lock-screen-default-emoji-container"> <div className={cl("default-emoji-container")}>
<Text variant="text-md/normal">Default reaction emoji:</Text> <Text variant="text-md/normal">Default reaction emoji:</Text>
{Parser.defaultRules[defaultReactionEmoji.emojiName ? "emoji" : "customEmoji"].react({ {Parser.defaultRules[defaultReactionEmoji.emojiName ? "emoji" : "customEmoji"].react({
name: defaultReactionEmoji.emojiName name: defaultReactionEmoji.emojiName
@ -258,29 +258,29 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
src: defaultReactionEmoji.emojiName src: defaultReactionEmoji.emojiName
? EmojiUtils.getURL(defaultReactionEmoji.emojiName) ? EmojiUtils.getURL(defaultReactionEmoji.emojiName)
: void 0 : void 0
}, void 0, { key: "0" })} }, void 0, { key: 0 })}
</div> </div>
} }
{channel.hasFlag(ChannelFlags.REQUIRE_TAG) && {channel.hasFlag(ChannelFlags.REQUIRE_TAG) &&
<Text variant="text-md/normal">Posts on this forum require a tag to be set.</Text> <Text variant="text-md/normal">Posts on this forum require a tag to be set.</Text>
} }
{availableTags && availableTags.length > 0 && {availableTags && availableTags.length > 0 &&
<div className="shc-lock-screen-tags-container"> <div className={cl("tags-container")}>
<Text variant="text-lg/bold">Available tags:</Text> <Text variant="text-lg/bold">Available tags:</Text>
<div className="shc-lock-screen-tags"> <div className={cl("tags")}>
{availableTags.map(tag => <TagComponent tag={tag} key={tag.id} />)} {availableTags.map(tag => <TagComponent tag={tag} key={tag.id} />)}
</div> </div>
</div> </div>
} }
<div className="shc-lock-screen-allowed-users-and-roles-container"> <div className={cl("allowed-users-and-roles-container")}>
<div className="shc-lock-screen-allowed-users-and-roles-container-title"> <div className={cl("allowed-users-and-roles-container-title")}>
{Settings.plugins.PermissionsViewer.enabled && ( {Vencord.Plugins.isPluginEnabled("PermissionsViewer") && (
<Tooltip text="Permission Details"> <Tooltip text="Permission Details">
{({ onMouseLeave, onMouseEnter }) => ( {({ onMouseLeave, onMouseEnter }) => (
<button <button
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
className="shc-lock-screen-allowed-users-and-roles-container-permdetails-btn" className={cl("allowed-users-and-roles-container-permdetails-btn")}
onClick={() => openRolesAndUsersPermissionsModal(permissions, GuildStore.getGuild(channel.guild_id), channel.name)} onClick={() => openRolesAndUsersPermissionsModal(permissions, GuildStore.getGuild(channel.guild_id), channel.name)}
> >
<svg <svg
@ -300,7 +300,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
<button <button
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
className="shc-lock-screen-allowed-users-and-roles-container-toggle-btn" className={cl("allowed-users-and-roles-container-toggle-btn")}
onClick={() => settings.store.defaultAllowedUsersAndRolesDropdownState = !defaultAllowedUsersAndRolesDropdownState} onClick={() => settings.store.defaultAllowedUsersAndRolesDropdownState = !defaultAllowedUsersAndRolesDropdownState}
> >
<svg <svg

View file

@ -19,8 +19,10 @@
import "./style.css"; import "./style.css";
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
import { canonicalizeMatch } from "@utils/patches"; import { canonicalizeMatch } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
@ -31,6 +33,8 @@ import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen";
const ChannelListClasses = findByPropsLazy("modeMuted", "modeSelected", "unread", "icon"); const ChannelListClasses = findByPropsLazy("modeMuted", "modeSelected", "unread", "icon");
export const cl = classNameFactory("vc-shc-");
const enum ShowMode { const enum ShowMode {
LockIcon, LockIcon,
HiddenIconWithMutedStyle HiddenIconWithMutedStyle
@ -108,8 +112,11 @@ export default definePlugin({
}, },
{ {
// Prevent Discord from trying to connect to hidden voice channels // Prevent Discord from trying to connect to hidden voice channels
match: /(?=&&\i\.\i\.selectVoiceChannel\((\i)\.id\))/, // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})` match: /(?=(\|\||&&)\i\.\i\.selectVoiceChannel\((\i)\.id\))/,
replace: (_, condition, channel) => condition === "||"
? `||$self.isHiddenChannel(${channel})`
: `&&!$self.isHiddenChannel(${channel})`
}, },
{ {
// Make Discord show inside the channel if clicking on a hidden or locked channel // Make Discord show inside the channel if clicking on a hidden or locked channel
@ -122,8 +129,11 @@ export default definePlugin({
{ {
find: ".AUDIENCE),{isSubscriptionGated", find: ".AUDIENCE),{isSubscriptionGated",
replacement: { replacement: {
match: /!(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/, // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
replace: (m, channel) => `${m}&&!$self.isHiddenChannel(${channel})` match: /(!)?(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/,
replace: (m, not, channel) => not
? `${m}&&!$self.isHiddenChannel(${channel})`
: `${m}||$self.isHiddenChannel(${channel})`
} }
}, },
{ {
@ -173,8 +183,11 @@ export default definePlugin({
}, },
// Make voice channels also appear as muted if they are muted // Make voice channels also appear as muted if they are muted
{ {
match: /(?<=\.wrapper:\i\.notInteractive,)(.+?)if\((\i)\)return (\i\.MUTED);/, // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
replace: (_, otherClasses, isMuted, mutedClassExpression) => `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return "";` match: /(?<=\.wrapper:\i\.notInteractive,)(.+?)(if\()?(\i)(?:\)return |\?)(\i\.MUTED)/,
replace: (_, otherClasses, isIf, isMuted, mutedClassExpression) => isIf
? `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return ""`
: `${isMuted}?${mutedClassExpression}:"",${otherClasses}${isMuted}?""`
} }
] ]
}, },
@ -184,8 +197,9 @@ export default definePlugin({
{ {
// Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden // Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden
predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle, predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle,
match: /\.LOCKED;if\((?<={channel:(\i).+?)/, // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
replace: (m, channel) => `${m}!$self.isHiddenChannel(${channel})&&` match: /(?<=\.LOCKED(?:;if\(|:))(?<={channel:(\i).+?)/,
replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&`
}, },
{ {
// Hide unreads // Hide unreads
@ -539,7 +553,7 @@ export default definePlugin({
aria-hidden={true} aria-hidden={true}
role="img" role="img"
> >
<path className="shc-evenodd-fill-current-color" d="M17 11V7C17 4.243 14.756 2 12 2C9.242 2 7 4.243 7 7V11C5.897 11 5 11.896 5 13V20C5 21.103 5.897 22 7 22H17C18.103 22 19 21.103 19 20V13C19 11.896 18.103 11 17 11ZM12 18C11.172 18 10.5 17.328 10.5 16.5C10.5 15.672 11.172 15 12 15C12.828 15 13.5 15.672 13.5 16.5C13.5 17.328 12.828 18 12 18ZM15 11H9V7C9 5.346 10.346 4 12 4C13.654 4 15 5.346 15 7V11Z" /> <path fill="currentcolor" fillRule="evenodd" d="M17 11V7C17 4.243 14.756 2 12 2C9.242 2 7 4.243 7 7V11C5.897 11 5 11.896 5 13V20C5 21.103 5.897 22 7 22H17C18.103 22 19 21.103 19 20V13C19 11.896 18.103 11 17 11ZM12 18C11.172 18 10.5 17.328 10.5 16.5C10.5 15.672 11.172 15 12 15C12.828 15 13.5 15.672 13.5 16.5C13.5 17.328 12.828 18 12 18ZM15 11H9V7C9 5.346 10.346 4 12 4C13.654 4 15 5.346 15 7V11Z" />
</svg> </svg>
), { noop: true }), ), { noop: true }),
@ -549,14 +563,14 @@ export default definePlugin({
<svg <svg
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
className={ChannelListClasses.icon + " " + "shc-hidden-channel-icon"} className={classes(ChannelListClasses.icon, cl("hidden-channel-icon"))}
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
aria-hidden={true} aria-hidden={true}
role="img" role="img"
> >
<path className="shc-evenodd-fill-current-color" d="m19.8 22.6-4.2-4.15q-.875.275-1.762.413Q12.95 19 12 19q-3.775 0-6.725-2.087Q2.325 14.825 1 11.5q.525-1.325 1.325-2.463Q3.125 7.9 4.15 7L1.4 4.2l1.4-1.4 18.4 18.4ZM12 16q.275 0 .512-.025.238-.025.513-.1l-5.4-5.4q-.075.275-.1.513-.025.237-.025.512 0 1.875 1.312 3.188Q10.125 16 12 16Zm7.3.45-3.175-3.15q.175-.425.275-.862.1-.438.1-.938 0-1.875-1.312-3.188Q13.875 7 12 7q-.5 0-.938.1-.437.1-.862.3L7.65 4.85q1.025-.425 2.1-.638Q10.825 4 12 4q3.775 0 6.725 2.087Q21.675 8.175 23 11.5q-.575 1.475-1.512 2.738Q20.55 15.5 19.3 16.45Zm-4.625-4.6-3-3q.7-.125 1.288.112.587.238 1.012.688.425.45.613 1.038.187.587.087 1.162Z" /> <path fill="currentcolor" fillRule="evenodd" d="m19.8 22.6-4.2-4.15q-.875.275-1.762.413Q12.95 19 12 19q-3.775 0-6.725-2.087Q2.325 14.825 1 11.5q.525-1.325 1.325-2.463Q3.125 7.9 4.15 7L1.4 4.2l1.4-1.4 18.4 18.4ZM12 16q.275 0 .512-.025.238-.025.513-.1l-5.4-5.4q-.075.275-.1.513-.025.237-.025.512 0 1.875 1.312 3.188Q10.125 16 12 16Zm7.3.45-3.175-3.15q.175-.425.275-.862.1-.438.1-.938 0-1.875-1.312-3.188Q13.875 7 12 7q-.5 0-.938.1-.437.1-.862.3L7.65 4.85q1.025-.425 2.1-.638Q10.825 4 12 4q3.775 0 6.725 2.087Q21.675 8.175 23 11.5q-.575 1.475-1.512 2.738Q20.55 15.5 19.3 16.45Zm-4.625-4.6-3-3q.7-.125 1.288.112.587.238 1.012.688.425.45.613 1.038.187.587.087 1.162Z" />
</svg> </svg>
)} )}
</Tooltip> </Tooltip>

View file

@ -1,43 +1,31 @@
.shc-lock-screen-outer-container { .vc-shc-container {
overflow: hidden scroll;
flex: 1 1 auto;
height: 100%;
width: 100%;
}
.shc-lock-screen-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
text-align: center; text-align: center;
gap: 0.65em;
margin: 0.5em 0;
min-height: 100%; min-height: 100%;
} }
.shc-lock-screen-container > * { .vc-shc-logo {
margin: 5px; width: 12em;
height: 12em;
} }
.shc-lock-screen-logo { .vc-shc-heading-container {
width: 180px;
height: 180px;
}
.shc-lock-screen-heading-container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
gap: 0.5em;
} }
.shc-lock-screen-heading-container > * { .vc-shc-heading-nsfw-icon {
margin: inherit;
}
.shc-lock-screen-heading-nsfw-icon {
color: var(--text-normal); color: var(--text-normal);
} }
.shc-lock-screen-topic-container { .vc-shc-topic-container {
color: var(--text-normal); color: var(--text-normal);
background: var(--bg-overlay-3, var(--background-secondary)); background: var(--bg-overlay-3, var(--background-secondary));
border-radius: 5px; border-radius: 5px;
@ -45,91 +33,75 @@
max-width: 70vw; max-width: 70vw;
} }
.shc-lock-screen-tags-container { .vc-shc-default-emoji-container {
display: flex;
flex-direction: row;
align-items: center;
background: var(--bg-overlay-3, var(--background-secondary));
border-radius: 8px;
padding: 0.75em;
margin-left: 0.75em;
}
.vc-shc-tags-container {
display: flex;
flex-direction: column;
background: var(--bg-overlay-3, var(--background-secondary)); background: var(--bg-overlay-3, var(--background-secondary));
border-radius: 5px; border-radius: 5px;
padding: 10px; padding: 0.75em;
gap: 0.75em;
max-width: 70vw; max-width: 70vw;
} }
.shc-lock-screen-tags-container > * { .vc-shc-tags {
margin: inherit;
}
.shc-lock-screen-tags {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 8px; gap: 0.35em;
} }
.shc-evenodd-fill-current-color { .vc-shc-allowed-users-and-roles-container {
fill-rule: evenodd;
fill: currentcolor;
}
.shc-hidden-channel-icon {
margin-left: 6px;
z-index: 0;
cursor: not-allowed;
}
.shc-lock-screen-default-emoji-container {
display: flex;
flex-direction: row;
align-items: center;
}
.shc-lock-screen-default-emoji-container > [class^="emojiContainer"] {
background: var(--bg-overlay-3, var(--background-secondary));
border-radius: 8px;
padding: 5px 6px;
margin-left: 5px;
}
.shc-lock-screen-allowed-users-and-roles-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
background: var(--bg-overlay-3, var(--background-secondary)); background: var(--bg-overlay-3, var(--background-secondary));
border-radius: 5px; border-radius: 5px;
padding: 10px; padding: 0.75em;
max-width: 70vw; max-width: 70vw;
} }
.shc-lock-screen-allowed-users-and-roles-container-title { .vc-shc-allowed-users-and-roles-container-title {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
gap: 0.5em;
} }
.shc-lock-screen-allowed-users-and-roles-container-toggle-btn { .vc-shc-allowed-users-and-roles-container-toggle-btn {
all: unset; all: unset;
margin-left: 5px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
}
.shc-lock-screen-allowed-users-and-roles-container-toggle-btn > svg {
color: var(--text-normal); color: var(--text-normal);
} }
.shc-lock-screen-allowed-users-and-roles-container-permdetails-btn { .vc-shc-allowed-users-and-roles-container-permdetails-btn {
all: unset; all: unset;
margin-right: 5px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
}
.shc-lock-screen-allowed-users-and-roles-container-permdetails-btn > svg {
color: var(--text-normal); color: var(--text-normal);
} }
.shc-lock-screen-allowed-users-and-roles-container > [class^="members"] { .vc-shc-allowed-users-and-roles-container > [class^="members"] {
margin-left: 10px; margin-left: 12px;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
} }
.vc-shc-hidden-channel-icon {
cursor: not-allowed;
margin-left: 6px;
z-index: 0;
}

View file

@ -76,8 +76,8 @@ export default definePlugin({
find: "#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}", find: "#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}",
replacement: [ replacement: [
{ {
match: /(\i)\.Tooltip,{(text:.{0,30}#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}\))/, match: /\i\.\i,{(text:.{0,30}#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}\))/,
replace: "$self.TooltipWrapper,{message:arguments[0].message,$2" replace: "$self.TooltipWrapper,{message:arguments[0].message,$1"
} }
] ]
} }

View file

@ -19,6 +19,7 @@
import "./spotifyStyles.css"; import "./spotifyStyles.css";
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons"; import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons";
import { debounce } from "@shared/debounce"; import { debounce } from "@shared/debounce";
@ -28,7 +29,7 @@ import { ContextMenuApi, FluxDispatcher, Forms, Menu, React, useEffect, useState
import { SpotifyStore, Track } from "./SpotifyStore"; import { SpotifyStore, Track } from "./SpotifyStore";
const cl = (className: string) => `vc-spotify-${className}`; const cl = classNameFactory("vc-spotify-");
function msToHuman(ms: number) { function msToHuman(ms: number) {
const minutes = ms / 1000 / 60; const minutes = ms / 1000 / 60;
@ -40,7 +41,7 @@ function msToHuman(ms: number) {
function Svg(path: string, label: string) { function Svg(path: string, label: string) {
return () => ( return () => (
<svg <svg
className={classes(cl("button-icon"), cl(label))} className={cl("button-icon", label)}
height="24" height="24"
width="24" width="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@ -126,7 +127,7 @@ function Controls() {
return ( return (
<Flex className={cl("button-row")} style={{ gap: 0 }}> <Flex className={cl("button-row")} style={{ gap: 0 }}>
<Button <Button
className={classes(cl("button"), cl(shuffle ? "shuffle-on" : "shuffle-off"))} className={classes(cl("button"), cl("shuffle"), cl(shuffle ? "shuffle-on" : "shuffle-off"))}
onClick={() => SpotifyStore.setShuffle(!shuffle)} onClick={() => SpotifyStore.setShuffle(!shuffle)}
> >
<Shuffle /> <Shuffle />
@ -143,7 +144,7 @@ function Controls() {
<SkipNext /> <SkipNext />
</Button> </Button>
<Button <Button
className={classes(cl("button"), cl(repeatClassName))} className={classes(cl("button"), cl("repeat"), cl(repeatClassName))}
onClick={() => SpotifyStore.setRepeat(nextRepeat)} onClick={() => SpotifyStore.setRepeat(nextRepeat)}
style={{ position: "relative" }} style={{ position: "relative" }}
> >
@ -285,11 +286,12 @@ function Info({ track }: { track: Track; }) {
</> </>
); );
if (coverExpanded && img) return ( if (coverExpanded && img)
<div id={cl("album-expanded-wrapper")}> return (
{i} <div id={cl("album-expanded-wrapper")}>
</div> {i}
); </div>
);
return ( return (
<div id={cl("info-wrapper")}> <div id={cl("info-wrapper")}>

View file

@ -30,22 +30,17 @@
background-color: var(--background-modifier-selected); background-color: var(--background-modifier-selected);
} }
.vc-spotify-button svg { .vc-spotify-button-icon {
height: 24px; height: 24px;
width: 24px; width: 24px;
} }
[class*="vc-spotify-shuffle"] > svg, .vc-spotify-shuffle .vc-spotify-button-icon,
[class*="vc-spotify-repeat"] > svg { .vc-spotify-repeat .vc-spotify-button-icon {
width: 22px; width: 22px;
height: 22px; height: 22px;
} }
.vc-spotify-button svg path {
width: 100%;
height: 100%;
}
/* .vc-spotify-button:hover { /* .vc-spotify-button:hover {
filter: brightness(1.3); filter: brightness(1.3);
} */ } */
@ -87,12 +82,19 @@
gap: 0.5em; gap: 0.5em;
} }
#vc-spotify-info-wrapper img { #vc-spotify-album-image {
height: 90%; height: 90%;
object-fit: contain; object-fit: contain;
border-radius: 3px;
transition: filter 0.2s;
} }
#vc-spotify-album-expanded-wrapper img { #vc-spotify-album-image:hover {
filter: brightness(1.2);
cursor: pointer;
}
#vc-spotify-album-expanded-wrapper #vc-spotify-album-image {
width: 100%; width: 100%;
object-fit: contain; object-fit: contain;
} }
@ -137,16 +139,6 @@
cursor: pointer; cursor: pointer;
} }
#vc-spotify-album-image {
border-radius: 3px;
transition: filter 0.2s;
}
#vc-spotify-album-image:hover {
filter: brightness(1.2);
cursor: pointer;
}
#vc-spotify-progress-bar { #vc-spotify-progress-bar {
position: relative; position: relative;
color: var(--text-normal); color: var(--text-normal);

View file

@ -45,7 +45,6 @@ const makeEmptyRuleArray = () => [makeEmptyRule()];
const settings = definePluginSettings({ const settings = definePluginSettings({
replace: { replace: {
type: OptionType.COMPONENT, type: OptionType.COMPONENT,
description: "",
component: () => { component: () => {
const { stringRules, regexRules } = settings.use(["stringRules", "regexRules"]); const { stringRules, regexRules } = settings.use(["stringRules", "regexRules"]);
@ -245,7 +244,7 @@ export default definePlugin({
}, },
async start() { async start() {
// TODO: Remove DataStore rules migrations once enough time has passed // TODO(OptionType.CUSTOM Related): Remove DataStore rules migrations once enough time has passed
const oldStringRules = await DataStore.get<Rule[]>(STRING_RULES_KEY); const oldStringRules = await DataStore.get<Rule[]>(STRING_RULES_KEY);
if (oldStringRules != null) { if (oldStringRules != null) {
settings.store.stringRules = oldStringRules; settings.store.stringRules = oldStringRules;

View file

@ -76,7 +76,7 @@ export function TranslateModal({ rootProps }: { rootProps: ModalProps; }) {
return ( return (
<ModalRoot {...rootProps}> <ModalRoot {...rootProps}>
<ModalHeader className={cl("modal-header")}> <ModalHeader className={cl("modal-header")}>
<Forms.FormTitle tag="h2"> <Forms.FormTitle tag="h2" className={cl("modal-title")}>
Translate Translate
</Forms.FormTitle> </Forms.FormTitle>
<ModalCloseButton onClick={rootProps.onClose} /> <ModalCloseButton onClick={rootProps.onClose} />

View file

@ -55,7 +55,7 @@ export function TranslationAccessory({ message }: { message: Message; }) {
return ( return (
<span className={cl("accessory")}> <span className={cl("accessory")}>
<TranslateIcon width={16} height={16} /> <TranslateIcon width={16} height={16} className={cl("accessory-icon")} />
{Parser.parse(translation.text)} {Parser.parse(translation.text)}
{" "} {" "}
(translated from {translation.sourceLanguage} - <Dismiss onDismiss={() => setTranslation(undefined)} />) (translated from {translation.sourceLanguage} - <Dismiss onDismiss={() => setTranslation(undefined)} />)

View file

@ -6,7 +6,7 @@
place-content: center space-between; place-content: center space-between;
} }
.vc-trans-modal-header h1 { .vc-trans-modal-title {
margin: 0; margin: 0;
} }
@ -17,7 +17,7 @@
font-weight: 400; font-weight: 400;
} }
.vc-trans-accessory svg { .vc-trans-accessory-icon {
margin-right: 0.25em; margin-right: 0.25em;
} }

View file

@ -23,12 +23,12 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord"; import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy, findExportedComponentLazy, findStoreLazy } from "@webpack"; import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
import { buildSeveralUsers } from "../typingTweaks"; import { buildSeveralUsers } from "../typingTweaks";
const ThreeDots = findExportedComponentLazy("Dots", "AnimatedDots"); const ThreeDots = findComponentByCodeLazy(".dots,", "dotRadius:");
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const TypingStore = findStoreLazy("TypingStore"); const TypingStore = findStoreLazy("TypingStore");
@ -100,16 +100,24 @@ function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: s
{props => ( {props => (
<div className="vc-typing-indicator" {...props}> <div className="vc-typing-indicator" {...props}>
{((settings.store.indicatorMode & IndicatorMode.Avatars) === IndicatorMode.Avatars) && ( {((settings.store.indicatorMode & IndicatorMode.Avatars) === IndicatorMode.Avatars) && (
<UserSummaryItem <div
users={typingUsersArray.map(id => UserStore.getUser(id))} onClick={e => {
guildId={guildId} e.stopPropagation();
renderIcon={false} e.preventDefault();
max={3} }}
showDefaultAvatarsForNullUsers onKeyPress={e => e.stopPropagation()}
showUserPopout >
size={16} <UserSummaryItem
className="vc-typing-indicator-avatars" users={typingUsersArray.map(id => UserStore.getUser(id))}
/> guildId={guildId}
renderIcon={false}
max={3}
showDefaultAvatarsForNullUsers
showUserPopout
size={16}
className="vc-typing-indicator-avatars"
/>
</div>
)} )}
{((settings.store.indicatorMode & IndicatorMode.Dots) === IndicatorMode.Dots) && ( {((settings.store.indicatorMode & IndicatorMode.Dots) === IndicatorMode.Dots) && (
<div className="vc-typing-indicator-dots"> <div className="vc-typing-indicator-dots">

View file

@ -21,12 +21,18 @@ import { ImageInvisible, ImageVisible } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common"; import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common";
import { MessageSnapshot } from "@webpack/types";
const EMBED_SUPPRESSED = 1 << 2; const EMBED_SUPPRESSED = 1 << 2;
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, embeds, flags, id: messageId } }) => { const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channel, message: { author, messageSnapshots, embeds, flags, id: messageId } }) => {
const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0; const isEmbedSuppressed = (flags & EMBED_SUPPRESSED) !== 0;
if (!isEmbedSuppressed && !embeds.length) return; const hasEmbedsInSnapshots = messageSnapshots.some(
(snapshot: MessageSnapshot) => snapshot?.message.embeds.length
);
if (!isEmbedSuppressed && !embeds.length && !hasEmbedsInSnapshots) return;
const hasEmbedPerms = channel.isPrivate() || !!(PermissionStore.getChannelPermissions({ id: channel.id }) & PermissionsBits.EMBED_LINKS); const hasEmbedPerms = channel.isPrivate() || !!(PermissionStore.getChannelPermissions({ id: channel.id }) & PermissionsBits.EMBED_LINKS);
if (author.id === UserStore.getCurrentUser().id && !hasEmbedPerms) return; if (author.id === UserStore.getCurrentUser().id && !hasEmbedPerms) return;

View file

@ -22,7 +22,7 @@ const VoiceStateStore = findStoreLazy("VoiceStateStore");
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const Avatar = findComponentByCodeLazy(".status)/2):0"); const Avatar = findComponentByCodeLazy(".status)/2):0");
const GroupDMAvatars = findComponentByCodeLazy(".AvatarSizeSpecs[", "getAvatarURL"); const GroupDMAvatars = findComponentByCodeLazy("frontSrc:", "getAvatarURL");
const ActionButtonClasses = findByPropsLazy("actionButton", "highlight"); const ActionButtonClasses = findByPropsLazy("actionButton", "highlight");

View file

@ -1,12 +0,0 @@
:is([class*="userProfile"], [class*="userPopout"]) [class*="bannerPremium"] {
background: center / cover no-repeat;
}
[class*="NonPremium"]:has([class*="bannerPremium"]) [class*="avatarPositionNormal"],
[class*="PremiumWithoutBanner"]:has([class*="bannerPremium"]) [class*="avatarPositionPremiumNoBanner"] {
top: 76px;
}
[style*="background-image"] [class*="background_"] {
background-color: transparent !important;
}

View file

@ -21,8 +21,6 @@ import { Link } from "@components/Link";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import style from "./index.css?managed";
const API_URL = "https://usrbg.is-hardly.online/users"; const API_URL = "https://usrbg.is-hardly.online/users";
interface UsrbgApiReturn { interface UsrbgApiReturn {

View file

@ -1,16 +1,16 @@
.vc-toolbox-btn, .vc-toolbox-btn,
.vc-toolbox-btn>svg { .vc-toolbox-icon {
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
} }
.vc-toolbox-btn>svg { .vc-toolbox-icon {
color: var(--interactive-normal); color: var(--interactive-normal);
} }
.vc-toolbox-btn[class*="selected"]>svg { .vc-toolbox-btn[class*="selected"] .vc-toolbox-icon {
color: var(--interactive-active); color: var(--interactive-active);
} }
.vc-toolbox-btn:hover>svg { .vc-toolbox-btn:hover .vc-toolbox-icon {
color: var(--interactive-hover); color: var(--interactive-hover);
} }

View file

@ -23,11 +23,11 @@ import { Settings, useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findExportedComponentLazy } from "@webpack"; import { findComponentByCodeLazy } from "@webpack";
import { Menu, Popout, useState } from "@webpack/common"; import { Menu, Popout, useState } from "@webpack/common";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider"); const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
function VencordPopout(onClose: () => void) { function VencordPopout(onClose: () => void) {
const { useQuickCss } = useSettings(["useQuickCss"]); const { useQuickCss } = useSettings(["useQuickCss"]);
@ -88,7 +88,7 @@ function VencordPopout(onClose: () => void) {
function VencordPopoutIcon(isShown: boolean) { function VencordPopoutIcon(isShown: boolean) {
return ( return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 27 27" width={24} height={24}> <svg viewBox="0 0 27 27" width={24} height={24} className="vc-toolbox-icon">
<path fill="currentColor" d={isShown ? "M9 0h1v1h1v2h1v2h3V3h1V1h1V0h1v2h1v2h1v7h-1v-1h-3V9h1V6h-1v4h-3v1h1v-1h2v1h3v1h-1v1h-3v2h1v1h1v1h1v3h-1v4h-2v-1h-1v-4h-1v4h-1v1h-2v-4H9v-3h1v-1h1v-1h1v-2H9v-1H8v-1h3V6h-1v3h1v1H8v1H7V4h1V2h1M5 19h2v1h1v1h1v3H4v-1h2v-1H4v-2h1m15-1h2v1h1v2h-2v1h2v1h-5v-3h1v-1h1m4 3h4v1h-4" : "M0 0h7v1H6v1H5v1H4v1H3v1H2v1h5v1H0V6h1V5h1V4h1V3h1V2h1V1H0m13 2h5v1h-1v1h-1v1h-1v1h3v1h-5V7h1V6h1V5h1V4h-3m8 5h1v5h1v-1h1v1h-1v1h1v-1h1v1h-1v3h-1v1h-2v1h-1v1h1v-1h2v-1h1v2h-1v1h-2v1h-1v-1h-1v1h-6v-1h-1v-1h-1v-2h1v1h2v1h3v1h1v-1h-1v-1h-3v-1h-4v-4h1v-2h1v-1h1v-1h1v2h1v1h1v-1h1v1h-1v1h2v-2h1v-2h1v-1h1M8 14h2v1H9v4h1v2h1v1h1v1h1v1h4v1h-6v-1H5v-1H4v-5h1v-1h1v-2h2m17 3h1v3h-1v1h-1v1h-1v2h-2v-2h2v-1h1v-1h1m1 0h1v3h-1v1h-2v-1h1v-1h1"} /> <path fill="currentColor" d={isShown ? "M9 0h1v1h1v2h1v2h3V3h1V1h1V0h1v2h1v2h1v7h-1v-1h-3V9h1V6h-1v4h-3v1h1v-1h2v1h3v1h-1v1h-3v2h1v1h1v1h1v3h-1v4h-2v-1h-1v-4h-1v4h-1v1h-2v-4H9v-3h1v-1h1v-1h1v-2H9v-1H8v-1h3V6h-1v3h1v1H8v1H7V4h1V2h1M5 19h2v1h1v1h1v3H4v-1h2v-1H4v-2h1m15-1h2v1h1v2h-2v1h2v1h-5v-3h1v-1h1m4 3h4v1h-4" : "M0 0h7v1H6v1H5v1H4v1H3v1H2v1h5v1H0V6h1V5h1V4h1V3h1V2h1V1H0m13 2h5v1h-1v1h-1v1h-1v1h3v1h-5V7h1V6h1V5h1V4h-3m8 5h1v5h1v-1h1v1h-1v1h1v-1h1v1h-1v3h-1v1h-2v1h-1v1h1v-1h2v-1h1v2h-1v1h-2v1h-1v-1h-1v1h-6v-1h-1v-1h-1v-2h1v1h2v1h3v1h1v-1h-1v-1h-3v-1h-4v-4h1v-2h1v-1h1v-1h1v2h1v1h1v-1h1v1h-1v1h2v-2h1v-2h1v-1h1M8 14h2v1H9v4h1v2h1v1h1v1h1v1h4v1h-6v-1H5v-1H4v-5h1v-1h1v-2h2m17 3h1v3h-1v1h-1v1h-1v2h-2v-2h2v-1h1v-1h1m1 0h1v3h-1v1h-2v-1h1v-1h1"} />
</svg> </svg>
); );

Some files were not shown because too many files have changed in this diff Show more