merge unconflict

This commit is contained in:
2025-12-09 17:29:54 +01:00
37 changed files with 584 additions and 168 deletions

View File

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

View File

@@ -2,7 +2,7 @@ import { CommandOption } from './Commands';
import { User, UserJSON } from '../User'; import { User, UserJSON } from '../User';
import { Embed, EmbedJSON } from './Embed'; import { Embed, EmbedJSON } from './Embed';
import { DiscordRecord } from "../Record"; import { DiscordRecord } from "../Record";
import { MessageFlags, MessageType, StickerFormatType } from "../../../enums"; import { ApplicationIntegrationType, MessageFlags, MessageType, StickerFormatType } from "../../../enums";
/* /*
* TODO: looks like discord has moved over to Date instead of Moment; * TODO: looks like discord has moved over to Date instead of Moment;
@@ -71,6 +71,19 @@ export class Message extends DiscordRecord {
type: number; type: number;
version: string; version: string;
}[]; }[];
interactionMetadata?: {
id: string;
type: number;
name?: string;
command_type?: number;
ephemerality_reason?: number;
user: User;
authorizing_integration_owners: Record<ApplicationIntegrationType, string>;
original_response_message_id?: string;
interacted_message_id?: string;
target_user?: User;
target_message_id?: string;
};
interactionError: unknown[]; interactionError: unknown[];
isSearchHit: boolean; isSearchHit: boolean;
loggingName: unknown; loggingName: unknown;

View File

@@ -1,7 +1,7 @@
{ {
"name": "@vencord/types", "name": "@vencord/types",
"private": false, "private": false,
"version": "1.13.2", "version": "1.13.7",
"description": "", "description": "",
"types": "index.d.ts", "types": "index.d.ts",
"scripts": { "scripts": {

View File

@@ -79,7 +79,7 @@ export interface ChatBarProps {
}; };
} }
export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null; export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; isAnyChat: boolean; }) => JSX.Element | null;
export type ChatBarButtonData = { export type ChatBarButtonData = {
render: ChatBarButtonFactory; render: ChatBarButtonFactory;
/** /**
@@ -98,13 +98,14 @@ const logger = new Logger("ChatButtons");
function VencordChatBarButtons(props: ChatBarProps) { function VencordChatBarButtons(props: ChatBarProps) {
const { chatBarButtons } = useSettings(["uiElements.chatBarButtons.*"]).uiElements; const { chatBarButtons } = useSettings(["uiElements.chatBarButtons.*"]).uiElements;
const { analyticsName } = props.type;
return ( return (
<> <>
{Array.from(ChatBarButtonMap) {Array.from(ChatBarButtonMap)
.filter(([key]) => chatBarButtons[key]?.enabled !== false) .filter(([key]) => chatBarButtons[key]?.enabled !== false)
.map(([key, { render: Button }]) => ( .map(([key, { render: Button }]) => (
<ErrorBoundary noop key={key} onError={e => logger.error(`Failed to render ${key}`, e.error)}> <ErrorBoundary noop key={key} onError={e => logger.error(`Failed to render ${key}`, e.error)}>
<Button {...props} isMainChat={props.type.analyticsName === "normal"} /> <Button {...props} isMainChat={analyticsName === "normal"} isAnyChat={["normal", "sidebar"].includes(analyticsName)} />
</ErrorBoundary> </ErrorBoundary>
))} ))}
</> </>

View File

@@ -24,6 +24,21 @@ const toastFailure = (err: any) =>
const logger = new Logger("SettingsSync:Offline", "#39b7e0"); const logger = new Logger("SettingsSync:Offline", "#39b7e0");
function isSafeObject(obj: any) {
if (obj == null || typeof obj !== "object") return true;
for (const key in obj) {
if (["__proto__", "constructor", "prototype"].includes(key)) {
return false;
}
if (!isSafeObject(obj[key])) {
return false;
}
}
return true;
}
export async function importSettings(data: string) { export async function importSettings(data: string) {
try { try {
var parsed = JSON.parse(data); var parsed = JSON.parse(data);
@@ -32,6 +47,9 @@ export async function importSettings(data: string) {
throw new Error("Failed to parse JSON: " + String(err)); throw new Error("Failed to parse JSON: " + String(err));
} }
if (!isSafeObject(parsed))
throw new Error("Unsafe Settings");
if ("settings" in parsed && "quickCss" in parsed) { if ("settings" in parsed && "quickCss" in parsed) {
Object.assign(PlainSettings, parsed.settings); Object.assign(PlainSettings, parsed.settings);
await VencordNative.settings.set(parsed.settings); await VencordNative.settings.set(parsed.settings);

View File

@@ -7,7 +7,7 @@
.vc-h3, .vc-h3,
.vc-h4, .vc-h4,
.vc-h5 { .vc-h5 {
color: var(--header-secondary); color: var(--header-primary);
} }
.vc-h1 { .vc-h1 {

View File

@@ -444,3 +444,101 @@ export function PlaceholderIcon(props: IconProps) {
</Icon> </Icon>
); );
} }
export function MainSettingsIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M10.56 1.1c-.46.05-.7.53-.64.98.18 1.16-.19 2.2-.98 2.53-.8.33-1.79-.15-2.49-1.1-.27-.36-.78-.52-1.14-.24-.77.59-1.45 1.27-2.04 2.04-.28.36-.12.87.24 1.14.96.7 1.43 1.7 1.1 2.49-.33.8-1.37 1.16-2.53.98-.45-.07-.93.18-.99.64a11.1 11.1 0 0 0 0 2.88c.06.46.54.7.99.64 1.16-.18 2.2.19 2.53.98.33.8-.14 1.79-1.1 2.49-.36.27-.52.78-.24 1.14.59.77 1.27 1.45 2.04 2.04.36.28.87.12 1.14-.24.7-.95 1.7-1.43 2.49-1.1.8.33 1.16 1.37.98 2.53-.07.45.18.93.64.99a11.1 11.1 0 0 0 2.88 0c.46-.06.7-.54.64-.99-.18-1.16.19-2.2.98-2.53.8-.33 1.79.14 2.49 1.1.27.36.78.52 1.14.24.77-.59 1.45-1.27 2.04-2.04.28-.36.12-.87-.24-1.14-.96-.7-1.43-1.7-1.1-2.49.33-.8 1.37-1.16 2.53-.98.45.07.93-.18.99-.64a11.1 11.1 0 0 0 0-2.88c-.06-.46-.54-.7-.99-.64-1.16.18-2.2-.19-2.53-.98-.33-.8.14-1.79 1.1-2.49.36-.27.52-.78.24-1.14a11.07 11.07 0 0 0-2.04-2.04c-.36-.28-.87-.12-1.14.24-.7.96-1.7 1.43-2.49 1.1-.8-.33-1.16-1.37-.98-2.53.07-.45-.18-.93-.64-.99a11.1 11.1 0 0 0-2.88 0ZM16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
/>
</Icon>
);
}
export function PluginsIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M18.559 12.8227C17.7884 13.4957 16.6663 13.3616 15.9404 12.641C14.7975 11.5063 11.4931 8.21104 11.4931 8.21104C10.897 7.63087 10.897 6.44662 11.4931 5.85464C12.319 5.03435 13.6053 3.75146 13.6053 3.75146C13.9641 3.39195 14.456 3.18972 14.9653 3.18886L18.3363 3.18425L19.5255 2L22.5 4.96048L21.3108 6.14473L21.3021 9.50878C21.2992 10.0164 21.0967 10.5026 20.735 10.8613C20.735 10.8613 19.5718 11.9384 18.559 12.8227ZM15.2315 13.9548L13.4954 15.8273C14.0972 16.4265 14.0972 16.9113 13.64 17.6997L11.3976 20.2485C11.0359 20.6081 10.5469 20.8103 10.0347 20.8111L6.66378 20.8158L5.47455 22L2.5 19.0395L3.68927 17.8553L3.70082 14.4912C3.70082 13.9836 3.90338 13.4974 4.26507 13.1387L6.37153 11.0404C6.96759 10.4485 8.15685 10.4485 8.73844 11.0404L8.74424 11.0465L10.5295 9.26998L11.7188 10.4542L9.93347 12.2305L12.3119 14.599L14.0972 12.8227L15.2315 13.9548Z"
/>
</Icon>
);
}
export function CloudIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M16.8333 19H5.16667C3.16667 19 1.5 17.3333 1.5 15.3333C1.5 13.4 2.96667 11.8667 4.83333 11.6667V11.3333C4.83333 7.86667 7.7 5 11.1667 5C14.0333 5 16.5667 6.93333 17.3 9.66667C19.7 9.86667 21.5 11.8667 21.5 14.3333C21.5 16.9333 19.4333 19 16.8333 19Z"
/>
</Icon>
);
}
export function BackupRestoreIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M21 2.01232C21.2652 2.01232 21.5196 2.11757 21.7071 2.30492C21.8946 2.49226 22 2.74636 22 3.0113V9.00521C22 9.27015 21.8946 9.52425 21.7071 9.7116C21.5196 9.89894 21.2652 10.0042 21 10.0042H15C14.7348 10.0042 14.4804 9.89894 14.2929 9.7116C14.1054 9.52425 14 9.27015 14 9.00521C14 8.74026 14.1054 8.48617 14.2929 8.29882C14.4804 8.11147 14.7348 8.00622 15 8.00622H18.93C18.352 7.00597 17.5638 6.14275 16.6198 5.47602C15.6758 4.80929 14.5983 4.35488 13.4616 4.1441C12.3249 3.93332 11.1559 3.97117 10.0353 4.25505C8.91459 4.53892 7.86883 5.06208 6.97 5.78848C6.76313 5.9554 6.49836 6.03338 6.23393 6.00528C5.96951 5.97718 5.72709 5.84529 5.56 5.63863C5.39291 5.43197 5.31485 5.16747 5.34298 4.90331C5.37111 4.63916 5.50313 4.39698 5.71 4.23006C6.7542 3.38308 7.959 2.7557 9.25204 2.38561C10.5451 2.01552 11.8996 1.91037 13.2344 2.07646C14.5691 2.24255 15.8565 2.67646 17.0191 3.35212C18.1818 4.02778 19.1957 4.93125 20 6.00826V3.0113C20 2.74636 20.1054 2.49226 20.2929 2.30492C20.4804 2.11757 20.7348 2.01232 21 2.01232ZM3 21.992C2.73478 21.992 2.48043 21.8867 2.29289 21.6994C2.10536 21.5121 2 21.258 2 20.993V14.9991C2 14.7342 2.10536 14.4801 2.29289 14.2927C2.48043 14.1054 2.73478 14.0001 3 14.0001H9C9.26522 14.0001 9.51957 14.1054 9.70711 14.2927C9.89464 14.4801 10 14.7342 10 14.9991C10 15.2641 9.89464 15.5182 9.70711 15.7055C9.51957 15.8928 9.26522 15.9981 9 15.9981H5.07C5.64801 16.9983 6.43617 17.8616 7.3802 18.5283C8.32424 19.195 9.40171 19.6494 10.5384 19.8602C11.6751 20.071 12.8441 20.0331 13.9647 19.7493C15.0854 19.4654 16.1312 18.9422 17.03 18.2158C17.1324 18.1332 17.2502 18.0715 17.3764 18.0343C17.5027 17.9971 17.6351 17.9851 17.7661 17.999C17.897 18.013 18.0239 18.0525 18.1395 18.1154C18.2552 18.1783 18.3573 18.2634 18.44 18.3657C18.5227 18.468 18.5845 18.5856 18.6217 18.7118C18.659 18.8379 18.6709 18.9702 18.657 19.101C18.6431 19.2318 18.6035 19.3586 18.5405 19.4741C18.4776 19.5896 18.3924 19.6916 18.29 19.7743C17.2452 20.6199 16.0403 21.2461 14.7475 21.6154C13.4547 21.9847 12.1005 22.0895 10.7662 21.9235C9.43181 21.7574 8.14476 21.324 6.98212 20.6491C5.81947 19.9743 4.80518 19.0719 4 17.9961V20.993C4 21.258 3.89464 21.5121 3.70711 21.6994C3.51957 21.8867 3.26522 21.992 3 21.992Z"
/>
</Icon>
);
}
export function UpdaterIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M12 2C12.2652 2 12.5196 2.10536 12.7071 2.29289C12.8946 2.48043 13 2.73478 13 3V13.59L16.3 10.29C16.3904 10.186 16.5013 10.1018 16.6258 10.0427C16.7503 9.98362 16.8856 9.95088 17.0234 9.94656C17.1611 9.94224 17.2982 9.96644 17.4261 10.0176C17.5541 10.0688 17.6701 10.1459 17.7668 10.244C17.8635 10.3421 17.939 10.4592 17.9883 10.5878C18.0377 10.7165 18.0599 10.8539 18.0537 10.9916C18.0474 11.1292 18.0127 11.2641 17.9519 11.3877C17.891 11.5114 17.8053 11.6211 17.7 11.71L12.7 16.71C12.5131 16.8932 12.2618 16.9959 12 16.9959C11.7382 16.9959 11.4869 16.8932 11.3 16.71L6.3 11.71C6.19474 11.6211 6.10898 11.5114 6.04812 11.3877C5.98726 11.2641 5.95261 11.1292 5.94634 10.9916C5.94007 10.8539 5.96231 10.7165 6.01167 10.5878C6.06104 10.4592 6.13646 10.3421 6.2332 10.244C6.32994 10.1459 6.44592 10.0688 6.57385 10.0176C6.70179 9.96644 6.83892 9.94224 6.97665 9.94656C7.11438 9.95088 7.24972 9.98362 7.3742 10.0427C7.49868 10.1018 7.6096 10.186 7.7 10.29L11 13.59V3C11 2.73478 11.1054 2.48043 11.2929 2.29289C11.4804 2.10536 11.7348 2 12 2ZM3 20C2.73478 20 2.48043 20.1054 2.29289 20.2929C2.10536 20.4804 2 20.7348 2 21C2 21.2652 2.10536 21.5196 2.29289 21.7071C2.48043 21.8946 2.73478 22 3 22H21C21.2652 22 21.5196 21.8946 21.7071 21.7071C21.8946 21.5196 22 21.2652 22 21C22 20.7348 21.8946 20.4804 21.7071 20.2929C21.5196 20.1054 21.2652 20 21 20H3Z"
/>
</Icon>
);
}
export function PatchHelperIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M7.79997 15.7699C8.49996 16.1999 8.99997 16.9099 8.99997 17.7299V20.9999C8.99997 21.2651 9.10533 21.5195 9.29286 21.707C9.48039 21.8945 9.73476 21.9999 9.99996 21.9999H14C14.2652 21.9999 14.5196 21.8945 14.7071 21.707C14.8946 21.5195 15 21.2651 15 20.9999V17.7299C15 16.9099 15.5 16.1999 16.2 15.7699C17.357 15.0536 18.3137 14.056 18.9812 12.8701C19.6486 11.6842 20.0048 10.3487 20.0168 8.98795C20.0288 7.62724 19.6961 6.28564 19.0497 5.08819C18.4032 3.89074 17.4642 2.87647 16.32 2.13989C15.72 1.74989 15 2.22989 15 2.93989V8.91988C15 9.18511 14.8946 9.43945 14.7071 9.62701C14.5196 9.81454 14.2652 9.9199 14 9.9199H9.99996C9.73476 9.9199 9.48039 9.81454 9.29286 9.62701C9.10533 9.43945 8.99997 9.18511 8.99997 8.91988V2.93989C8.99997 2.22989 8.27997 1.74989 7.67997 2.13989C6.53577 2.87647 5.59671 3.89074 4.9503 5.08819C4.30386 6.28564 3.97113 7.62724 3.9831 8.98795C3.9951 10.3487 4.35138 11.6842 5.01879 12.8701C5.6862 14.056 6.64299 15.0536 7.79997 15.7699Z"
/>
</Icon>
);
}
export function VesktopSettingsIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M18.157.056a1.224 1.224 0 0 0-.709.628c-.105.229-.114.305-.115 1.081v.836l-.351.176a5.545 5.545 0 0 0-.586.342l-.233.167-.543-.311c-.899-.515-.944-.535-1.293-.539-.562-.004-1.018.352-1.17.917-.07.262-.021.567.138.858.151.28.21.325 1.02.794l.606.35v1.38l-.606.35c-.81.469-.869.514-1.02.793-.16.291-.208.597-.137.859.151.564.607.92 1.17.916.347-.004.393-.023 1.289-.537l.54-.308.313.21c.172.116.438.262.588.325l.275.114v.85c.001.793.009.87.115 1.098a1.19 1.19 0 0 0 1.969.282c.269-.307.292-.412.292-1.37v-.869l.315-.148c.173-.081.435-.228.582-.325l.266-.177.706.4c.796.45 1.029.523 1.408.438.882-.198 1.235-1.287.635-1.96-.085-.097-.457-.348-.827-.56l-.67-.385V5.359l.67-.386c.37-.212.742-.463.827-.56.6-.672.247-1.762-.635-1.96-.38-.084-.612-.012-1.405.437l-.7.397-.235-.18a3.792 3.792 0 0 0-.586-.344l-.35-.163v-.848c0-.935-.023-1.044-.292-1.35a1.2 1.2 0 0 0-1.26-.346M4.007 1.25a4.15 4.15 0 0 0-1.21.44c-.354.207-1.102.955-1.309 1.309-.199.34-.374.837-.441 1.252-.07.434-.07 10.426 0 10.86.127.792.42 1.343 1.039 1.963.477.478.81.697 1.317.874.59.204.818.216 4.125.217h3.163v2.42l-1.975.014c-2.202.015-2.148.008-2.5.362-.255.253-.346.474-.346.84s.09.587.345.84c.376.376-.09.348 5.688.348 5.75 0 5.309.025 5.67-.327a1.1 1.1 0 0 0 .362-.86c.002-.367-.09-.586-.344-.84-.353-.355-.3-.348-2.5-.363l-1.976-.014v-2.42h3.164c3.306-.001 3.535-.013 4.124-.217.508-.177.84-.396 1.318-.874.882-.882 1.113-1.57 1.082-3.213-.018-.956-.047-1.068-.364-1.384-.253-.255-.474-.346-.84-.346s-.586.091-.84.346c-.317.316-.343.42-.372 1.47-.023.846-.036.966-.13 1.14a1.22 1.22 0 0 1-.597.553c-.217.098-.276.098-7.757.098-7.48 0-7.54 0-7.757-.098a1.153 1.153 0 0 1-.612-.602l-.114-.242V9.68c.001-5.041.003-5.119.1-5.333.122-.27.3-.462.553-.599.19-.103.242-.104 2.958-.128 3.08-.027 2.927-.01 3.288-.372.255-.254.345-.474.345-.84s-.09-.587-.345-.84c-.364-.365-.204-.347-3.288-.355-1.52-.004-2.881.012-3.024.036m15.047 3.693c.207.108.452.361.57.59.143.278.143.755 0 1.028-.285.54-1.08.81-1.636.556a1.357 1.357 0 0 1-.59-.625c-.107-.255-.092-.7.032-.956.121-.251.46-.568.69-.645.213-.073.755-.043.934.052"
/>
</Icon>
);
}

View File

@@ -21,7 +21,7 @@ import "@components/NxComponents.css";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { HTMLProps, PropsWithChildren } from "react"; import { HTMLProps, PropsWithChildren } from "react";
import { Heading } from "./Heading"; import { Heading, HeadingTag } from "./Heading";
import { Paragraph } from "./Paragraph"; import { Paragraph } from "./Paragraph";
@@ -41,9 +41,11 @@ export function NxCard(props: NxCardProps) {
); );
} }
interface NxTitleProps extends PropsWithChildren<HTMLProps<HTMLElement>> {
tag?: HeadingTag;
}
export function NxTitle(props:NxTitleProps) {
export function NxTitle(props: PropsWithChildren<HTMLProps<HTMLElement>>) {
return ( return (
<Heading {...props} className={classes(props.className, "nx-title")}>{props.children}</Heading> <Heading {...props} className={classes(props.className, "nx-title")}>{props.children}</Heading>
); );

View File

@@ -16,7 +16,7 @@
.vc-settings-quickActions-containerButtons-2 { .vc-settings-quickActions-containerButtons-2 {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 0.5em; gap: 0.5em;
} }
@@ -32,6 +32,15 @@
gap: 0.5em; gap: 0.5em;
} }
.vc-settings-quickActions-pill {
all: unset;
background: var(--button-secondary-background);
color: var(--text-default);
display: flex;
align-items: center;
gap: 0.5em;
}
.vc-settings-quickActions-button:focus-visible { .vc-settings-quickActions-button:focus-visible {
outline: 2px solid var(--focus-primary); outline: 2px solid var(--focus-primary);
outline-offset: 2px; outline-offset: 2px;

View File

@@ -85,7 +85,7 @@ export function QuickActionContainer({ title, children, columns = "3" }: PropsWi
<InfoIcon /> <InfoIcon />
</button> </button>
</NxTitle> </NxTitle>
<span className={cl("containerButtons-" + columns)}>{children}</span> <span className={cl(`containerButtons-${columns}`)}>{children}</span>
</NxCard> </NxCard>
); );
} }

View File

@@ -16,20 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { BaseText } from "@components/BaseText";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { handleComponentFailed } from "@components/handleComponentFailed"; import { handleComponentFailed } from "@components/handleComponentFailed";
import { Margins } from "@utils/margins";
import { ModalCloseButton, ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { ModalCloseButton, ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { onlyOnce } from "@utils/onlyOnce"; import { onlyOnce } from "@utils/onlyOnce";
import type { ComponentType, PropsWithChildren } from "react"; import type { ComponentType, PropsWithChildren } from "react";
export function SettingsTab({ title, children }: PropsWithChildren<{ title: string; }>) { export function SettingsTab({ children }: PropsWithChildren) {
return ( return (
<section> <section className="vc-settings-tab">{children}</section>
<BaseText tag="h2" size="xl" weight="semibold" className={Margins.bottom16}>{title}</BaseText>
{children}
</section>
); );
} }

View File

@@ -104,7 +104,7 @@ function PatchHelper() {
} }
return ( return (
<SettingsTab title="Patch Helper"> <SettingsTab>
<HeadingTertiary>Full patch</HeadingTertiary> <HeadingTertiary>Full patch</HeadingTertiary>
<FullPatchInput <FullPatchInput
setFind={onFindChange} setFind={onFindChange}

View File

@@ -27,7 +27,7 @@
} }
.vc-plugins-setting-description { .vc-plugins-setting-description {
color: var(--header-secondary); color: var(--text-default);
} }
.vc-plugins-setting-error { .vc-plugins-setting-error {

View File

@@ -268,7 +268,7 @@ function PluginSettings() {
} }
return ( return (
<SettingsTab title="Plugins"> <SettingsTab>
<ReloadRequiredCard required={changes.hasChanges} /> <ReloadRequiredCard required={changes.hasChanges} />
<UIElementsButton /> <UIElementsButton />

View File

@@ -29,7 +29,7 @@ import { Button,Text } from "@webpack/common";
export function BackupAndRestoreTab() { export function BackupAndRestoreTab() {
return ( return (
<SettingsTab title="Backup & Restore"> <SettingsTab>
<Flex flexDirection="column" gap="0.5em"> <Flex flexDirection="column" gap="0.5em">
<NxCard variant="warning"> <NxCard variant="warning">
<Heading tag="h4">Warning</Heading> <Heading tag="h4">Warning</Heading>

View File

@@ -28,7 +28,7 @@ import { NxCard, NxText, NxTitle } from "@components/NxComponents";
import { Paragraph } from "@components/Paragraph"; import { Paragraph } from "@components/Paragraph";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { Alerts, Button, Forms, Tooltip } from "@webpack/common"; import { Alerts, Button, Tooltip } from "@webpack/common";
function validateUrl(url: string) { function validateUrl(url: string) {
try { try {
@@ -98,16 +98,14 @@ function CloudTab() {
const settings = useSettings(["cloud.authenticated", "cloud.url"]); const settings = useSettings(["cloud.authenticated", "cloud.url"]);
return ( return (
<SettingsTab title="Vencord Cloud"> <SettingsTab>
<section className={Margins.top16}> <section className={Margins.top16}>
<NxCard className={Margins.bottom20}> <NxText size="medium" className={Margins.bottom20}>
<NxText> Nexulien comes with a cloud integration that adds goodies like settings sync across devices.
Vencord comes with a cloud integration that adds goodies like settings sync across devices.
It <Link href="https://vencord.dev/cloud/privacy">respects your privacy</Link>, and It <Link href="https://vencord.dev/cloud/privacy">respects your privacy</Link>, and
the <Link href="https://github.com/Vencord/Backend">source code</Link> is AGPL 3.0 licensed so you the <Link href="https://github.com/Vencord/Backend">source code</Link> is AGPL 3.0 licensed so you
can host it yourself. It may or may not work with Nexulien; use with caution. can host it yourself.
</NxText> </NxText>
</NxCard>
<FormSwitch <FormSwitch
key="backend" key="backend"
title="Enable Cloud Integrations" title="Enable Cloud Integrations"
@@ -119,15 +117,28 @@ function CloudTab() {
else else
settings.cloud.authenticated = v; settings.cloud.authenticated = v;
}} }}
hideBorder={!settings.cloud.authenticated} />
<NxTitle tag="h5" className={Margins.top16}>Backend URL</NxTitle>
<NxText className={Margins.bottom8}>
Which backend to use when using cloud integrations.
</NxText>
<CheckedTextInput
key="backendUrl"
value={settings.cloud.url}
onChange={async v => {
settings.cloud.url = v;
settings.cloud.authenticated = false;
deauthorizeCloud();
}}
validate={validateUrl}
/> />
{settings.cloud.authenticated ? <> {settings.cloud.authenticated ? <>
<NxCard> <NxCard>
<NxTitle>Backend URL</NxTitle> <NxTitle>Backend URL</NxTitle>
<Forms.FormText className={Margins.bottom8}> <NxText className={Margins.bottom8}>
Which backend to use when using cloud integrations. Which backend to use when using cloud integrations.
</Forms.FormText> </NxText>
<CheckedTextInput <CheckedTextInput
key="backendUrl" key="backendUrl"
value={settings.cloud.url} value={settings.cloud.url}

View File

@@ -37,7 +37,7 @@ function ThemesTab() {
const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL); const [currentTab, setCurrentTab] = useState(ThemeTab.LOCAL);
return ( return (
<SettingsTab title="Themes"> <SettingsTab>
<TabBar <TabBar
type="top" type="top"
look="brand" look="brand"
@@ -69,9 +69,10 @@ function ThemesTab() {
function UserscriptThemesTab() { function UserscriptThemesTab() {
return ( return (
<SettingsTab title="Themes"> <SettingsTab>
<NxCard variant="danger"> <NxCard variant="danger">
<NxTitle>Themes are not supported on the Userscript!</NxTitle> <NxTitle tag="h5">Themes are not supported on the Userscript!</NxTitle>
<span> <span>
You can instead install themes with the <Link href={getStylusWebStoreUrl()}>Stylus extension</Link>! You can instead install themes with the <Link href={getStylusWebStoreUrl()}>Stylus extension</Link>!
</span> </span>

View File

@@ -21,7 +21,6 @@ import "./styles.css";
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { FormSwitch } from "@components/FormSwitch"; import { FormSwitch } from "@components/FormSwitch";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { Margins } from "@utils/margins";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { getRepo, isNewer, UpdateLogger } from "@utils/updater"; import { getRepo, isNewer, UpdateLogger } from "@utils/updater";
import { Forms, React } from "@webpack/common"; import { Forms, React } from "@webpack/common";
@@ -43,18 +42,16 @@ function Updater() {
}; };
return ( return (
<SettingsTab title="Nexulien Updater"> <SettingsTab>
<Forms.FormTitle tag="h5" className={Margins.bottom16}>Updater Settings</Forms.FormTitle>
<FormSwitch <FormSwitch
title="Automatically update" title="Automatically update"
description="Automatically update Vencord without confirmation prompt" description="Automatically update Nexulien without confirmation prompt"
value={settings.autoUpdate} value={settings.autoUpdate}
onChange={(v: boolean) => settings.autoUpdate = v} onChange={(v: boolean) => settings.autoUpdate = v}
/> />
<FormSwitch <FormSwitch
title="Get notified when an automatic update completes" title="Get notified when an automatic update completes"
description="Show a notification when Vencord automatically updates" description="Show a notification when Nexulien automatically updates"
value={settings.autoUpdateNotification} value={settings.autoUpdateNotification}
onChange={(v: boolean) => settings.autoUpdateNotification = v} onChange={(v: boolean) => settings.autoUpdateNotification = v}
disabled={!settings.autoUpdate} disabled={!settings.autoUpdate}

View File

@@ -135,7 +135,7 @@ function VencordSettings() {
const user = UserStore?.getCurrentUser(); const user = UserStore?.getCurrentUser();
return ( return (
<SettingsTab title="Nexulien Settings"> <SettingsTab>
<HeaderCard /> <HeaderCard />
{isPluginDev(user?.id) && !hideContributorCard && ( {isPluginDev(user?.id) && !hideContributorCard && (

View File

@@ -16,17 +16,103 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { CloudTab, NotificationsTab, PatchHelperTab, PluginsTab, ThemesTab, UpdaterTab, VencordTab } from "@components/settings/tabs"; import { BackupRestoreIcon, CloudIcon, LogIcon,MainSettingsIcon, PaintbrushIcon, PatchHelperIcon, PlaceholderIcon, PluginsIcon, UpdaterIcon, VesktopSettingsIcon } from "@components/Icons";
import { BackupAndRestoreTab, CloudTab, NotificationsTab, PatchHelperTab, PluginsTab, ThemesTab, UpdaterTab, VencordTab } from "@components/settings/tabs";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord"; import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import { isTruthy } from "@utils/guards";
import definePlugin, { IconProps, OptionType } from "@utils/types";
import { waitFor } from "@webpack";
import { React } from "@webpack/common"; import { React } from "@webpack/common";
import type { ComponentType, PropsWithChildren, ReactNode } from "react";
import gitHash from "~git-hash"; import gitHash from "~git-hash";
type SectionType = "HEADER" | "DIVIDER" | "CUSTOM"; let LayoutTypes = {
type SectionTypes = Record<SectionType, SectionType>; SECTION: 1,
SIDEBAR_ITEM: 2,
PANEL: 3,
PANE: 4
};
waitFor(["SECTION", "SIDEBAR_ITEM", "PANEL", "PANE"], v => LayoutTypes = v);
const FallbackSectionTypes = {
HEADER: "HEADER",
DIVIDER: "DIVIDER",
CUSTOM: "CUSTOM"
};
type SectionTypes = typeof FallbackSectionTypes;
type SettingsLocation =
| "top"
| "aboveNitro"
| "belowNitro"
| "aboveActivity"
| "belowActivity"
| "bottom";
type HeaderCardSize =
| "default"
| "minimal"
| "none";
interface SettingsLayoutNode {
type: number;
key?: string;
legacySearchKey?: string;
useLabel?(): string;
useTitle?(): string;
buildLayout?(): SettingsLayoutNode[];
icon?(): ReactNode;
render?(): ReactNode;
}
interface EntryOptions {
key: string,
title: string,
panelTitle?: string,
Component: ComponentType<{}>,
Icon: ComponentType<IconProps>;
}
interface SettingsLayoutBuilder {
key?: string;
buildLayout(): SettingsLayoutNode[];
}
const settings = definePluginSettings({
showHint: {
type: OptionType.BOOLEAN,
description: "If the hint referring to these settings should show",
default: true
},
hideContributorCard: {
type: OptionType.BOOLEAN,
description: "If the contributor card should be hidden, even if you are a contributor.",
default: false
},
settingsLocation: {
type: OptionType.SELECT,
description: "Where to put the Nexulien settings section",
options: [
{ label: "At the very top", value: "top" },
{ label: "Above the Nitro section", value: "aboveNitro", default: true },
{ label: "Below the Nitro section", value: "belowNitro" },
{ label: "Above Activity Settings", value: "aboveActivity" },
{ label: "Below Activity Settings", value: "belowActivity" },
{ label: "At the very bottom", value: "bottom" },
] as { label: string; value: SettingsLocation; default?: boolean; }[]
},
headerCardSize: {
type: OptionType.SELECT,
description: "Change how much space the Nexulien header takes up",
options: [
{ label: "Default - as much as intended", value: "default", default: true },
{ label: "Minimal - small card with logo and buttons", value: "minimal" },
{ label: "None", value: "none" },
] as { label: string; value: HeaderCardSize; default?: boolean }[]
},
});
export default definePlugin({ export default definePlugin({
name: "Settings", name: "Settings",
@@ -34,10 +120,19 @@ export default definePlugin({
authors: [Devs.Ven, Devs.Megu, Devs.Jaegerwald], authors: [Devs.Ven, Devs.Megu, Devs.Jaegerwald],
required: true, required: true,
settings,
patches: [ patches: [
{ {
find: ".versionHash", find: ".versionHash",
replacement: [ replacement: [
{
match: /\.compactInfo.+?(?=null!=(\i)&&(.{0,20}\i\.Text.{0,200}?,children:).{0,15}?("span"),({className:\i\.versionHash,children:\["Build Override: ",\1\.id\]\})\)\}\))/,
replace: (m, _buildOverride, makeRow, component, props) => {
props = props.replace(/children:\[.+\]/, "");
return `${m},$self.makeInfoElements(${component},${props}).map(e=>${makeRow}e})),`;
}
},
{ {
match: /\.info.+?\[\(0,\i\.jsxs?\)\((.{1,10}),(\{[^{}}]+\{.{0,20}.versionHash,.+?\})\)," "/, match: /\.info.+?\[\(0,\i\.jsxs?\)\((.{1,10}),(\{[^{}}]+\{.{0,20}.versionHash,.+?\})\)," "/,
replace: (m, component, props) => { replace: (m, component, props) => {
@@ -46,7 +141,7 @@ export default definePlugin({
} }
}, },
{ {
match: /copyValue:\i\.join\(" "\)/, match: /copyValue:\i\.join\(" "\)/g,
replace: "$& + $self.getInfoString()" replace: "$& + $self.getInfoString()"
} }
] ]
@@ -73,15 +168,173 @@ export default definePlugin({
} }
}, },
{ {
find: "2025-09-user-settings-redesign-1", find: ".buildLayout().map",
replacement: { replacement: {
match: /enabled:![01],showLegacyOpen:/g, match: /(\i)\.buildLayout\(\)(?=\.map)/,
replace: "enabled:false,showLegacyOpen:" replace: "$self.buildLayout($1)"
}
},
{
find: "getWebUserSettingFromSection",
replacement: {
match: /new Map\(\[(?=\[.{0,10}\.ACCOUNT,.{0,10}\.ACCOUNT_PANEL)/,
replace: "new Map([...$self.getSettingsSectionMappings(),"
} }
} }
], ],
buildEntry(options: EntryOptions): SettingsLayoutNode {
const { key, title, panelTitle = title, Component, Icon } = options;
return ({
key,
type: LayoutTypes.SIDEBAR_ITEM,
legacySearchKey: title.toUpperCase(),
useTitle: () => title,
icon: () => <Icon width={20} height={20} />,
buildLayout: () => [
{
key: key + "_panel",
type: LayoutTypes.PANEL,
useTitle: () => panelTitle,
buildLayout: () => [
{
key: key + "_pane",
type: LayoutTypes.PANE,
buildLayout: () => [],
render: () => <Component />,
useTitle: () => panelTitle
}
]
}
]
});
},
getSettingsSectionMappings() {
return [
["VencordSettings", "vencord_main_panel"],
["VencordPlugins", "vencord_plugins_panel"],
["VencordThemes", "vencord_themes_panel"],
["VencordUpdater", "vencord_updater_panel"],
["VencordCloud", "vencord_cloud_panel"],
["VencordBackupAndRestore", "vencord_backup_restore_panel"],
["VencordPatchHelper", "vencord_patch_helper_panel"]
];
},
buildLayout(originalLayoutBuilder: SettingsLayoutBuilder) {
const layout = originalLayoutBuilder.buildLayout();
if (originalLayoutBuilder.key !== "$Root") return layout;
if (!Array.isArray(layout)) return layout;
if (layout.some(s => s?.key === "vencord_section")) return layout;
const { buildEntry } = this;
const vencordEntries: SettingsLayoutNode[] = [
buildEntry({
key: "vencord_main",
title: "Settings",
panelTitle: "Settings",
Component: VencordTab,
Icon: MainSettingsIcon
}),
buildEntry({
key: "vencord_plugins",
title: "Plugins",
Component: PluginsTab,
Icon: PluginsIcon
}),
buildEntry({
key: "vencord_themes",
title: "Themes",
Component: ThemesTab,
Icon: PaintbrushIcon
}),
!IS_UPDATER_DISABLED && UpdaterTab && buildEntry({
key: "vencord_updater",
title: "Updater",
panelTitle: "Updater",
Component: UpdaterTab,
Icon: UpdaterIcon
}),
buildEntry({
key: "nexulien_notifications",
title: "Notifications",
panelTitle: "Notifications",
Component: NotificationsTab,
Icon: LogIcon,
}),
buildEntry({
key: "vencord_cloud",
title: "Cloud",
panelTitle: "Cloud",
Component: CloudTab,
Icon: CloudIcon
}),
buildEntry({
key: "vencord_backup_restore",
title: "Backup & Restore",
Component: BackupAndRestoreTab,
Icon: BackupRestoreIcon
}),
IS_DEV && PatchHelperTab && buildEntry({
key: "vencord_patch_helper",
title: "Patch Helper",
Component: PatchHelperTab,
Icon: PatchHelperIcon
}),
...this.customEntries.map(buildEntry),
// TODO: Remove deprecated customSections in a future update
...this.customSections.map((func, i) => {
const { section, element, label } = func(FallbackSectionTypes);
if (Object.values(FallbackSectionTypes).includes(section)) return null;
return buildEntry({
key: `vencord_deprecated_custom_${section}`,
title: label,
Component: element,
Icon: section === "Vesktop" ? VesktopSettingsIcon : PlaceholderIcon
});
})
].filter(isTruthy);
const vencordSection: SettingsLayoutNode = {
key: "vencord_section",
type: LayoutTypes.SECTION,
useLabel: () => "Nexulien",
buildLayout: () => vencordEntries
};
const { settingsLocation } = settings.store;
const places: Record<SettingsLocation, string> = {
top: "user_section",
aboveNitro: "billing_section",
belowNitro: "billing_section",
aboveActivity: "activity_section",
belowActivity: "activity_section",
bottom: "logout_section"
};
const key = places[settingsLocation] ?? places.top;
let idx = layout.findIndex(s => typeof s?.key === "string" && s.key === key);
if (idx === -1) {
idx = 2;
} else if (settingsLocation.startsWith("below")) {
idx += 1;
}
layout.splice(idx, 0, vencordSection);
return layout;
},
/** @deprecated Use customEntries */
customSections: [] as ((SectionTypes: SectionTypes) => any)[], customSections: [] as ((SectionTypes: SectionTypes) => any)[],
customEntries: [] as EntryOptions[],
makeSettingsCategories(SectionTypes: SectionTypes) { makeSettingsCategories(SectionTypes: SectionTypes) {
return [ return [
@@ -91,7 +344,7 @@ export default definePlugin({
className: "vc-settings-header" className: "vc-settings-header"
}, },
{ {
section: "settings/tabs", section: "VencordSettings",
label: "Settings", label: "Settings",
element: VencordTab, element: VencordTab,
className: "vc-settings" className: "vc-settings"
@@ -126,6 +379,12 @@ export default definePlugin({
element: CloudTab, element: CloudTab,
className: "vc-cloud" className: "vc-cloud"
}, },
{
section: "VencordBackupAndRestore",
label: "Backup & Restore",
element: BackupAndRestoreTab,
className: "vc-backup-restore"
},
IS_DEV && { IS_DEV && {
section: "VencordPatchHelper", section: "VencordPatchHelper",
label: "Patch Helper", label: "Patch Helper",
@@ -139,12 +398,12 @@ export default definePlugin({
].filter(Boolean); ].filter(Boolean);
}, },
isRightSpot({ header, settings }: { header?: string; settings?: string[]; }) { isRightSpot({ header, settings: s }: { header?: string; settings?: string[]; }) {
const firstChild = settings?.[0]; const firstChild = s?.[0];
// lowest two elements... sanity backup // lowest two elements... sanity backup
if (firstChild === "LOGOUT" || firstChild === "SOCIAL_LINKS") return true; if (firstChild === "LOGOUT" || firstChild === "SOCIAL_LINKS") return true;
const { settingsLocation } = Settings.plugins.Settings; const { settingsLocation } = settings.store;
if (settingsLocation === "bottom") return firstChild === "LOGOUT"; if (settingsLocation === "bottom") return firstChild === "LOGOUT";
if (settingsLocation === "belowActivity") return firstChild === "CHANGELOG"; if (settingsLocation === "belowActivity") return firstChild === "CHANGELOG";
@@ -152,7 +411,7 @@ export default definePlugin({
if (!header) return; if (!header) return;
try { try {
const names = { const names: Record<Exclude<SettingsLocation, "bottom" | "belowActivity">, string> = {
top: getIntlMessage("USER_SETTINGS"), top: getIntlMessage("USER_SETTINGS"),
aboveNitro: getIntlMessage("BILLING_SETTINGS"), aboveNitro: getIntlMessage("BILLING_SETTINGS"),
belowNitro: getIntlMessage("APP_SETTINGS"), belowNitro: getIntlMessage("APP_SETTINGS"),
@@ -182,50 +441,12 @@ export default definePlugin({
return (...args: any[]) => { return (...args: any[]) => {
const elements = originalHook(...args); const elements = originalHook(...args);
if (!this.patchedSettings.has(elements)) if (!this.patchedSettings.has(elements))
elements.unshift(...this.makeSettingsCategories({ elements.unshift(...this.makeSettingsCategories(FallbackSectionTypes));
HEADER: "HEADER",
DIVIDER: "DIVIDER",
CUSTOM: "CUSTOM"
}));
return elements; return elements;
}; };
}, },
options: {
showHint: {
type: OptionType.BOOLEAN,
description: "If the hint referring to these settings should show",
default: true
},
hideContributorCard: {
type: OptionType.BOOLEAN,
description: "If the contributor card should be hidden, even if you are a contributor.",
default: false
},
settingsLocation: {
type: OptionType.SELECT,
description: "Where to put the Nexulien settings section",
options: [
{ label: "At the very top", value: "top" },
{ label: "Above the Nitro section", value: "aboveNitro", default: true },
{ label: "Below the Nitro section", value: "belowNitro" },
{ label: "Above Activity Settings", value: "aboveActivity" },
{ label: "Below Activity Settings", value: "belowActivity" },
{ label: "At the very bottom", value: "bottom" },
]
},
headerCardSize: {
type: OptionType.SELECT,
description: "Change how much space the Nexulien header takes up",
options: [
{ label: "Default - as much as intended", value: "default", default: true },
{ label: "Minimal - small card with logo and buttons", value: "minimal" },
{ label: "None", value: "none" },
]
},
},
get electronVersion() { get electronVersion() {
return VencordNative.native.getVersions().electron || window.legcord?.electron || null; return VencordNative.native.getVersions().electron || window.legcord?.electron || null;
}, },
@@ -264,7 +485,7 @@ export default definePlugin({
return "\n" + this.getInfoRows().join("\n"); return "\n" + this.getInfoRows().join("\n");
}, },
makeInfoElements(Component: React.ComponentType<React.PropsWithChildren>, props: React.PropsWithChildren) { makeInfoElements(Component: ComponentType<PropsWithChildren>, props: PropsWithChildren) {
return this.getInfoRows().map((text, i) => return this.getInfoRows().map((text, i) =>
<Component key={i} {...props}>{text}</Component> <Component key={i} {...props}>{text}</Component>
); );

View File

@@ -0,0 +1,10 @@
/*
* Discord has dumb max height logic for their context menus.
* If a context menu is at the bottom of the screen, its submenus are capped to its max height and can't even grow upwards
* We unset the variable they use to cap height. This allows submenus to grow as tall as they want
*/
#user-settings-cog,
[aria-activedescendant^="user-settings-cog"] {
--reference-position-layer-max-height: initial !important;
}

View File

@@ -5,16 +5,17 @@
*/ */
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory, disableStyle, enableStyle } from "@api/Styles";
import { buildPluginMenuEntries, buildThemeMenuEntries } from "@plugins/vencordToolbox/menu"; import { buildPluginMenuEntries, buildThemeMenuEntries } from "@plugins/vencordToolbox/menu";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { waitFor } from "@webpack"; import { waitFor } from "@webpack";
import { ComponentDispatch, FocusLock, Menu, useEffect, useRef } from "@webpack/common"; import { ComponentDispatch, FocusLock, Menu, useEffect, useRef } from "@webpack/common";
import type { HTMLAttributes, ReactElement } from "react"; import type { HTMLAttributes, ReactElement } from "react";
import fullHeightStyle from "./fullHeightContext.css?managed";
type SettingsEntry = { section: string, label: string; }; type SettingsEntry = { section: string, label: string; };
const cl = classNameFactory(""); const cl = classNameFactory("");
@@ -42,6 +43,11 @@ const settings = definePluginSettings({
} }
}); });
interface TransformedSettingsEntry {
section: string;
items: SettingsEntry[];
}
interface LayerProps extends HTMLAttributes<HTMLDivElement> { interface LayerProps extends HTMLAttributes<HTMLDivElement> {
mode: "SHOWN" | "HIDDEN"; mode: "SHOWN" | "HIDDEN";
baseLayer?: boolean; baseLayer?: boolean;
@@ -81,6 +87,15 @@ export default definePlugin({
authors: [Devs.Kyuuhachi], authors: [Devs.Kyuuhachi],
settings, settings,
start() {
if (settings.store.organizeMenu)
enableStyle(fullHeightStyle);
},
stop() {
disableStyle(fullHeightStyle);
},
patches: [ patches: [
{ {
find: "this.renderArtisanalHack()", find: "this.renderArtisanalHack()",
@@ -124,8 +139,8 @@ export default definePlugin({
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}", find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
replacement: [ replacement: [
{ {
match: /=\[\];if\((\i)(?=\.forEach)/, match: /=\[\];if\((\i)(?=\.forEach.{0,100}"logout"!==\i.{0,30}(\i)\.get\(\i\))/,
replace: "=$self.wrapMap([]);if($self.transformSettingsEntries($1)", replace: "=$self.wrapMap([]);if($self.transformSettingsEntries($1,$2)",
predicate: () => settings.store.organizeMenu predicate: () => settings.store.organizeMenu
}, },
{ {
@@ -154,36 +169,34 @@ export default definePlugin({
return <Layer {...props} />; return <Layer {...props} />;
}, },
transformSettingsEntries(list: SettingsEntry[]) { transformSettingsEntries(list: SettingsEntry[], keyMap: Map<string, string>) {
const items = [{ label: null as string | null, items: [] as SettingsEntry[] }]; const items = [] as TransformedSettingsEntry[];
for (const item of list) { for (const item of list) {
if (item.section === "HEADER") { if (item.section === "HEADER") {
items.push({ label: item.label, items: [] }); keyMap.set(item.label, item.label);
} else if (item.section === "DIVIDER") { items.push({ section: item.label, items: [] });
items.push({ label: getIntlMessage("OTHER_OPTIONS"), items: [] }); } else if (item.section !== "DIVIDER" && keyMap.has(item.section)) {
} else { items.at(-1)?.items.push(item);
items.at(-1)!.items.push(item);
} }
} }
return items; return items;
}, },
wrapMap(toWrap: any[]) { wrapMap(toWrap: TransformedSettingsEntry[]) {
const otherOptions = getIntlMessage("OTHER_OPTIONS");
// @ts-expect-error // @ts-expect-error
toWrap.map = function (render: (item: SettingsEntry) => ReactElement<any>) { toWrap.map = function (render: (item: SettingsEntry) => ReactElement<any>) {
return this return this
.filter(a => a.items.length > 0 && a.label !== otherOptions) .filter(a => a.items.length > 0)
.map(({ label, items }) => { .map(({ section, items }) => {
const children = items.map(render); const children = items.map(render);
if (label) { if (section) {
return ( return (
<Menu.MenuItem <Menu.MenuItem
key={label} key={section}
id={label.replace(/\W/, "_")} id={section.replace(/\W/, "_")}
label={label} label={section}
> >
{children} {children}
</Menu.MenuItem> </Menu.MenuItem>

View File

@@ -17,6 +17,11 @@
*/ */
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { LinkButton } from "@components/Button";
import { Card } from "@components/Card";
import { Heading } from "@components/Heading";
import { Margins } from "@components/margins";
import { Paragraph } from "@components/Paragraph";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@@ -61,6 +66,10 @@ function setActivity(activity: Activity | null) {
} }
const settings = definePluginSettings({ const settings = definePluginSettings({
apiKey: {
description: "Custom Last.fm API key. Not required but highly recommended to avoid rate limiting with our shared key",
type: OptionType.STRING,
},
username: { username: {
description: "Last.fm username", description: "Last.fm username",
type: OptionType.STRING, type: OptionType.STRING,
@@ -165,10 +174,6 @@ const settings = definePluginSettings({
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
default: true, default: true,
}, },
apiKey: {
description: "Custom Last.fm API key. You shouldn't need to set this",
type: OptionType.STRING,
},
}); });
export default definePlugin({ export default definePlugin({
@@ -178,6 +183,16 @@ export default definePlugin({
settings, settings,
settingsAboutComponent() {
return (
<Card>
<Heading tag="h5">How to create an API key</Heading>
<Paragraph>Set <strong>Application name</strong> and <strong>Application description</strong> to anything and leave the rest blank.</Paragraph>
<LinkButton size="small" href="https://www.last.fm/api/account/create" className={Margins.top8}>Create API Key</LinkButton>
</Card>
);
},
start() { start() {
this.updatePresence(); this.updatePresence();
this.updateInterval = setInterval(() => { this.updatePresence(); }, 16000); this.updateInterval = setInterval(() => { this.updatePresence(); }, 16000);
@@ -243,7 +258,7 @@ export default definePlugin({
async getActivity(): Promise<Activity | null> { async getActivity(): Promise<Activity | null> {
if (settings.store.hideWithActivity) { if (settings.store.hideWithActivity) {
if (PresenceStore.getActivities(AuthenticationStore.getId()).some(a => a.application_id !== DISCORD_APP_ID)) { if (PresenceStore.getActivities(AuthenticationStore.getId()).some(a => a.application_id !== DISCORD_APP_ID && a.type !== ActivityType.CUSTOM_STATUS)) {
return null; return null;
} }
} }

View File

@@ -21,9 +21,9 @@ import { definePluginSettings } from "@api/Settings";
import NoReplyMentionPlugin from "@plugins/noReplyMention"; import NoReplyMentionPlugin from "@plugins/noReplyMention";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { MessageFlags } from "@vencord/discord-types/enums"; import { ApplicationIntegrationType, MessageFlags } from "@vencord/discord-types/enums";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, MessageTypeSets, PermissionsBits, PermissionStore, UserStore, WindowStore } from "@webpack/common"; import { AuthenticationStore, FluxDispatcher, MessageTypeSets, PermissionsBits, PermissionStore, WindowStore } from "@webpack/common";
const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage"); const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage");
const EditStore = findByPropsLazy("isEditing", "isEditingAny"); const EditStore = findByPropsLazy("isEditing", "isEditingAny");
@@ -76,7 +76,9 @@ export default definePlugin({
}, },
onMessageClick(msg, channel, event) { onMessageClick(msg, channel, event) {
const isMe = msg.author.id === UserStore.getCurrentUser().id; const myId = AuthenticationStore.getId();
const isMe = msg.author.id === myId;
const isSelfInvokedUserApp = msg.interactionMetadata?.authorizing_integration_owners[ApplicationIntegrationType.USER_INSTALL] === myId;
if (!isDeletePressed) { if (!isDeletePressed) {
if (event.detail < 2) return; if (event.detail < 2) return;
if (settings.store.requireModifier && !event.ctrlKey && !event.shiftKey) return; if (settings.store.requireModifier && !event.ctrlKey && !event.shiftKey) return;
@@ -106,7 +108,7 @@ export default definePlugin({
showMentionToggle: channel.guild_id !== null showMentionToggle: channel.guild_id !== null
}); });
} }
} else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel))) { } else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel) || isSelfInvokedUserApp)) {
if (msg.deleted) { if (msg.deleted) {
FluxDispatcher.dispatch({ FluxDispatcher.dispatch({
type: "MESSAGE_DELETE", type: "MESSAGE_DELETE",

View File

@@ -22,7 +22,7 @@ import { Devs } from "@utils/constants";
import definePlugin, { IconComponent, StartAt } from "@utils/types"; import definePlugin, { IconComponent, StartAt } from "@utils/types";
import { CloudUpload, MessageAttachment } from "@vencord/discord-types"; import { CloudUpload, MessageAttachment } from "@vencord/discord-types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { DraftStore, DraftType, SelectedChannelStore, UserStore, useStateFromStores } from "@webpack/common"; import { DraftStore, DraftType, UserStore, useStateFromStores } from "@webpack/common";
const UploadStore = findByPropsLazy("getUploads"); const UploadStore = findByPropsLazy("getUploads");
@@ -89,11 +89,10 @@ const PreviewIcon: IconComponent = ({ height = 20, width = 20, className }) => {
); );
}; };
const PreviewButton: ChatBarButtonFactory = ({ isMainChat, isEmpty, type: { attachments } }) => { const PreviewButton: ChatBarButtonFactory = ({ isAnyChat, isEmpty, type: { attachments }, channel: { id: channelId } }) => {
const channelId = SelectedChannelStore.getChannelId();
const draft = useStateFromStores([DraftStore], () => getDraft(channelId)); const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
if (!isMainChat) return null; if (!isAnyChat) return null;
const hasAttachments = attachments && UploadStore.getUploads(channelId, DraftType.ChannelMessage).length > 0; const hasAttachments = attachments && UploadStore.getUploads(channelId, DraftType.ChannelMessage).length > 0;
const hasContent = !isEmpty && draft?.length > 0; const hasContent = !isEmpty && draft?.length > 0;

View File

@@ -134,7 +134,7 @@ export default definePlugin({
}, },
// Reaction List // Reaction List
{ {
find: ".reactorDefault", find: ".reactionDefault",
replacement: { replacement: {
match: /tag:"strong"(?=.{0,50}\i\.name)(?<=onContextMenu:.{0,15}\((\i),(\i),\i\).+?)/, match: /tag:"strong"(?=.{0,50}\i\.name)(?<=onContextMenu:.{0,15}\((\i),(\i),\i\).+?)/,
replace: "$&,style:$self.getColorStyle($2?.id,$1?.channel?.id)" replace: "$&,style:$self.getColorStyle($2?.id,$1?.channel?.id)"

View File

@@ -48,7 +48,7 @@ function parseTime(time: string) {
return `<t:${Math.round(ms)}:t>`; return `<t:${Math.round(ms)}:t>`;
} }
const Formats = ["", "t", "T", "d", "D", "f", "F", "R"] as const; const Formats = ["", "t", "T", "d", "D", "f", "F", "s", "S", "R"] as const;
type Format = typeof Formats[number]; type Format = typeof Formats[number];
const cl = classNameFactory("vc-st-"); const cl = classNameFactory("vc-st-");
@@ -144,8 +144,8 @@ const SendTimestampIcon: IconComponent = ({ height = 20, width = 20, className }
); );
}; };
const SendTimestampButton: ChatBarButtonFactory = ({ isMainChat }) => { const SendTimestampButton: ChatBarButtonFactory = ({ isAnyChat }) => {
if (!isMainChat) return null; if (!isAnyChat) return null;
return ( return (
<ChatBarButton <ChatBarButton

View File

@@ -55,7 +55,7 @@ function FriendsIndicator() {
width: "100%", width: "100%",
fontSize: "12px", fontSize: "12px",
fontWeight: "600", fontWeight: "600",
color: "var(--header-secondary)", color: "var(--text-default)",
textTransform: "uppercase", textTransform: "uppercase",
textAlign: "center", textAlign: "center",
}}> }}>
@@ -79,7 +79,7 @@ function ServersIndicator() {
width: "100%", width: "100%",
fontSize: "12px", fontSize: "12px",
fontWeight: "600", fontWeight: "600",
color: "var(--header-secondary)", color: "var(--text-default)",
textTransform: "uppercase", textTransform: "uppercase",
textAlign: "center", textAlign: "center",
}}> }}>

View File

@@ -18,11 +18,12 @@
import { ILanguageRegistration } from "@vap/shiki"; import { ILanguageRegistration } from "@vap/shiki";
export const VPC_REPO = "Vap0r1ze/vapcord"; import { SHIKI_REPO, SHIKI_REPO_COMMIT } from "./themes";
export const VPC_REPO_COMMIT = "4d0e4b420fb1e4358852bbd18c804a6f5e54c0d7";
export const vpcRepoAssets = `https://raw.githubusercontent.com/${VPC_REPO}/${VPC_REPO_COMMIT}/assets/shiki-codeblocks`; export const JSON_REPO = "Vencord/ShikiPluginAssets";
export const vpcRepoGrammar = (fileName: string) => `${vpcRepoAssets}/${fileName}`; export const JSON_REPO_COMMIT = "75d69df9fdf596a31eef8b7f6f891231a6feab44";
export const vpcRepoLanguages = `${vpcRepoAssets}/languages.json`; export const JSON_URL = `https://cdn.jsdelivr.net/gh/${JSON_REPO}@${JSON_REPO_COMMIT}/grammars.json`;
export const shikiRepoGrammar = (name: string) => `https://cdn.jsdelivr.net/gh/${SHIKI_REPO}@${SHIKI_REPO_COMMIT}/packages/tm-grammars/grammars/${name}.json`;
export interface Language { export interface Language {
name: string; name: string;
@@ -36,22 +37,26 @@ export interface Language {
} }
export interface LanguageJson { export interface LanguageJson {
name: string; name: string;
id: string; displayName: string;
fileName: string;
devicon?: string;
scopeName: string; scopeName: string;
devicon?: string;
aliases?: string[]; aliases?: string[];
} }
export const languages: Record<string, Language> = {}; export const languages: Record<string, Language> = {};
export const loadLanguages = async () => { export const loadLanguages = async () => {
const langsJson: LanguageJson[] = await fetch(vpcRepoLanguages).then(res => res.ok ? res.json() : []); const langsJson: LanguageJson[] = await fetch(JSON_URL).then(res => res.ok ? res.json() : []);
const loadedLanguages = Object.fromEntries( const loadedLanguages = Object.fromEntries(
langsJson.map(lang => [lang.id, { langsJson.map(lang => {
...lang, const { name, displayName, ...rest } = lang;
grammarUrl: vpcRepoGrammar(lang.fileName), return [name, {
}]) ...rest,
id: name,
name: displayName,
grammarUrl: shikiRepoGrammar(name),
}];
})
); );
Object.assign(languages, loadedLanguages); Object.assign(languages, loadedLanguages);
}; };

View File

@@ -19,8 +19,8 @@
import { IShikiTheme } from "@vap/shiki"; import { IShikiTheme } from "@vap/shiki";
export const SHIKI_REPO = "shikijs/textmate-grammars-themes"; export const SHIKI_REPO = "shikijs/textmate-grammars-themes";
export const SHIKI_REPO_COMMIT = "2d87559c7601a928b9f7e0f0dda243d2fb6d4499"; export const SHIKI_REPO_COMMIT = "bc5436518111d87ea58eb56d97b3f9bec30e6b83";
export const shikiRepoTheme = (name: string) => `https://raw.githubusercontent.com/${SHIKI_REPO}/${SHIKI_REPO_COMMIT}/packages/tm-themes/themes/${name}.json`; export const shikiRepoTheme = (name: string) => `https://cdn.jsdelivr.net/gh/${SHIKI_REPO}@${SHIKI_REPO_COMMIT}/packages/tm-themes/themes/${name}.json`;
export const themes = { export const themes = {
// Default // Default
@@ -48,6 +48,12 @@ export const themes = {
GithubLightDefault: shikiRepoTheme("github-light-default"), GithubLightDefault: shikiRepoTheme("github-light-default"),
GithubLightHighContrast: shikiRepoTheme("github-light-high-contrast"), GithubLightHighContrast: shikiRepoTheme("github-light-high-contrast"),
GithubLight: shikiRepoTheme("github-light"), GithubLight: shikiRepoTheme("github-light"),
GruvBoxDarkHard: shikiRepoTheme("gruvbox-dark-hard"),
GruvBoxDarkMedium: shikiRepoTheme("gruvbox-dark-medium"),
GruvBoxDarkSoft: shikiRepoTheme("gruvbox-dark-soft"),
GruvBoxLightHard: shikiRepoTheme("gruvbox-light-hard"),
GruvBoxLightMedium: shikiRepoTheme("gruvbox-light-medium"),
GruvBoxLightSoft: shikiRepoTheme("gruvbox-light-soft"),
Houston: shikiRepoTheme("houston"), Houston: shikiRepoTheme("houston"),
KanagawaDragon: shikiRepoTheme("kanagawa-dragon"), KanagawaDragon: shikiRepoTheme("kanagawa-dragon"),
KanagawaLotus: shikiRepoTheme("kanagawa-lotus"), KanagawaLotus: shikiRepoTheme("kanagawa-lotus"),

View File

@@ -1 +1 @@
@import url("https://cdn.jsdelivr.net/gh/devicons/devicon@v2.10.1/devicon.min.css"); @import url("https://cdn.jsdelivr.net/gh/devicons/devicon@v2.17.0/devicon.min.css");

View File

@@ -457,11 +457,11 @@ export default definePlugin({
// Filter hidden channels from GuildChannelStore.getChannels unless told otherwise // Filter hidden channels from GuildChannelStore.getChannels unless told otherwise
match: /(?<=getChannels\(\i)(\){.*?)return (.+?)}/, match: /(?<=getChannels\(\i)(\){.*?)return (.+?)}/,
replace: (_, rest, channels) => `,shouldIncludeHidden${rest}return $self.resolveGuildChannels(${channels},shouldIncludeHidden??arguments[0]==="@favorites");}` replace: (_, rest, channels) => `,shouldIncludeHidden${rest}return $self.resolveGuildChannels(${channels},shouldIncludeHidden??arguments[0]==="@favorites");}`
} },
] ]
}, },
{ {
find: "#{intl::FORM_LABEL_MUTED}", find: ".invitesDisabledTooltip",
replacement: { replacement: {
// Make GuildChannelStore.getChannels return hidden channels // Make GuildChannelStore.getChannels return hidden channels
match: /(?<=getChannels\(\i)(?=\))/, match: /(?<=getChannels\(\i)(?=\))/,

View File

@@ -149,7 +149,7 @@
} }
.vc-spotify-comma { .vc-spotify-comma {
color: var(--header-secondary); color: var(--text-default);
} }
.vc-spotify-artist[role="link"]:hover, .vc-spotify-artist[role="link"]:hover,

View File

@@ -42,7 +42,7 @@ export const TranslateIcon: IconComponent = ({ height = 20, width = 20, classNam
export let setShouldShowTranslateEnabledTooltip: undefined | ((show: boolean) => void); export let setShouldShowTranslateEnabledTooltip: undefined | ((show: boolean) => void);
export const TranslateChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => { export const TranslateChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
const { autoTranslate, showChatBarButton } = settings.use(["autoTranslate", "showChatBarButton"]); const { autoTranslate } = settings.use(["autoTranslate"]);
const [shouldShowTranslateEnabledTooltip, setter] = useState(false); const [shouldShowTranslateEnabledTooltip, setter] = useState(false);
useEffect(() => { useEffect(() => {
@@ -50,7 +50,7 @@ export const TranslateChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
return () => setShouldShowTranslateEnabledTooltip = undefined; return () => setShouldShowTranslateEnabledTooltip = undefined;
}, []); }, []);
if (!isMainChat || !showChatBarButton) return null; if (!isMainChat) return null;
const toggle = () => { const toggle = () => {
const newState = !autoTranslate; const newState = !autoTranslate;

View File

@@ -45,11 +45,6 @@ export const settings = definePluginSettings({
hidden: true hidden: true
}, },
showChatBarButton: {
type: OptionType.BOOLEAN,
description: "Show translate button in chat bar",
default: true
},
service: { service: {
type: OptionType.SELECT, type: OptionType.SELECT,
description: IS_WEB ? "Translation service (Not supported on Web!)" : "Translation service", description: IS_WEB ? "Translation service (Not supported on Web!)" : "Translation service",

View File

@@ -645,6 +645,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
defautluser0: { defautluser0: {
name: "defautluser0", name: "defautluser0",
id: 912035400485330954n id: 912035400485330954n
},
vv: {
name: "VV",
id: 254866377087778816n
} }
} satisfies Record<string, Dev>); } satisfies Record<string, Dev>);

View File

@@ -18,6 +18,7 @@
import type * as t from "@vencord/discord-types"; import type * as t from "@vencord/discord-types";
import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "@webpack"; import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "@webpack";
import type * as TSPattern from "ts-pattern";
export let FluxDispatcher: t.FluxDispatcher; export let FluxDispatcher: t.FluxDispatcher;
waitFor(["dispatch", "subscribe"], m => { waitFor(["dispatch", "subscribe"], m => {
@@ -47,7 +48,7 @@ export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYea
export const hljs: typeof import("highlight.js").default = findByPropsLazy("highlight", "registerLanguage"); export const hljs: typeof import("highlight.js").default = findByPropsLazy("highlight", "registerLanguage");
export const { match, P }: Pick<typeof import("ts-pattern"), "match" | "P"> = mapMangledModuleLazy("@ts-pattern/matcher", { export const { match, P }: { match: typeof TSPattern["match"], P: typeof TSPattern["P"]; } = mapMangledModuleLazy("@ts-pattern/matcher", {
match: filters.byCode("return new"), match: filters.byCode("return new"),
P: filters.byProps("when") P: filters.byProps("when")
}); });