This commit is contained in:
Ulysia 2025-02-06 15:22:59 +01:00
commit 30748b9930
78 changed files with 503 additions and 532 deletions

View file

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

View file

@ -43,20 +43,21 @@ interface DecoratorProps {
export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null;
type OnlyIn = "guilds" | "dms";
export const decorators = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>();
export const decoratorsFactories = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>();
export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) {
decorators.set(identifier, { render, onlyIn });
decoratorsFactories.set(identifier, { render, onlyIn });
}
export function removeMemberListDecorator(identifier: string) {
decorators.delete(identifier);
decoratorsFactories.delete(identifier);
}
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
export function __getDecorators(props: DecoratorProps): JSX.Element {
const isInGuild = !!(props.guildId);
return Array.from(
decorators.entries(),
const decorators = Array.from(
decoratorsFactories.entries(),
([key, { render: Decorator, onlyIn }]) => {
if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild))
return null;
@ -68,4 +69,10 @@ export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
);
}
);
return (
<div className="vc-member-list-decorators-wrapper">
{decorators}
</div>
);
}

View file

@ -48,23 +48,29 @@ export interface MessageDecorationProps {
}
export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null;
export const decorations = new Map<string, MessageDecorationFactory>();
export const decorationsFactories = new Map<string, MessageDecorationFactory>();
export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) {
decorations.set(identifier, decoration);
decorationsFactories.set(identifier, decoration);
}
export function removeMessageDecoration(identifier: string) {
decorations.delete(identifier);
decorationsFactories.delete(identifier);
}
export function __addDecorationsToMessage(props: MessageDecorationProps): (JSX.Element | null)[] {
return Array.from(
decorations.entries(),
export function __addDecorationsToMessage(props: MessageDecorationProps): JSX.Element {
const decorations = Array.from(
decorationsFactories.entries(),
([key, Decoration]) => (
<ErrorBoundary noop message={`Failed to render ${key} Message Decoration`} key={key}>
<Decoration {...props} />
</ErrorBoundary>
)
);
return (
<div className="vc-message-decorations-wrapper">
{decorations}
</div>
);
}

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

@ -16,6 +16,7 @@ export default definePlugin({
{
find: '"sticker")',
replacement: {
// 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)&&`

View file

@ -19,10 +19,15 @@
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import managedStyle from "./style.css?managed";
export default definePlugin({
name: "MemberListDecoratorsAPI",
description: "API to add decorators to member list (both in servers and DMs)",
authors: [Devs.TheSun, Devs.Ven],
managedStyle,
patches: [
{
find: ".lostPermission)",
@ -32,7 +37,7 @@ export default definePlugin({
replace: "$&vencordProps=$1,"
}, {
match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
replace: "$&(typeof vencordProps=='undefined'?null:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
}
]
},
@ -40,8 +45,8 @@ export default definePlugin({
find: "PrivateChannel.renderAvatar",
replacement: {
match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/,
replace: "decorators:[...Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]), $1?$2:null]"
replace: "decorators:[Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]),$1?$2:null]"
}
}
],
]
});

View file

@ -0,0 +1,11 @@
.vc-member-list-decorators-wrapper {
display: flex;
align-items: center;
justify-content: center;
gap: 0.25em;
}
.vc-member-list-decorators-wrapper:not(:empty) {
/* Margin to match default Discord decorators */
margin-left: 0.25em;
}

View file

@ -19,17 +19,22 @@
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import managedStyle from "./style.css?managed";
export default definePlugin({
name: "MessageDecorationsAPI",
description: "API to add decorations to messages",
authors: [Devs.TheSun],
managedStyle,
patches: [
{
find: '"Message Username"',
replacement: {
match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/,
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
replace: "$&,Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
}
}
],
]
});

View file

@ -0,0 +1,18 @@
.vc-message-decorations-wrapper {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.25em;
}
.vc-message-decorations-wrapper:not(:empty) {
/* Margin to match default Discord decorators */
margin-left: 0.25em;
/* Align vertically */
position: relative;
vertical-align: top;
top: 0.1rem;
height: calc(1rem + 4px);
max-height: calc(1rem + 4px)
}

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,6 +65,7 @@ export default definePlugin({
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
},
{
// 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

@ -23,7 +23,7 @@ import definePlugin, { ReporterTestable } from "@utils/types";
import { findByCodeLazy } from "@webpack";
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
const fetchApplicationsRPC = findByCodeLazy("APPLICATION_RPC(", "Client ID");
const fetchApplicationsRPC = findByCodeLazy('"Invalid Origin"', ".application");
async function lookupAsset(applicationId: string, key: string): Promise<string> {
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];

View file

@ -24,14 +24,14 @@ let style: HTMLStyleElement;
function setCss() {
style.textContent = `
.vc-nsfw-img [class^=imageWrapper] img,
.vc-nsfw-img [class^=wrapperPaused] video {
.vc-nsfw-img [class^=imageContainer],
.vc-nsfw-img [class^=wrapperPaused] {
filter: blur(${Settings.plugins.BlurNSFW.blurAmount}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);
}
}
`;
}
@ -54,7 +54,7 @@ export default definePlugin({
options: {
blurAmount: {
type: OptionType.NUMBER,
description: "Blur Amount",
description: "Blur Amount (in pixels)",
default: 10,
onChange: setCss
}

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,6 +7,7 @@
import "./clientTheme.css";
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
@ -14,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 = [
@ -60,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>
@ -76,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>}
@ -123,6 +126,7 @@ export default definePlugin({
stop() {
document.getElementById("clientThemeVars")?.remove();
document.getElementById("clientThemeOffsets")?.remove();
document.getElementById("clientThemeLightModeFixes")?.remove();
}
});

View file

@ -33,11 +33,11 @@ function getEmojiMarkdown(target: Target, copyUnicode: boolean): string {
: `:${emojiName}:`;
}
const extension = target?.firstChild.src.match(
/https:\/\/cdn\.discordapp\.com\/emojis\/\d+\.(\w+)/
)?.[1];
const url = new URL(target.firstChild.src);
const hasParam = url.searchParams.get("animated") === "true";
const isGif = url.pathname.endsWith(".gif");
return `<${extension === "gif" ? "a" : ""}:${emojiName.replace(/~\d+$/, "")}:${emojiId}>`;
return `<${(hasParam || isGif) ? "a" : ""}:${emojiName.replace(/~\d+$/, "")}:${emojiId}>`;
}
const settings = definePluginSettings({
@ -55,7 +55,7 @@ export default definePlugin({
settings,
contextMenus: {
"expression-picker"(children, { target }: { target: Target }) {
"expression-picker"(children, { target }: { target: Target; }) {
if (target.dataset.type !== "emoji") return;
children.push(

View file

@ -44,6 +44,7 @@ export default definePlugin({
{
find: ".selectPreviousCommandOption(",
replacement: {
// 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

@ -256,6 +256,7 @@ export default definePlugin({
},
{
// Disallow the emoji for premium locked if the intention doesn't allow it
// 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})`

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

@ -17,14 +17,13 @@
*/
import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { getUserSettingLazy } from "@api/UserSettings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy } from "@webpack";
import style from "./style.css?managed";
import managedStyle from "./style.css?managed";
const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON");
@ -90,6 +89,8 @@ export default definePlugin({
dependencies: ["UserSettingsAPI"],
settings,
managedStyle,
patches: [
{
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
@ -102,11 +103,4 @@ export default definePlugin({
GameActivityToggleButton: ErrorBoundary.wrap(GameActivityToggleButton, { noop: true }),
start() {
enableStyle(style);
},
stop() {
disableStyle(style);
}
});

View file

@ -16,28 +16,44 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "./styles.css";
import { get, set } from "@api/DataStore";
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";
import { MessageSnapshot } from "@webpack/types";
let style: HTMLStyleElement;
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) {
// @ts-ignore - discord-types lags behind discord.
@ -50,49 +66,42 @@ export default definePlugin({
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)
};
},
async start() {
style = document.createElement("style");
style.id = "VencordHideAttachments";
document.head.appendChild(style);
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() {
style.remove();
hiddenMessages.clear();
},
async buildCss() {
const elements = [...hiddenMessages].map(id => `#message-accessories-${id}`).join(",");
style.textContent = `
: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%;
}
`;
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

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

View file

@ -18,7 +18,6 @@
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { makeRange } from "@components/PluginSettings/components";
import { debounce } from "@shared/debounce";
import { Devs } from "@utils/constants";
@ -29,7 +28,7 @@ import type { Root } from "react-dom/client";
import { Magnifier, MagnifierProps } from "./components/Magnifier";
import { ELEMENT_ID } from "./constants";
import styles from "./styles.css?managed";
import managedStyle from "./styles.css?managed";
export const settings = definePluginSettings({
saveZoomValues: {
@ -160,6 +159,8 @@ export default definePlugin({
authors: [Devs.Aria],
tags: ["ImageUtilities"],
managedStyle,
patches: [
{
find: ".contain,SCALE_DOWN:",
@ -252,14 +253,12 @@ export default definePlugin({
},
start() {
enableStyle(styles);
this.element = document.createElement("div");
this.element.classList.add("MagnifierContainer");
document.body.appendChild(this.element);
},
stop() {
disableStyle(styles);
// so componenetWillUnMount gets called if Magnifier component is still alive
this.root && this.root.unmount();
this.element?.remove();

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\.\i\.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

@ -26,6 +26,7 @@ import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecor
import { addMessageClickListener, addMessagePreEditListener, addMessagePreSendListener, removeMessageClickListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents";
import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover";
import { Settings, SettingsStore } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { Logger } from "@utils/Logger";
import { canonicalizeFind } from "@utils/patches";
import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types";
@ -254,7 +255,7 @@ export function subscribeAllPluginsFluxEvents(fluxDispatcher: typeof FluxDispatc
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
const {
name, commands, contextMenus, userProfileBadge,
name, commands, contextMenus, managedStyle, userProfileBadge,
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton
} = p;
@ -298,6 +299,8 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
}
}
if (managedStyle) enableStyle(managedStyle);
if (userProfileBadge) addProfileBadge(userProfileBadge);
if (onBeforeMessageEdit) addMessagePreEditListener(onBeforeMessageEdit);
@ -315,7 +318,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
const {
name, commands, contextMenus, userProfileBadge,
name, commands, contextMenus, managedStyle, userProfileBadge,
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton
} = p;
@ -357,6 +360,8 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
}
}
if (managedStyle) disableStyle(managedStyle);
if (userProfileBadge) removeProfileBadge(userProfileBadge);
if (onBeforeMessageEdit) removeMessagePreEditListener(onBeforeMessageEdit);

View file

@ -23,11 +23,11 @@ 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) {
function calculateNameColorForUser(id?: string) {
const { lightness } = settings.use(["lightness"]);
const idHash = useMemo(() => h64(id), [id]);
const idHash = useMemo(() => id ? h64(id) : null, [id]);
return `hsl(${idHash % 360n}, 100%, ${lightness}%)`;
return idHash && `hsl(${idHash % 360n}, 100%, ${lightness}%)`;
}
const settings = definePluginSettings({
@ -41,13 +41,25 @@ const settings = definePluginSettings({
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],
authors: [Devs.Grzesiek11, Devs.jamesbt365],
settings,
patches: [
@ -70,16 +82,28 @@ export default definePlugin({
calculateNameColorForMessageContext(context: any) {
const id = context?.message?.author?.id;
if (id == null) {
return null;
const colorString = context?.author?.colorString;
const color = calculateNameColorForUser(id);
if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) {
return colorString;
}
return calculateNameColorForUser(id);
return (!settings.store.applyColorOnlyToUsersWithoutColor || !colorString)
? color
: colorString;
},
calculateNameColorForListContext(context: any) {
const id = context?.user?.id;
if (id == null) {
return null;
const colorString = context?.colorString;
const color = calculateNameColorForUser(id);
if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) {
return colorString;
}
return calculateNameColorForUser(id);
return (!settings.store.applyColorOnlyToUsersWithoutColor || !colorString)
? color
: colorString;
}
});

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

@ -442,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,'
}
]
},

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

View file

@ -46,6 +46,7 @@ export default definePlugin({
find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}",
replacement: [
{
// 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)")
}

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

@ -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

@ -133,7 +133,7 @@ function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] {
}));
}
const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => {
const PlatformIndicator = ({ user, small = false }: { user: User; small?: boolean; }) => {
if (!user || user.bot) return null;
ensureOwnStatus(user);
@ -155,11 +155,7 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma
return (
<span
className="vc-platform-indicator"
style={{
marginLeft: wantMargin ? 4 : 0,
top: wantTopMargin ? 2 : 0,
gap: 2
}}
style={{ gap: "2px" }}
>
{icons}
</span>
@ -190,7 +186,7 @@ const indicatorLocations = {
description: "Inside messages",
onEnable: () => addMessageDecoration("platform-indicator", props =>
<ErrorBoundary noop>
<PlatformIndicator user={props.message?.author} wantTopMargin={true} />
<PlatformIndicator user={props.message?.author} />
</ErrorBoundary>
),
onDisable: () => removeMessageDecoration("platform-indicator")

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

@ -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,23 +87,25 @@ function PickerModal({ rootProps, close }: { rootProps: ModalProps, close(): voi
/>
<Forms.FormTitle>Timestamp Format</Forms.FormTitle>
<Select
options={
Formats.map(m => ({
label: m,
value: m
}))
}
isSelected={v => v === format}
select={v => setFormat(v)}
serialize={v => v}
renderOptionLabel={o => (
<div className={cl("format-label")}>
{Parser.parse(formatTimestamp(time, o.value))}
</div>
)}
renderOptionValue={() => rendered}
/>
<div className={cl("format-select")}>
<Select
options={
Formats.map(m => ({
label: m,
value: m
}))
}
isSelected={v => v === format}
select={v => setFormat(v)}
serialize={v => v}
renderOptionLabel={o => (
<div className={cl("format-label")}>
{Parser.parse(formatTimestamp(time, o.value))}
</div>
)}
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

@ -94,6 +94,7 @@ function GuildInfoModal({ guild }: GuildProps) {
<div className={cl("header")}>
{iconUrl
? <img
className={cl("icon")}
src={iconUrl}
alt=""
onClick={() => openImageModal({
@ -170,6 +171,7 @@ function Owner(guildId: string, owner: User) {
return (
<div className={cl("owner")}>
<img
className={cl("owner-avatar")}
src={ownerAvatarUrl}
alt=""
onClick={() => openImageModal({

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,6 +112,7 @@ export default definePlugin({
},
{
// Prevent Discord from trying to connect to hidden voice channels
// 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})`
@ -124,6 +129,7 @@ export default definePlugin({
{
find: ".AUDIENCE),{isSubscriptionGated",
replacement: {
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
match: /(!)?(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/,
replace: (m, not, channel) => not
? `${m}&&!$self.isHiddenChannel(${channel})`
@ -177,6 +183,7 @@ export default definePlugin({
},
// Make voice channels also appear as muted if they are muted
{
// 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 ""`
@ -190,6 +197,7 @@ 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,
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
match: /(?<=\.LOCKED(?:;if\(|:))(?<={channel:(\i).+?)/,
replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&`
},
@ -545,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 }),
@ -555,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

@ -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,11 +286,12 @@ function Info({ track }: { track: Track; }) {
</>
);
if (coverExpanded && img) return (
<div id={cl("album-expanded-wrapper")}>
{i}
</div>
);
if (coverExpanded && img)
return (
<div id={cl("album-expanded-wrapper")}>
{i}
</div>
);
return (
<div id={cl("info-wrapper")}>

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

@ -244,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

@ -130,15 +130,13 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) {
interface VoiceChannelIndicatorProps {
userId: string;
isMessageIndicator?: boolean;
isProfile?: boolean;
isActionButton?: boolean;
shouldHighlight?: boolean;
}
const clickTimers = {} as Record<string, any>;
export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isMessageIndicator, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => {
export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => {
const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined);
const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId);
@ -182,7 +180,7 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isMessageIndi
{props => {
const iconProps: IconProps = {
...props,
className: classes(isMessageIndicator && cl("message-indicator"), (!isProfile && !isActionButton) && cl("speaker-margin"), isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight),
className: classes(isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight),
size: isActionButton ? 20 : undefined,
onClick
};

View file

@ -60,7 +60,7 @@ export default definePlugin({
find: "#{intl::USER_PROFILE_LOAD_ERROR}",
replacement: {
match: /(\.fetchError.+?\?)null/,
replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId,isProfile:true})`
replace: (_, rest) => `${rest}$self.VoiceChannelIndicator({userId:arguments[0]?.userId})`
},
predicate: () => settings.store.showInUserProfileModal
},
@ -99,7 +99,7 @@ export default definePlugin({
addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />);
}
if (settings.store.showInMessages) {
addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />);
addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} />);
}
},

View file

@ -13,16 +13,6 @@
color: var(--interactive-hover);
}
.vc-uvs-speaker-margin {
margin-left: 4px;
}
.vc-uvs-message-indicator {
display: inline-flex;
top: 2.5px;
position: relative;
}
.vc-uvs-tooltip-container {
max-width: 300px;
}

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

@ -17,13 +17,10 @@
*/
import { definePluginSettings } from "@api/Settings";
import { enableStyle } from "@api/Styles";
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 {
@ -115,8 +112,6 @@ export default definePlugin({
},
async start() {
enableStyle(style);
const res = await fetch(API_URL);
if (res.ok) {
this.data = await res.json();

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

@ -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>
);

View file

@ -9,10 +9,6 @@
margin-bottom: 1em;
}
.vc-vmsg-modal audio {
width: 100%;
}
.vc-vmsg-preview {
color: var(--text-normal);
border-radius: 24px;

View file

@ -150,6 +150,11 @@ export interface PluginDef {
tags?: string[];
/**
* Managed style to automatically enable and disable when the plugin is enabled or disabled
*/
managedStyle?: string;
userProfileBadge?: ProfileBadge;
onMessageClick?: MessageClickListener;

View file

@ -57,7 +57,7 @@ export const Heading = waitForComponent<t.Heading>("Heading", filters.componentB
export const Select = waitForComponent<t.Select>("Select", filters.componentByCode('.selectPositionTop]:"top"===', '"Escape"==='));
export const SearchableSelect = waitForComponent<t.SearchableSelect>("SearchableSelect", filters.componentByCode('.selectPositionTop]:"top"===', ".multi]:"));
export const Slider = waitForComponent<t.Slider>("Slider", filters.componentByCode('"markDash".concat('));
export const Popout = waitForComponent<t.Popout>("Popout", filters.componentByCode("ref:this.ref,preload:"));
export const Popout = waitForComponent<t.Popout>("Popout", filters.componentByCode("ref:this.ref,", "renderPopout:this.renderPopout,"));
export const Dialog = waitForComponent<t.Dialog>("Dialog", filters.componentByCode('role:"dialog",tabIndex:-1'));
export const TabBar = waitForComponent("TabBar", filters.componentByCode("ref:this.tabBarRef,className:"));
export const Paginator = waitForComponent<t.Paginator>("Paginator", filters.componentByCode('rel:"prev",children:'));

View file

@ -24,7 +24,7 @@ import { WebpackInstance } from "discord-types/other";
import { traceFunction } from "../debug/Tracer";
import { patches } from "../plugins";
import { _initWebpack, beforeInitListeners, factoryListeners, moduleListeners, subscriptions, wreq } from ".";
import { _initWebpack, _shouldIgnoreModule, beforeInitListeners, factoryListeners, moduleListeners, subscriptions, wreq } from ".";
const logger = new Logger("WebpackInterceptor", "#8caaee");
@ -173,35 +173,9 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
if (!exports) return;
if (require.c) {
let shouldMakeNonEnumerable = false;
const shouldIgnoreModule = _shouldIgnoreModule(exports);
nonEnumerableChecking: {
// There are (at the time of writing) 11 modules exporting the window,
// and also modules exporting DOMTokenList, which breaks webpack finding
// Make these non enumerable to improve search performance and avoid erros
if (exports === window || exports[Symbol.toStringTag] === "DOMTokenList") {
shouldMakeNonEnumerable = true;
break nonEnumerableChecking;
}
if (typeof exports !== "object") {
break nonEnumerableChecking;
}
if (exports.default === window || exports.default?.[Symbol.toStringTag] === "DOMTokenList") {
shouldMakeNonEnumerable = true;
break nonEnumerableChecking;
}
for (const nested in exports) {
if (exports[nested] === window || exports[nested]?.[Symbol.toStringTag] === "DOMTokenList") {
shouldMakeNonEnumerable = true;
break nonEnumerableChecking;
}
}
}
if (shouldMakeNonEnumerable) {
if (shouldIgnoreModule) {
Object.defineProperty(require.c, id, {
value: require.c[id],
enumerable: false,
@ -226,17 +200,16 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
if (exports && filter(exports)) {
subscriptions.delete(filter);
callback(exports, id);
} else if (typeof exports === "object") {
if (exports.default && filter(exports.default)) {
}
if (typeof exports !== "object") {
continue;
}
for (const exportKey in exports) {
if (exports[exportKey] && filter(exports[exportKey])) {
subscriptions.delete(filter);
callback(exports.default, id);
} else {
for (const nested in exports) {
if (exports[nested] && filter(exports[nested])) {
subscriptions.delete(filter);
callback(exports[nested], id);
}
}
callback(exports[exportKey], id);
}
}
} catch (err) {

View file

@ -69,17 +69,23 @@ export const filters = {
m.constructor?.displayName === name,
componentByCode: (...code: CodeFilter): FilterFn => {
const filter = filters.byCode(...code);
return m => {
if (filter(m)) return true;
if (!m.$$typeof) return false;
if (m.type)
return m.type.render
? filter(m.type.render) // memo + forwardRef
: filter(m.type); // memo
if (m.render) return filter(m.render); // forwardRef
const byCodeFilter = filters.byCode(...code);
const filter = m => {
let inner = m;
while (inner != null) {
if (byCodeFilter(inner)) return true;
else if (!inner.$$typeof) return false;
else if (inner.type) inner = inner.type; // memos
else if (inner.render) inner = inner.render; // forwardRefs
else return false;
}
return false;
};
filter.$$vencordProps = [...code];
return filter;
}
};
@ -95,6 +101,38 @@ export function _initWebpack(webpackRequire: WebpackInstance) {
cache = webpackRequire.c;
}
// Credits to Zerebos for implementing this in BD, thus giving the idea for us to implement it too
const TypedArray = Object.getPrototypeOf(Int8Array);
function _shouldIgnoreValue(value: any) {
if (value == null) return true;
if (value === window) return true;
if (value === document || value === document.documentElement) return true;
if (value[Symbol.toStringTag] === "DOMTokenList") return true;
if (value instanceof TypedArray) return true;
return false;
}
export function _shouldIgnoreModule(exports: any) {
if (_shouldIgnoreValue(exports)) {
return true;
}
if (typeof exports !== "object") {
return false;
}
let allNonEnumerable = true;
for (const exportKey in exports) {
if (!_shouldIgnoreValue(exports[exportKey])) {
allNonEnumerable = false;
}
}
return allNonEnumerable;
}
let devToolsOpen = false;
if (IS_DEV && IS_DISCORD_DESKTOP) {
// At this point in time, DiscordNative has not been exposed yet, so setImmediate is needed
@ -121,7 +159,7 @@ export const find = traceFunction("find", function find(filter: FilterFn, { isIn
for (const key in cache) {
const mod = cache[key];
if (!mod.loaded || !mod?.exports) continue;
if (!mod?.loaded || mod.exports == null) continue;
if (filter(mod.exports)) {
return isWaitFor ? [mod.exports, key] : mod.exports;
@ -129,11 +167,6 @@ export const find = traceFunction("find", function find(filter: FilterFn, { isIn
if (typeof mod.exports !== "object") continue;
if (mod.exports.default && filter(mod.exports.default)) {
const found = mod.exports.default;
return isWaitFor ? [found, key] : found;
}
for (const nestedMod in mod.exports) {
const nested = mod.exports[nestedMod];
if (nested && filter(nested)) {
@ -156,16 +189,15 @@ export function findAll(filter: FilterFn) {
const ret = [] as any[];
for (const key in cache) {
const mod = cache[key];
if (!mod.loaded || !mod?.exports) continue;
if (!mod?.loaded || mod.exports == null) continue;
if (filter(mod.exports))
ret.push(mod.exports);
else if (typeof mod.exports !== "object")
if (typeof mod.exports !== "object")
continue;
if (mod.exports.default && filter(mod.exports.default))
ret.push(mod.exports.default);
else for (const nestedMod in mod.exports) {
for (const nestedMod in mod.exports) {
const nested = mod.exports[nestedMod];
if (nested && filter(nested)) ret.push(nested);
}
@ -204,7 +236,7 @@ export const findBulk = traceFunction("findBulk", function findBulk(...filterFns
outer:
for (const key in cache) {
const mod = cache[key];
if (!mod.loaded || !mod?.exports) continue;
if (!mod?.loaded || mod.exports == null) continue;
for (let j = 0; j < length; j++) {
const filter = filters[j];
@ -221,13 +253,6 @@ export const findBulk = traceFunction("findBulk", function findBulk(...filterFns
if (typeof mod.exports !== "object")
continue;
if (mod.exports.default && filter(mod.exports.default)) {
results[j] = mod.exports.default;
filters[j] = undefined;
if (++found === length) break outer;
break;
}
for (const nestedMod in mod.exports) {
const nested = mod.exports[nestedMod];
if (nested && filter(nested)) {