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",
"private": "true",
"version": "1.11.0",
"version": "1.11.3",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {

View file

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

View file

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

View file

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

View file

@ -18,8 +18,8 @@
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 });
}

View file

@ -18,7 +18,7 @@
import { DefinedSettings, PluginOptionBase } from "@utils/types";
export interface ISettingElementProps<T extends PluginOptionBase> {
interface ISettingElementPropsBase<T> {
option: T;
onChange(newValue: any): void;
pluginSettings: {
@ -30,6 +30,9 @@ export interface ISettingElementProps<T extends PluginOptionBase> {
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 "./SettingBooleanComponent";
export * from "./SettingCustomComponent";

View file

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

View file

@ -63,10 +63,7 @@
height: 8em;
display: flex;
flex-direction: column;
}
.vc-plugins-info-card div {
line-height: 32px;
gap: 0.25em;
}
.vc-plugins-restart-card {
@ -76,11 +73,11 @@
color: var(--info-warning-text);
}
.vc-plugins-restart-card button {
.vc-plugins-restart-button {
margin-top: 0.5em;
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);
}

View file

@ -62,14 +62,21 @@ async function runReporter() {
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
} catch (e) {
let logMessage = searchType;
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") 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") {
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
if (args[0].$$vencordProps != null) {
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);
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);
}

View file

@ -28,7 +28,7 @@ import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
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 { Forms, Toasts, UserStore } from "@webpack/common";
import { User } from "discord-types/general";
@ -144,8 +144,8 @@ export default definePlugin({
closeModal(modalKey);
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
}}>
<Modals.ModalRoot {...props}>
<Modals.ModalHeader>
<ModalRoot {...props}>
<ModalHeader>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<Forms.FormTitle
tag="h2"
@ -159,8 +159,8 @@ export default definePlugin({
Vencord Donor
</Forms.FormTitle>
</Flex>
</Modals.ModalHeader>
<Modals.ModalContent>
</ModalHeader>
<ModalContent>
<Flex>
<img
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!!
</Forms.FormText>
</div>
</Modals.ModalContent>
<Modals.ModalFooter>
</ModalContent>
<ModalFooter>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<DonateButton />
</Flex>
</Modals.ModalFooter>
</Modals.ModalRoot>
</ModalFooter>
</ModalRoot>
</ErrorBoundary>
));
},

View file

@ -12,11 +12,16 @@ export default definePlugin({
description: "API to add buttons to the chat input",
authors: [Devs.Ven],
patches: [{
patches: [
{
find: '"sticker")',
replacement: {
match: /return\(!\i\.\i&&(?=\(\i\.isDM.+?(\i)\.push\(.{0,50}"gift")/,
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,
replacement: {
match: /Menu,{(?<=\.jsxs?\)\(\i\.Menu,{)/g,
replace: "$&contextMenuApiArguments:typeof arguments!=='undefined'?arguments:[],"
noWarn: true,
replacement: [
{
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",
replacement: {
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
match: /(\{openWarningPopout:.{0,100}type:this.props.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);)(?<=\)\(({.+?})\)\.then.+?)/,
// 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}` +
// https://regex101.com/r/hBlXpl/1
match: /let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);(?<=\)\(({.+?})\)\.then.+?)/,
replace: (m, parsedMessage, channel, replyOptions, extra) => m +
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
"return{shouldClear:false,shouldRefocus:true};"
}
@ -52,8 +49,7 @@ export default definePlugin({
replacement: {
match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
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}`
},
{
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})`
}
]

View file

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

View file

@ -17,14 +17,13 @@
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { useStateFromStores } from "@webpack/common";
import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Animations, useStateFromStores } from "@webpack/common";
import type { CSSProperties } from "react";
import { ExpandedGuildFolderStore, settings } from ".";
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
const Animations = findByPropsLazy("a", "animated", "useTransition");
const GuildsBar = findComponentByCodeLazy('("guildsnav")');
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
{
predicate: () => !settings.store.keepIcons,
match: /(?<=#{intl::SERVER_FOLDER_PLACEHOLDER}.+?useTransition\)\()/,
replace: "$self.shouldShowTransition(arguments[0])&&"
match: /(?=,\{from:\{height)/,
replace: "&&$self.shouldShowTransition(arguments[0])"
},
// 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.colorString) {
children.push(
children.unshift(
<Menu.MenuItem
id="vc-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) {
children.push(
<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 { Devs } from "@utils/constants";
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 { RenameButton } from "./components/RenameButton";
@ -34,7 +34,7 @@ const UserSettingsModal = findByPropsLazy("saveAccountChanges", "open");
const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer");
const SessionIconClasses = findByPropsLazy("sessionIcon");
const BlobMask = findExportedComponentLazy("BlobMask");
const BlobMask = findComponentByCodeLazy("!1,lowerBadgeSize:");
const settings = definePluginSettings({
backgroundCheck: {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -63,7 +63,7 @@ function makeShortcuts() {
default:
const uniqueMatches = [...new Set(matches)];
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];
}
@ -165,11 +165,38 @@ function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) {
const currentVal = val.getter();
if (!currentVal || val.preload === false) return currentVal;
const value = currentVal[SYM_LAZY_GET]
? forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED]
: currentVal;
function unwrapProxy(value: any) {
if (value[SYM_LAZY_GET]) {
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;
}

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.
// See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673
{
find: ".ENTER&&(!",
find: ".selectPreviousCommandOption(",
replacement: {
match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/,
replace: "$self.shouldSubmit($1, $2)"
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
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"
viewBox="0 0 36 36"
aria-label="Toggle Dearrow"
className="vc-dearrow-icon"
>
<path
fill="#1213BD"

View file

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

View file

@ -17,7 +17,6 @@ import DecorSection from "./ui/components/DecorSection";
export const settings = definePluginSettings({
changeDecoration: {
type: OptionType.COMPONENT,
description: "Change your avatar decoration",
component() {
if (!Vencord.Plugins.plugins.Decor.started) return <Forms.FormText>
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,
predicate: () => settings.store.enableEmojiBypass,
replacement: [
@ -256,8 +256,11 @@ export default definePlugin({
},
{
// Disallow the emoji for premium locked if the intention doesn't allow it
match: /!\i\.\i\.canUseEmojisEverywhere\(\i\)/,
replace: m => `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
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

View file

@ -22,11 +22,11 @@ import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { NoopComponent } from "@utils/react";
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 { Message } from "discord-types/general";
const { useMessageMenu } = findByPropsLazy("useMessageMenu");
const useMessageMenu = findByCodeLazy(".MESSAGE,commandTargetId:");
interface CopyIdMenuItemProps {
id: string;

View file

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

View file

@ -25,7 +25,7 @@ import { findComponentByCodeLazy } from "@webpack";
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")!;

View file

@ -16,78 +16,92 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "./styles.css";
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 { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
import definePlugin from "@utils/types";
import { ChannelStore } from "@webpack/common";
let style: HTMLStyleElement;
import { MessageSnapshot } from "@webpack/types";
const KEY = "HideAttachments_HiddenIds";
let hiddenMessages: Set<string> = new Set();
const getHiddenMessages = () => get(KEY).then(set => {
hiddenMessages = set ?? new Set<string>();
let hiddenMessages = new Set<string>();
async function getHiddenMessages() {
hiddenMessages = await get(KEY) ?? new Set();
return hiddenMessages;
});
}
const saveHiddenMessages = (ids: Set<string>) => set(KEY, ids);
migratePluginSettings("HideMedia", "HideAttachments");
export default definePlugin({
name: "HideAttachments",
description: "Hide attachments and Embeds for individual messages via hover button",
name: "HideMedia",
description: "Hide attachments and embeds for individual messages via hover button",
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) {
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);
return {
label: isHidden ? "Show Attachments" : "Hide Attachments",
label: isHidden ? "Show Media" : "Hide Media",
icon: isHidden ? ImageVisible : ImageInvisible,
message: msg,
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() {
await getHiddenMessages();
await this.buildCss();
},
stop() {
deleteStyle("HideAttachments");
hiddenMessages.clear();
},
async buildCss() {
const elements = [...hiddenMessages].map(id => `#message-accessories-${id}`).join(",");
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
});
shouldHide(messageId: string) {
return hiddenMessages.has(messageId);
},
async toggleHide(id: string) {
async toggleHide(channelId: string, messageId: string) {
const ids = await getHiddenMessages();
if (!ids.delete(id))
ids.add(id);
if (!ids.delete(messageId))
ids.add(messageId);
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",
replacement: {
match: /if\((\i)<=(?:1<<30|1073741824)\)return/,
match: /if\((\i)<=(?:0x40000000|(?:1<<30|1073741824))\)return/,
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);
if (ignoredActivityIndex === -1) settings.store.ignoredActivities.push(activity);
else settings.store.ignoredActivities.splice(ignoredActivityIndex, 1);
recalculateActivities();
}
function recalculateActivities() {
@ -149,8 +147,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) {
const settings = definePluginSettings({
importCustomRPC: {
type: OptionType.COMPONENT,
description: "",
component: () => <ImportCustomRPCComponent />
component: ImportCustomRPCComponent
},
listMode: {
type: OptionType.SELECT,
@ -170,7 +167,6 @@ const settings = definePluginSettings({
},
idsList: {
type: OptionType.COMPONENT,
description: "",
default: "",
onChange(newValue: string) {
const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean));
@ -245,7 +241,7 @@ export default definePlugin({
find: '"LocalActivityStore"',
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);`
}
]

View file

@ -195,6 +195,7 @@ export const Magnifier = ErrorBoundary.wrap<MagnifierProps>(({ instance, size: i
/>
) : (
<img
className={cl("image")}
ref={imageRef}
style={{
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"]);
children.push(

View file

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

View file

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

View file

@ -142,14 +142,21 @@ for (const p of neededApiPlugins) {
for (const p of pluginsValues) {
if (p.settings) {
p.settings.pluginName = p.name;
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];
p.options[name] = { ...def, ...checks };
}
}
if (def.onChange != null) {
SettingsStore.addChangeListener(`plugins.${p.name}.${name}`, def.onChange);
if (p.options) {
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 presenceStore = findByPropsLazy("getLocalPresence");
const PresenceStore = findByPropsLazy("getLocalPresence");
async function getApplicationAsset(key: string): Promise<string> {
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
@ -124,6 +124,11 @@ const settings = definePluginSettings({
type: OptionType.BOOLEAN,
default: true,
},
hideWithActivity: {
description: "Hide Last.fm presence if you have any other presence",
type: OptionType.BOOLEAN,
default: false,
},
statusName: {
description: "custom status text",
type: OptionType.STRING,
@ -274,13 +279,17 @@ export default definePlugin({
},
async getActivity(): Promise<Activity | null> {
if (settings.store.hideWithSpotify) {
for (const activity of presenceStore.getActivities()) {
if (activity.type === ActivityType.LISTENING && activity.application_id !== applicationId) {
// there is already music status because of Spotify or richerCider (probably more)
if (settings.store.hideWithActivity) {
if (PresenceStore.getActivities().some(a => a.application_id !== applicationId)) {
return null;
}
}
if (settings.store.hideWithSpotify) {
if (PresenceStore.getActivities().some(a => a.type === ActivityType.LISTENING && a.application_id !== applicationId)) {
// there is already music status because of Spotify or richerCider (probably more)
return null;
}
}
const trackData = await this.fetchTrackData();

View file

@ -57,7 +57,7 @@ export default definePlugin({
{
find: ".ROLE_MENTION)",
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])"
}
}],

View file

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

View file

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

View file

@ -1,24 +1,8 @@
/* Message content highlighting */
.messagelogger-deleted [class*="contents"] > :is(div, h1, h2, h3, p) {
color: var(--status-danger, #f04747) !important;
}
/* Markdown title highlighting */
.messagelogger-deleted [class*="contents"] :is(h1, h2, h3) {
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;
.messagelogger-deleted {
--text-normal: var(--status-danger, #f04747);
--interactive-normal: var(--status-danger, #f04747);
--text-muted: var(--status-danger, #f04747);
--embed-title: var(--red-460, #be3535);
--text-link: var(--red-460, #be3535);
--header-primary: var(--red-460, #be3535);
}

View file

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

View file

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

View file

@ -89,7 +89,7 @@ export default definePlugin({
settings,
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);
if (oldTags != null) {
// @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/>.
*/
import { Settings } from "@api/Settings";
import { definePluginSettings, migratePluginSetting } from "@api/Settings";
import { Devs } from "@utils/constants";
import { runtimeHashMessageKey } from "@utils/intlHash";
import { Logger } from "@utils/Logger";
@ -32,10 +32,29 @@ interface MessageDeleteProps {
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({
name: "NoBlockedMessages",
description: "Hides all blocked messages from chat completely.",
authors: [Devs.rushii, Devs.Samu],
description: "Hides all blocked/ignored messages from chat completely",
authors: [Devs.rushii, Devs.Samu, Devs.jamesbt365],
settings,
patches: [
{
find: "#{intl::BLOCKED_MESSAGES_HIDE}",
@ -51,38 +70,40 @@ export default definePlugin({
'"ReadStateStore"'
].map(find => ({
find,
predicate: () => Settings.plugins.NoBlockedMessages.ignoreBlockedMessages === true,
predicate: () => settings.store.ignoreMessages,
replacement: [
{
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 {
return RelationshipStore.isBlocked(message.author.id);
} catch (e) {
new Logger("NoBlockedMessages").error("Failed to check if user is blocked:", e);
if (RelationshipStore.isBlocked(message.author.id)) {
return true;
}
},
shouldHide(props: MessageDeleteProps) {
try {
return props.collapsedReason() === i18n.t[runtimeHashMessageKey("BLOCKED_MESSAGE_COUNT")]();
return settings.store.applyToIgnoredUsers && RelationshipStore.isIgnored(message.author.id);
} catch (e) {
console.error(e);
}
new Logger("NoBlockedMessages").error("Failed to check if user is blocked or ignored:", e);
return false;
}
},
shouldHide(props: MessageDeleteProps): boolean {
try {
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) {
console.error(e);
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,
patches: [
{
find: "_ensureAudio(){",
find: "ensureAudio(){",
replacement: {
match: /(?=Math\.min\(\i\.\i\.getOutputVolume\(\)\/100)/,
match: /(?=Math\.min\(\i\.\i\.getOutputVolume\(\)\/100)/g,
replace: "$self.settings.store.notificationVolume/100*"
},
},

View file

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

View file

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

View file

@ -157,7 +157,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
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
? role?.name ?? "Unknown Role"

View file

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

View file

@ -6,7 +6,7 @@
import { classNameFactory } from "@api/Styles";
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 { 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 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"/);

View file

@ -155,7 +155,7 @@ export function moveChannel(channelId: string, direction: -1 | 1) {
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() {
if (Settings.plugins.PinDMs.dmSectioncollapsed != null) {
settings.store.dmSectionCollapsed = Settings.plugins.PinDMs.dmSectioncollapsed;

View file

@ -25,7 +25,7 @@ import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
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 { User } from "discord-types/general";
@ -70,7 +70,9 @@ const 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 tooltip = platform === "embedded"
@ -79,7 +81,7 @@ const PlatformIcon = ({ platform, status, small }: { platform: Platform, status:
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) {

View file

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

View file

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

View file

@ -39,7 +39,7 @@ function BlockedUser({ user, isBusy, setIsBusy }: { user: ReviewDBUser; isBusy:
return (
<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>
<UnblockButton
onClick={isBusy ? undefined : async () => {

View file

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

View file

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

View file

@ -16,16 +16,11 @@
border: 1px solid var(--profile-message-input-border-color);
}
.vc-rdb-modal-footer > div {
.vc-rdb-modal-footer-wrapper {
width: 100%;
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 {
margin-bottom: 4px;
font-weight: bold;
@ -69,7 +64,7 @@
border-radius: 8px;
}
.vc-rdb-review-comment img {
.vc-rdb-review-comment [class*="avatar"] {
vertical-align: text-top;
}
@ -117,13 +112,13 @@
align-items: center;
}
.vc-rdb-block-modal-row img {
.vc-rdb-block-modal-avatar {
border-radius: 50%;
height: 2em;
width: 2em;
}
.vc-rdb-block-modal img::before {
.vc-rdb-block-modal-avatar::before {
content: "";
display: block;
width: 100%;

View file

@ -68,15 +68,16 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi
return (
<ModalRoot {...rootProps}>
<ModalHeader className={cl("modal-header")}>
<Forms.FormTitle tag="h2">
<Forms.FormTitle tag="h2" className={cl("modal-title")}>
Timestamp Picker
</Forms.FormTitle>
<ModalCloseButton onClick={close} />
<ModalCloseButton onClick={close} className={cl("modal-close-button")} />
</ModalHeader>
<ModalContent className={cl("modal-content")}>
<input
className={cl("date-picker")}
type="datetime-local"
value={value}
onChange={e => setValue(e.currentTarget.value)}
@ -86,6 +87,7 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi
/>
<Forms.FormTitle>Timestamp Format</Forms.FormTitle>
<div className={cl("format-select")}>
<Select
options={
Formats.map(m => ({
@ -103,6 +105,7 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi
)}
renderOptionValue={() => rendered}
/>
</div>
<Forms.FormTitle className={Margins.bottom8}>Preview</Forms.FormTitle>
<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);
color: var(--text-normal);
width: 95%;
@ -12,35 +12,28 @@
font-size: 100%;
}
.vc-st-format-label,
.vc-st-format-label span {
background-color: transparent;
}
.vc-st-modal-content [class|="select"] {
.vc-st-format-select {
margin-bottom: 1em;
--background-modifier-accent: transparent;
}
.vc-st-modal-content [class|="select"] span {
background-color: var(--input-background);
.vc-st-format-label {
--background-modifier-accent: transparent;
}
.vc-st-modal-header {
place-content: center space-between;
}
.vc-st-modal-header h1 {
.vc-st-modal-title {
margin: 0;
}
.vc-st-modal-header button {
.vc-st-modal-close-button {
padding: 0;
}
.vc-st-preview-text {
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 {
ServerInfo,
Friends,
BlockedUsers
BlockedUsers,
IgnoredUsers
}
interface GuildProps {
@ -44,7 +45,8 @@ interface RelationshipProps extends GuildProps {
const fetched = {
friends: false,
blocked: false
blocked: false,
ignored: false
};
function renderTimestamp(timestamp: number) {
@ -56,10 +58,12 @@ function renderTimestamp(timestamp: number) {
function GuildInfoModal({ guild }: GuildProps) {
const [friendCount, setFriendCount] = useState<number>();
const [blockedCount, setBlockedCount] = useState<number>();
const [ignoredCount, setIgnoredCount] = useState<number>();
useEffect(() => {
fetched.friends = false;
fetched.blocked = false;
fetched.ignored = false;
}, []);
const [currentTab, setCurrentTab] = useState(Tabs.ServerInfo);
@ -90,6 +94,7 @@ function GuildInfoModal({ guild }: GuildProps) {
<div className={cl("header")}>
{iconUrl
? <img
className={cl("icon")}
src={iconUrl}
alt=""
onClick={() => openImageModal({
@ -132,12 +137,19 @@ function GuildInfoModal({ guild }: GuildProps) {
>
Blocked Users{blockedCount !== undefined ? ` (${blockedCount})` : ""}
</TabBar.Item>
<TabBar.Item
className={cl("tab", { selected: currentTab === Tabs.IgnoredUsers })}
id={Tabs.IgnoredUsers}
>
Ignored Users{ignoredCount !== undefined ? ` (${ignoredCount})` : ""}
</TabBar.Item>
</TabBar>
<div className={cl("tab-content")}>
{currentTab === Tabs.ServerInfo && <ServerInfoTab guild={guild} />}
{currentTab === Tabs.Friends && <FriendsTab guild={guild} setCount={setFriendCount} />}
{currentTab === Tabs.BlockedUsers && <BlockedUsersTab guild={guild} setCount={setBlockedCount} />}
{currentTab === Tabs.IgnoredUsers && <IgnoredUserTab guild={guild} setCount={setIgnoredCount} />}
</div>
</div>
);
@ -159,6 +171,7 @@ function Owner(guildId: string, owner: User) {
return (
<div className={cl("owner")}>
<img
className={cl("owner-avatar")}
src={ownerAvatarUrl}
alt=""
onClick={() => openImageModal({
@ -211,7 +224,13 @@ function BlockedUsersTab({ guild, setCount }: RelationshipProps) {
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 members = [] as string[];

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -33,6 +33,7 @@ export function VerifiedIcon() {
forcedIconColor={forcedIconColor}
size={16}
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-connection-name">{connection.name}</span>
{connection.verified && <VerifiedIcon />}
<TooltipIcon height={16} width={16} />
<TooltipIcon height={16} width={16} className="vc-sc-tooltip-icon" />
</span>
}
key={connection.id}

View file

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

View file

@ -18,6 +18,7 @@
import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { classes } from "@utils/misc";
import { formatDuration } from "@utils/text";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
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 { sortPermissionOverwrites } from "../../permissionsViewer/utils";
import { settings } from "..";
import { cl, settings } from "..";
const enum SortOrderTypes {
LATEST_ACTIVITY = 0,
@ -168,19 +169,19 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
}, [channelId]);
return (
<div className={ChatScrollClasses.auto + " " + ChatScrollClasses.customTheme + " " + ChatClasses.chatContent + " " + "shc-lock-screen-outer-container"}>
<div className="shc-lock-screen-container">
<img className="shc-lock-screen-logo" src={HiddenChannelLogo} />
<div className={classes(ChatScrollClasses.auto, ChatScrollClasses.customTheme, ChatScrollClasses.managedReactiveScroller)}>
<div className={cl("container")}>
<img className={cl("logo")} src={HiddenChannelLogo} />
<div className="shc-lock-screen-heading-container">
<Text variant="heading-xxl/bold">This is a {!PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) ? "hidden" : "locked"} {ChannelTypesToChannelNames[type]} channel.</Text>
<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>
{channel.isNSFW() &&
<Tooltip text="NSFW">
{({ onMouseLeave, onMouseEnter }) => (
<svg
onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter}
className="shc-lock-screen-heading-nsfw-icon"
className={cl("heading-nsfw-icon")}
width="32"
height="32"
viewBox="0 0 48 48"
@ -202,7 +203,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
)}
{channel.isForumChannel() && topic && topic.length > 0 && (
<div className="shc-lock-screen-topic-container">
<div className={cl("topic-container")}>
{Parser.parseTopic(topic, false, { channelId })}
</div>
)}
@ -213,7 +214,6 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
<Timestamp timestamp={new Date(SnowflakeUtils.extractTimestamp(lastMessageId))} />
</Text>
}
{lastPinTimestamp &&
<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>
}
{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>
{Parser.defaultRules[defaultReactionEmoji.emojiName ? "emoji" : "customEmoji"].react({
name: defaultReactionEmoji.emojiName
@ -258,29 +258,29 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
src: defaultReactionEmoji.emojiName
? EmojiUtils.getURL(defaultReactionEmoji.emojiName)
: void 0
}, void 0, { key: "0" })}
}, void 0, { key: 0 })}
</div>
}
{channel.hasFlag(ChannelFlags.REQUIRE_TAG) &&
<Text variant="text-md/normal">Posts on this forum require a tag to be set.</Text>
}
{availableTags && availableTags.length > 0 &&
<div className="shc-lock-screen-tags-container">
<div className={cl("tags-container")}>
<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} />)}
</div>
</div>
}
<div className="shc-lock-screen-allowed-users-and-roles-container">
<div className="shc-lock-screen-allowed-users-and-roles-container-title">
{Settings.plugins.PermissionsViewer.enabled && (
<div className={cl("allowed-users-and-roles-container")}>
<div className={cl("allowed-users-and-roles-container-title")}>
{Vencord.Plugins.isPluginEnabled("PermissionsViewer") && (
<Tooltip text="Permission Details">
{({ onMouseLeave, onMouseEnter }) => (
<button
onMouseLeave={onMouseLeave}
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)}
>
<svg
@ -300,7 +300,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
<button
onMouseLeave={onMouseLeave}
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}
>
<svg

View file

@ -19,8 +19,10 @@
import "./style.css";
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
import { canonicalizeMatch } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
@ -31,6 +33,8 @@ import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen";
const ChannelListClasses = findByPropsLazy("modeMuted", "modeSelected", "unread", "icon");
export const cl = classNameFactory("vc-shc-");
const enum ShowMode {
LockIcon,
HiddenIconWithMutedStyle
@ -108,8 +112,11 @@ export default definePlugin({
},
{
// Prevent Discord from trying to connect to hidden voice channels
match: /(?=&&\i\.\i\.selectVoiceChannel\((\i)\.id\))/,
replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})`
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
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
@ -122,8 +129,11 @@ export default definePlugin({
{
find: ".AUDIENCE),{isSubscriptionGated",
replacement: {
match: /!(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/,
replace: (m, channel) => `${m}&&!$self.isHiddenChannel(${channel})`
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
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
{
match: /(?<=\.wrapper:\i\.notInteractive,)(.+?)if\((\i)\)return (\i\.MUTED);/,
replace: (_, otherClasses, isMuted, mutedClassExpression) => `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return "";`
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
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
predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle,
match: /\.LOCKED;if\((?<={channel:(\i).+?)/,
replace: (m, channel) => `${m}!$self.isHiddenChannel(${channel})&&`
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
match: /(?<=\.LOCKED(?:;if\(|:))(?<={channel:(\i).+?)/,
replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&`
},
{
// Hide unreads
@ -539,7 +553,7 @@ export default definePlugin({
aria-hidden={true}
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>
), { noop: true }),
@ -549,14 +563,14 @@ export default definePlugin({
<svg
onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter}
className={ChannelListClasses.icon + " " + "shc-hidden-channel-icon"}
className={classes(ChannelListClasses.icon, cl("hidden-channel-icon"))}
width="24"
height="24"
viewBox="0 0 24 24"
aria-hidden={true}
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>
)}
</Tooltip>

View file

@ -1,43 +1,31 @@
.shc-lock-screen-outer-container {
overflow: hidden scroll;
flex: 1 1 auto;
height: 100%;
width: 100%;
}
.shc-lock-screen-container {
.vc-shc-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
gap: 0.65em;
margin: 0.5em 0;
min-height: 100%;
}
.shc-lock-screen-container > * {
margin: 5px;
.vc-shc-logo {
width: 12em;
height: 12em;
}
.shc-lock-screen-logo {
width: 180px;
height: 180px;
}
.shc-lock-screen-heading-container {
.vc-shc-heading-container {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5em;
}
.shc-lock-screen-heading-container > * {
margin: inherit;
}
.shc-lock-screen-heading-nsfw-icon {
.vc-shc-heading-nsfw-icon {
color: var(--text-normal);
}
.shc-lock-screen-topic-container {
.vc-shc-topic-container {
color: var(--text-normal);
background: var(--bg-overlay-3, var(--background-secondary));
border-radius: 5px;
@ -45,91 +33,75 @@
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));
border-radius: 5px;
padding: 10px;
padding: 0.75em;
gap: 0.75em;
max-width: 70vw;
}
.shc-lock-screen-tags-container > * {
margin: inherit;
}
.shc-lock-screen-tags {
.vc-shc-tags {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 8px;
gap: 0.35em;
}
.shc-evenodd-fill-current-color {
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 {
.vc-shc-allowed-users-and-roles-container {
display: flex;
flex-direction: column;
align-items: center;
background: var(--bg-overlay-3, var(--background-secondary));
border-radius: 5px;
padding: 10px;
padding: 0.75em;
max-width: 70vw;
}
.shc-lock-screen-allowed-users-and-roles-container-title {
.vc-shc-allowed-users-and-roles-container-title {
display: flex;
flex-direction: row;
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;
margin-left: 5px;
cursor: pointer;
display: flex;
align-items: center;
}
.shc-lock-screen-allowed-users-and-roles-container-toggle-btn > svg {
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;
margin-right: 5px;
cursor: pointer;
display: flex;
align-items: center;
}
.shc-lock-screen-allowed-users-and-roles-container-permdetails-btn > svg {
color: var(--text-normal);
}
.shc-lock-screen-allowed-users-and-roles-container > [class^="members"] {
margin-left: 10px;
.vc-shc-allowed-users-and-roles-container > [class^="members"] {
margin-left: 12px;
flex-wrap: wrap;
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}",
replacement: [
{
match: /(\i)\.Tooltip,{(text:.{0,30}#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}\))/,
replace: "$self.TooltipWrapper,{message:arguments[0].message,$2"
match: /\i\.\i,{(text:.{0,30}#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}\))/,
replace: "$self.TooltipWrapper,{message:arguments[0].message,$1"
}
]
}

View file

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

View file

@ -30,22 +30,17 @@
background-color: var(--background-modifier-selected);
}
.vc-spotify-button svg {
.vc-spotify-button-icon {
height: 24px;
width: 24px;
}
[class*="vc-spotify-shuffle"] > svg,
[class*="vc-spotify-repeat"] > svg {
.vc-spotify-shuffle .vc-spotify-button-icon,
.vc-spotify-repeat .vc-spotify-button-icon {
width: 22px;
height: 22px;
}
.vc-spotify-button svg path {
width: 100%;
height: 100%;
}
/* .vc-spotify-button:hover {
filter: brightness(1.3);
} */
@ -87,12 +82,19 @@
gap: 0.5em;
}
#vc-spotify-info-wrapper img {
#vc-spotify-album-image {
height: 90%;
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%;
object-fit: contain;
}
@ -137,16 +139,6 @@
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 {
position: relative;
color: var(--text-normal);

View file

@ -45,7 +45,6 @@ const makeEmptyRuleArray = () => [makeEmptyRule()];
const settings = definePluginSettings({
replace: {
type: OptionType.COMPONENT,
description: "",
component: () => {
const { stringRules, regexRules } = settings.use(["stringRules", "regexRules"]);
@ -245,7 +244,7 @@ export default definePlugin({
},
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);
if (oldStringRules != null) {
settings.store.stringRules = oldStringRules;

View file

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

View file

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

View file

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

View file

@ -23,12 +23,12 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
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 { buildSeveralUsers } from "../typingTweaks";
const ThreeDots = findExportedComponentLazy("Dots", "AnimatedDots");
const ThreeDots = findComponentByCodeLazy(".dots,", "dotRadius:");
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const TypingStore = findStoreLazy("TypingStore");
@ -100,6 +100,13 @@ function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: s
{props => (
<div className="vc-typing-indicator" {...props}>
{((settings.store.indicatorMode & IndicatorMode.Avatars) === IndicatorMode.Avatars) && (
<div
onClick={e => {
e.stopPropagation();
e.preventDefault();
}}
onKeyPress={e => e.stopPropagation()}
>
<UserSummaryItem
users={typingUsersArray.map(id => UserStore.getUser(id))}
guildId={guildId}
@ -110,6 +117,7 @@ function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: s
size={16}
className="vc-typing-indicator-avatars"
/>
</div>
)}
{((settings.store.indicatorMode & IndicatorMode.Dots) === IndicatorMode.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 definePlugin from "@utils/types";
import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common";
import { MessageSnapshot } from "@webpack/types";
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;
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);
if (author.id === UserStore.getCurrentUser().id && !hasEmbedPerms) return;

View file

@ -22,7 +22,7 @@ const VoiceStateStore = findStoreLazy("VoiceStateStore");
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const Avatar = findComponentByCodeLazy(".status)/2):0");
const GroupDMAvatars = findComponentByCodeLazy(".AvatarSizeSpecs[", "getAvatarURL");
const GroupDMAvatars = findComponentByCodeLazy("frontSrc:", "getAvatarURL");
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 definePlugin, { OptionType } from "@utils/types";
import style from "./index.css?managed";
const API_URL = "https://usrbg.is-hardly.online/users";
interface UsrbgApiReturn {

View file

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

View file

@ -23,11 +23,11 @@ import { Settings, useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findExportedComponentLazy } from "@webpack";
import { findComponentByCodeLazy } from "@webpack";
import { Menu, Popout, useState } from "@webpack/common";
import type { ReactNode } from "react";
const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider");
const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
function VencordPopout(onClose: () => void) {
const { useQuickCss } = useSettings(["useQuickCss"]);
@ -88,7 +88,7 @@ function VencordPopout(onClose: () => void) {
function VencordPopoutIcon(isShown: boolean) {
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"} />
</svg>
);

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