This commit is contained in:
boop-the-dog
2025-10-10 12:08:05 -04:00
120 changed files with 2027 additions and 1166 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.12.13", "version": "1.13.2",
"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

@@ -0,0 +1 @@
node_modules

View File

@@ -0,0 +1 @@
Hint: https://docs.discord.food is an incredible resource and allows you to copy paste complete enums and interfaces

View File

@@ -0,0 +1,30 @@
export const enum ActivityType {
PLAYING = 0,
STREAMING = 1,
LISTENING = 2,
WATCHING = 3,
CUSTOM_STATUS = 4,
COMPETING = 5,
HANG_STATUS = 6
}
export const enum ActivityFlags {
INSTANCE = 1 << 0,
JOIN = 1 << 1,
/** @deprecated */
SPECTATE = 1 << 2,
/** @deprecated */
JOIN_REQUEST = 1 << 3,
SYNC = 1 << 4,
PLAY = 1 << 5,
PARTY_PRIVACY_FRIENDS = 1 << 6,
PARTY_PRIVACY_VOICE_CHANNEL = 1 << 7,
EMBEDDED = 1 << 8,
CONTEXTLESS = 1 << 9
}
export const enum ActivityStatusDisplayType {
NAME = 0,
STATE = 1,
DETAILS = 2
}

View File

@@ -1,3 +1,4 @@
export * from "./activity";
export * from "./channel"; export * from "./channel";
export * from "./commands"; export * from "./commands";
export * from "./messages"; export * from "./messages";

View File

@@ -11,3 +11,586 @@ export const enum StickerFormatType {
LOTTIE = 3, LOTTIE = 3,
GIF = 4 GIF = 4
} }
export const enum MessageType {
/**
* A default message (see below)
*
* Value: 0
* Name: DEFAULT
* Rendered Content: "{content}"
* Deletable: true
*/
DEFAULT = 0,
/**
* A message sent when a user is added to a group DM or thread
*
* Value: 1
* Name: RECIPIENT_ADD
* Rendered Content: "{author} added {mentions [0] } to the {group/thread}."
* Deletable: false
*/
RECIPIENT_ADD = 1,
/**
* A message sent when a user is removed from a group DM or thread
*
* Value: 2
* Name: RECIPIENT_REMOVE
* Rendered Content: "{author} removed {mentions [0] } from the {group/thread}."
* Deletable: false
*/
RECIPIENT_REMOVE = 2,
/**
* A message sent when a user creates a call in a private channel
*
* Value: 3
* Name: CALL
* Rendered Content: participated ? "{author} started a call{ended ? " that lasted {duration}" : " — Join the call"}." : "You missed a call from {author} that lasted {duration}."
* Deletable: false
*/
CALL = 3,
/**
* A message sent when a group DM or thread's name is changed
*
* Value: 4
* Name: CHANNEL_NAME_CHANGE
* Rendered Content: "{author} changed the {is_forum ? "post title" : "channel name"}: {content} "
* Deletable: false
*/
CHANNEL_NAME_CHANGE = 4,
/**
* A message sent when a group DM's icon is changed
*
* Value: 5
* Name: CHANNEL_ICON_CHANGE
* Rendered Content: "{author} changed the channel icon."
* Deletable: false
*/
CHANNEL_ICON_CHANGE = 5,
/**
* A message sent when a message is pinned in a channel
*
* Value: 6
* Name: CHANNEL_PINNED_MESSAGE
* Rendered Content: "{author} pinned a message to this channel."
* Deletable: true
*/
CHANNEL_PINNED_MESSAGE = 6,
/**
* A message sent when a user joins a guild
*
* Value: 7
* Name: USER_JOIN
* Rendered Content: See user join message type , obtained via the formula timestamp_ms % 13
* Deletable: true
*/
USER_JOIN = 7,
/**
* A message sent when a user subscribes to (boosts) a guild
*
* Value: 8
* Name: PREMIUM_GUILD_SUBSCRIPTION
* Rendered Content: "{author} just boosted the server{content ? " {content} times"}!"
* Deletable: true
*/
PREMIUM_GUILD_SUBSCRIPTION = 8,
/**
* A message sent when a user subscribes to (boosts) a guild to tier 1
*
* Value: 9
* Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_1
* Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 1! "
* Deletable: true
*/
PREMIUM_GUILD_SUBSCRIPTION_TIER_1 = 9,
/**
* A message sent when a user subscribes to (boosts) a guild to tier 2
*
* Value: 10
* Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_2
* Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 2! "
* Deletable: true
*/
PREMIUM_GUILD_SUBSCRIPTION_TIER_2 = 10,
/**
* A message sent when a user subscribes to (boosts) a guild to tier 3
*
* Value: 11
* Name: PREMIUM_GUILD_SUBSCRIPTION_TIER_3
* Rendered Content: "{author} just boosted the server{content ? " {content} times"}! {guild} has achieved Level 3! "
* Deletable: true
*/
PREMIUM_GUILD_SUBSCRIPTION_TIER_3 = 11,
/**
* A message sent when a news channel is followed
*
* Value: 12
* Name: CHANNEL_FOLLOW_ADD
* Rendered Content: "{author} has added {content} to this channel. Its most important updates will show up here."
* Deletable: true
*/
CHANNEL_FOLLOW_ADD = 12,
/**
* A message sent when a guild is disqualified from discovery
*
* Value: 14
* Name: GUILD_DISCOVERY_DISQUALIFIED
* Rendered Content: "This server has been removed from Server Discovery because it no longer passes all the requirements. Check Server Settings for more details."
* Deletable: true
*/
GUILD_DISCOVERY_DISQUALIFIED = 14,
/**
* A message sent when a guild requalifies for discovery
*
* Value: 15
* Name: GUILD_DISCOVERY_REQUALIFIED
* Rendered Content: "This server is eligible for Server Discovery again and has been automatically relisted!"
* Deletable: true
*/
GUILD_DISCOVERY_REQUALIFIED = 15,
/**
* A message sent when a guild has failed discovery requirements for a week
*
* Value: 16
* Name: GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING
* Rendered Content: "This server has failed Discovery activity requirements for 1 week. If this server fails for 4 weeks in a row, it will be automatically removed from Discovery."
* Deletable: true
*/
GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16,
/**
* A message sent when a guild has failed discovery requirements for 3 weeks
*
* Value: 17
* Name: GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING
* Rendered Content: "This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery."
* Deletable: true
*/
GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17,
/**
* A message sent when a thread is created
*
* Value: 18
* Name: THREAD_CREATED
* Rendered Content: "{author} started a thread: {content} . See all threads."
* Deletable: true
*/
THREAD_CREATED = 18,
/**
* A message sent when a user replies to a message
*
* Value: 19
* Name: REPLY
* Rendered Content: "{content}"
* Deletable: true
*/
REPLY = 19,
/**
* A message sent when a user uses a slash command
*
* Value: 20
* Name: CHAT_INPUT_COMMAND
* Rendered Content: "{content}"
* Deletable: true
*/
CHAT_INPUT_COMMAND = 20,
/**
* A message sent when a thread starter message is added to a thread
*
* Value: 21
* Name: THREAD_STARTER_MESSAGE
* Rendered Content: "{referenced_message?.content}" ?? "Sorry, we couldn't load the first message in this thread"
* Deletable: false
*/
THREAD_STARTER_MESSAGE = 21,
/**
* A message sent to remind users to invite friends to a guild
*
* Value: 22
* Name: GUILD_INVITE_REMINDER
* Rendered Content: "Wondering who to invite?\nStart by inviting anyone who can help you build the server!"
* Deletable: true
*/
GUILD_INVITE_REMINDER = 22,
/**
* A message sent when a user uses a context menu command
*
* Value: 23
* Name: CONTEXT_MENU_COMMAND
* Rendered Content: "{content}"
* Deletable: true
*/
CONTEXT_MENU_COMMAND = 23,
/**
* A message sent when auto moderation takes an action
*
* Value: 24
* Name: AUTO_MODERATION_ACTION
* Rendered Content: Special embed rendered from embeds[0]
* Deletable: true 1
*/
AUTO_MODERATION_ACTION = 24,
/**
* A message sent when a user purchases or renews a role subscription
*
* Value: 25
* Name: ROLE_SUBSCRIPTION_PURCHASE
* Rendered Content: "{author} {is_renewal ? "renewed" : "joined"} {role_subscription.tier_name} and has been a subscriber of {guild} for {role_subscription.total_months_subscribed} month(?s)!"
* Deletable: true
*/
ROLE_SUBSCRIPTION_PURCHASE = 25,
/**
* A message sent when a user is upsold to a premium interaction
*
* Value: 26
* Name: INTERACTION_PREMIUM_UPSELL
* Rendered Content: "{content}"
* Deletable: true
*/
INTERACTION_PREMIUM_UPSELL = 26,
/**
* A message sent when a stage channel starts
*
* Value: 27
* Name: STAGE_START
* Rendered Content: "{author} started {content} "
* Deletable: true
*/
STAGE_START = 27,
/**
* A message sent when a stage channel ends
*
* Value: 28
* Name: STAGE_END
* Rendered Content: "{author} ended {content} "
* Deletable: true
*/
STAGE_END = 28,
/**
* A message sent when a user starts speaking in a stage channel
*
* Value: 29
* Name: STAGE_SPEAKER
* Rendered Content: "{author} is now a speaker."
* Deletable: true
*/
STAGE_SPEAKER = 29,
/**
* A message sent when a user raises their hand in a stage channel
*
* Value: 30
* Name: STAGE_RAISE_HAND
* Rendered Content: "{author} requested to speak."
* Deletable: true
*/
STAGE_RAISE_HAND = 30,
/**
* A message sent when a stage channel's topic is changed
*
* Value: 31
* Name: STAGE_TOPIC
* Rendered Content: "{author} changed the Stage topic: {content} "
* Deletable: true
*/
STAGE_TOPIC = 31,
/**
* A message sent when a user purchases an application premium subscription
*
* Value: 32
* Name: GUILD_APPLICATION_PREMIUM_SUBSCRIPTION
* Rendered Content: "{author} upgraded {application ?? "a deleted application"} to premium for this server!"
* Deletable: true
*/
GUILD_APPLICATION_PREMIUM_SUBSCRIPTION = 32,
/**
* A message sent when a user gifts a premium (Nitro) referral
*
* Value: 35
* Name: PREMIUM_REFERRAL
* Rendered Content: "{content}"
* Deletable: true
*/
PREMIUM_REFERRAL = 35,
/**
* A message sent when a user enabled lockdown for the guild
*
* Value: 36
* Name: GUILD_INCIDENT_ALERT_MODE_ENABLED
* Rendered Content: "{author} enabled security actions until {content}."
* Deletable: true
*/
GUILD_INCIDENT_ALERT_MODE_ENABLED = 36,
/**
* A message sent when a user disables lockdown for the guild
*
* Value: 37
* Name: GUILD_INCIDENT_ALERT_MODE_DISABLED
* Rendered Content: "{author} disabled security actions."
* Deletable: true
*/
GUILD_INCIDENT_ALERT_MODE_DISABLED = 37,
/**
* A message sent when a user reports a raid for the guild
*
* Value: 38
* Name: GUILD_INCIDENT_REPORT_RAID
* Rendered Content: "{author} reported a raid in {guild}."
* Deletable: true
*/
GUILD_INCIDENT_REPORT_RAID = 38,
/**
* A message sent when a user reports a false alarm for the guild
*
* Value: 39
* Name: GUILD_INCIDENT_REPORT_FALSE_ALARM
* Rendered Content: "{author} reported a false alarm in {guild}."
* Deletable: true
*/
GUILD_INCIDENT_REPORT_FALSE_ALARM = 39,
/**
* A message sent when no one sends a message in the current channel for 1 hour
*
* Value: 40
* Name: GUILD_DEADCHAT_REVIVE_PROMPT
* Rendered Content: "{content}"
* Deletable: true
*/
GUILD_DEADCHAT_REVIVE_PROMPT = 40,
/**
* A message sent when a user buys another user a gift
*
* Value: 41
* Name: CUSTOM_GIFT
* Rendered Content: Special embed rendered from embeds[0].url and gift_info
* Deletable: true
*/
CUSTOM_GIFT = 41,
/**
* Value: 42
* Name: GUILD_GAMING_STATS_PROMPT
* Rendered Content: "{content}"
* Deletable: true
*/
GUILD_GAMING_STATS_PROMPT = 42,
/**
* A message sent when a user purchases a guild product
*
* Value: 44
* Name: PURCHASE_NOTIFICATION
* Rendered Content: "{author} has purchased {purchase_notification.guild_product_purchase.product_name}!"
* Deletable: true
*/
PURCHASE_NOTIFICATION = 44,
/**
* A message sent when a poll is finalized
*
* Value: 46
* Name: POLL_RESULT
* Rendered Content: Special embed rendered from embeds[0]
* Deletable: true
*/
POLL_RESULT = 46,
/**
* A message sent by the Discord Updates account when a new changelog is posted
*
* Value: 47
* Name: CHANGELOG
* Rendered Content: "{content}"
* Deletable: true
*/
CHANGELOG = 47,
/**
* A message sent when a Nitro promotion is triggered
*
* Value: 48
* Name: NITRO_NOTIFICATION
* Rendered Content: Special embed rendered from content
* Deletable: true
*/
NITRO_NOTIFICATION = 48,
/**
* A message sent when a voice channel is linked to a lobby
*
* Value: 49
* Name: CHANNEL_LINKED_TO_LOBBY
* Rendered Content: "{content}"
* Deletable: true
*/
CHANNEL_LINKED_TO_LOBBY = 49,
/**
* A local-only ephemeral message sent when a user is prompted to gift Nitro to a friend on their friendship anniversary
*
* Value: 50
* Name: GIFTING_PROMPT
* Rendered Content: Special embed
* Deletable: true
*/
GIFTING_PROMPT = 50,
/**
* A local-only message sent when a user receives an in-game message NUX
*
* Value: 51
* Name: IN_GAME_MESSAGE_NUX
* Rendered Content: "{author} messaged you from {application.name}. In-game chat may not include rich messaging features such as images, polls, or apps. Learn More "
* Deletable: true
*/
IN_GAME_MESSAGE_NUX = 51,
/**
* A message sent when a user accepts a guild join request
*
* Value: 52
* Name: GUILD_JOIN_REQUEST_ACCEPT_NOTIFICATION 2
* Rendered Content: "{join_request.user}'s application to {content} was approved! Welcome!"
* Deletable: true
*/
GUILD_JOIN_REQUEST_ACCEPT_NOTIFICATION = 52,
/**
* A message sent when a user rejects a guild join request
*
* Value: 53
* Name: GUILD_JOIN_REQUEST_REJECT_NOTIFICATION 2
* Rendered Content: "{join_request.user}'s application to {content} was rejected."
* Deletable: true
*/
GUILD_JOIN_REQUEST_REJECT_NOTIFICATION = 53,
/**
* A message sent when a user withdraws a guild join request
*
* Value: 54
* Name: GUILD_JOIN_REQUEST_WITHDRAWN_NOTIFICATION 2
* Rendered Content: "{join_request.user}'s application to {content} has been withdrawn."
* Deletable: true
*/
GUILD_JOIN_REQUEST_WITHDRAWN_NOTIFICATION = 54,
/**
* A message sent when a user upgrades to HD streaming
*
* Value: 55
* Name: HD_STREAMING_UPGRADED
* Rendered Content: "{author} activated HD Splash Potion "
* Deletable: true
*/
HD_STREAMING_UPGRADED = 55,
/**
* A message sent when a user resolves a moderation report by deleting the offending message
*
* Value: 58
* Name: REPORT_TO_MOD_DELETED_MESSAGE
* Rendered Content: "{author} deleted the message"
* Deletable: true
*/
REPORT_TO_MOD_DELETED_MESSAGE = 58,
/**
* A message sent when a user resolves a moderation report by timing out the offending user
*
* Value: 59
* Name: REPORT_TO_MOD_TIMEOUT_USER
* Rendered Content: "{author} timed out {mentions [0] }"
* Deletable: true
*/
REPORT_TO_MOD_TIMEOUT_USER = 59,
/**
* A message sent when a user resolves a moderation report by kicking the offending user
*
* Value: 60
* Name: REPORT_TO_MOD_KICK_USER
* Rendered Content: "{author} kicked {mentions [0] }"
* Deletable: true
*/
REPORT_TO_MOD_KICK_USER = 60,
/**
* A message sent when a user resolves a moderation report by banning the offending user
*
* Value: 61
* Name: REPORT_TO_MOD_BAN_USER
* Rendered Content: "{author} banned {mentions [0] }"
* Deletable: true
*/
REPORT_TO_MOD_BAN_USER = 61,
/**
* A message sent when a user resolves a moderation report
*
* Value: 62
* Name: REPORT_TO_MOD_CLOSED_REPORT
* Rendered Content: "{author} resolved this flag"
* Deletable: true
*/
REPORT_TO_MOD_CLOSED_REPORT = 62,
/**
* A message sent when a user adds a new emoji to a guild
*
* Value: 63
* Name: EMOJI_ADDED
* Rendered Content: "{author} added a new emoji, {content} :{emoji.name}: "
* Deletable: true
*/
EMOJI_ADDED = 63,
}
export const enum MessageFlags {
/**
* Message has been published to subscribed channels (via Channel Following)
*
* Value: 1 << 0
*/
CROSSPOSTED = 1 << 0,
/**
* Message originated from a message in another channel (via Channel Following)
*/
IS_CROSSPOST = 1 << 1,
/**
* Embeds will not be included when serializing this message
*/
SUPPRESS_EMBEDS = 1 << 2,
/**
* Source message for this crosspost has been deleted (via Channel Following)
*/
SOURCE_MESSAGE_DELETED = 1 << 3,
/**
* Message came from the urgent message system
*/
URGENT = 1 << 4,
/**
* Message has an associated thread, with the same ID as the message
*/
HAS_THREAD = 1 << 5,
/**
* Message is only visible to the user who invoked the interaction
*/
EPHEMERAL = 1 << 6,
/**
* Message is an interaction response and the bot is "thinking"
*/
LOADING = 1 << 7,
/**
* Some roles were not mentioned and added to the thread
*/
FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8,
/**
* Message is hidden from the guild's feed
*/
GUILD_FEED_HIDDEN = 1 << 9,
/**
* Message contains a link that impersonates Discord
*/
SHOULD_SHOW_LINK_NOT_DISCORD_WARNING = 1 << 10,
/**
* Message will not trigger push and desktop notifications
*/
SUPPRESS_NOTIFICATIONS = 1 << 12,
/**
* Message's audio attachment is rendered as a voice message
*/
IS_VOICE_MESSAGE = 1 << 13,
/**
* Message has a forwarded message snapshot attached
*/
HAS_SNAPSHOT = 1 << 14,
/**
* Message contains components from version 2 of the UI kit
*/
IS_COMPONENTS_V2 = 1 << 15,
/**
* Message was triggered by the social layer integration
*/
SENT_BY_SOCIAL_LAYER_INTEGRATION = 1 << 16,
}

View File

@@ -1,6 +1,7 @@
{ {
"name": "@vencord/discord-types", "name": "@vencord/discord-types",
"author": "Vencord Contributors", "author": "Vencord Contributors",
"private": false,
"description": "Typescript definitions for the webpack modules of the Discord Web app", "description": "Typescript definitions for the webpack modules of the Discord Web app",
"version": "1.0.0", "version": "1.0.0",
"license": "LGPL-3.0-or-later", "license": "LGPL-3.0-or-later",
@@ -12,8 +13,10 @@
"directory": "packages/discord-types" "directory": "packages/discord-types"
}, },
"dependencies": { "dependencies": {
"@types/react": "^19.0.10",
"moment": "^2.22.2", "moment": "^2.22.2",
"type-fest": "^4.41.0" "type-fest": "^4.41.0"
},
"peerDependencies": {
"@types/react": "^19.0.10"
} }
} }

View File

@@ -0,0 +1,36 @@
import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "../../enums";
export interface ActivityAssets {
large_image?: string;
large_text?: string;
small_image?: string;
small_text?: string;
}
export interface ActivityButton {
label: string;
url: string;
}
export interface Activity {
name: string;
application_id: string;
type: ActivityType;
state?: string;
state_url?: string;
details?: string;
details_url?: string;
url?: string;
flags: ActivityFlags;
status_display_type?: ActivityStatusDisplayType;
timestamps?: {
start?: number;
end?: number;
};
assets?: ActivityAssets;
buttons?: string[];
metadata?: {
button_urls?: Array<string>;
};
}

View File

@@ -1,3 +1,4 @@
export * from "./Activity";
export * from "./Application"; export * from "./Application";
export * from "./Channel"; export * from "./Channel";
export * from "./Guild"; export * from "./Guild";

View File

@@ -2,9 +2,9 @@ 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 { StickerFormatType } from "../../../enums"; import { 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;
*/ */
export class Message extends DiscordRecord { export class Message extends DiscordRecord {
@@ -35,7 +35,7 @@ export class Message extends DiscordRecord {
customRenderedContent: unknown; customRenderedContent: unknown;
editedTimestamp: Date; editedTimestamp: Date;
embeds: Embed[]; embeds: Embed[];
flags: number; flags: MessageFlags;
giftCodes: string[]; giftCodes: string[];
id: string; id: string;
interaction: { interaction: {
@@ -100,7 +100,7 @@ export class Message extends DiscordRecord {
stickers: unknown[]; stickers: unknown[];
timestamp: moment.Moment; timestamp: moment.Moment;
tts: boolean; tts: boolean;
type: number; type: MessageType;
webhookId: string | undefined; webhookId: string | undefined;
/** /**
@@ -121,10 +121,13 @@ export class Message extends DiscordRecord {
removeReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message; removeReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message;
getChannelId(): string; getChannelId(): string;
hasFlag(flag: number): boolean; hasFlag(flag: MessageFlags): boolean;
isCommandType(): boolean; isCommandType(): boolean;
isEdited(): boolean; isEdited(): boolean;
isSystemDM(): boolean; isSystemDM(): boolean;
/** Vencord added */
deleted?: boolean;
} }
/** A smaller Message object found in FluxDispatcher and elsewhere. */ /** A smaller Message object found in FluxDispatcher and elsewhere. */
@@ -193,3 +196,9 @@ export interface MessageReaction {
emoji: ReactionEmoji; emoji: ReactionEmoji;
me: boolean; me: boolean;
} }
// Object.keys(findByProps("REPLYABLE")).map(JSON.stringify).join("|")
export type MessageTypeSets = Record<
"UNDELETABLE" | "GUILD_DISCOVERY_STATUS" | "USER_MESSAGE" | "NOTIFIABLE_SYSTEM_MESSAGE" | "REPLYABLE" | "FORWARDABLE" | "REFERENCED_MESSAGE_AVAILABLE" | "AVAILABLE_IN_GUILD_FEED" | "DEADCHAT_PROMPTS" | "NON_COLLAPSIBLE" | "NON_PARSED" | "AUTOMOD_INCIDENT_ACTIONS" | "SELF_MENTIONABLE_SYSTEM" | "SCHEDULABLE",
Set<MessageType>
>;

View File

@@ -161,19 +161,6 @@ export type Button = ComponentType<ButtonProps> & {
Link: any; Link: any;
}; };
export type Switch = ComponentType<PropsWithChildren<{
value: boolean;
onChange(value: boolean): void;
disabled?: boolean;
hideBorder?: boolean;
className?: string;
style?: CSSProperties;
note?: ReactNode;
tooltipNote?: ReactNode;
}>>;
export type CheckboxAligns = { export type CheckboxAligns = {
CENTER: "center"; CENTER: "center";
TOP: "top"; TOP: "top";

View File

@@ -6,13 +6,15 @@ import type { FluxEvents } from "./fluxEvents";
export { FluxEvents }; export { FluxEvents };
type FluxEventsAutoComplete = LiteralUnion<FluxEvents, string>;
export interface FluxDispatcher { export interface FluxDispatcher {
_actionHandlers: any; _actionHandlers: any;
_subscriptions: any; _subscriptions: any;
dispatch(event: { [key: string]: unknown; type: FluxEvents; }): Promise<void>; dispatch(event: { [key: string]: unknown; type: FluxEventsAutoComplete; }): Promise<void>;
isDispatching(): boolean; isDispatching(): boolean;
subscribe(event: FluxEvents, callback: (data: any) => void): void; subscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void;
unsubscribe(event: FluxEvents, callback: (data: any) => void): void; unsubscribe(event: FluxEventsAutoComplete, callback: (data: any) => void): void;
wait(callback: () => void): void; wait(callback: () => void): void;
} }

View File

@@ -1,7 +1,7 @@
{ {
"name": "@vencord/types", "name": "@vencord/types",
"private": false, "private": false,
"version": "1.11.5", "version": "1.13.2",
"description": "", "description": "",
"types": "index.d.ts", "types": "index.d.ts",
"scripts": { "scripts": {
@@ -19,9 +19,14 @@
"dependencies": { "dependencies": {
"@types/lodash": "4.17.15", "@types/lodash": "4.17.15",
"@types/node": "^22.13.4", "@types/node": "^22.13.4",
"@types/react": "18.3.1", "@vencord/discord-types": "^1.0.0",
"@types/react-dom": "18.3.1", "highlight.js": "11.11.1",
"standalone-electron-types": "^34.2.0", "moment": "^2.22.2",
"ts-pattern": "^5.6.0",
"type-fest": "^4.35.0" "type-fest": "^4.35.0"
},
"peerDependencies": {
"@types/react": "18.3.1",
"@types/react-dom": "18.3.1"
} }
} }

26
pnpm-lock.yaml generated
View File

@@ -195,9 +195,18 @@ importers:
'@types/react-dom': '@types/react-dom':
specifier: 18.3.1 specifier: 18.3.1
version: 18.3.1 version: 18.3.1
standalone-electron-types: '@vencord/discord-types':
specifier: ^34.2.0 specifier: ^1.0.0
version: 34.2.0 version: 1.0.0(@types/react@18.3.1)
highlight.js:
specifier: 11.11.1
version: 11.11.1
moment:
specifier: ^2.22.2
version: 2.30.1
ts-pattern:
specifier: ^5.6.0
version: 5.6.2
type-fest: type-fest:
specifier: ^4.35.0 specifier: ^4.35.0
version: 4.38.0 version: 4.38.0
@@ -742,6 +751,11 @@ packages:
'@vap/shiki@0.10.5': '@vap/shiki@0.10.5':
resolution: {integrity: sha512-5BHVGvQT8qonbLSASon5aQFQ18OZU4FxSl9tLSj6oJ0sap3KdMbYcfGq25M9zFZR1g1dJN7fgjmZXBIS5omIdw==} resolution: {integrity: sha512-5BHVGvQT8qonbLSASon5aQFQ18OZU4FxSl9tLSj6oJ0sap3KdMbYcfGq25M9zFZR1g1dJN7fgjmZXBIS5omIdw==}
'@vencord/discord-types@1.0.0':
resolution: {integrity: sha512-idtijKdbjiqo8k+4mo5Aq9zrQYwr2u11VmiRasaf+8+cGC5bYsMTIZl7zKLF3VdYx2a0ICjxgZtcfa1U3mv1Yg==}
peerDependencies:
'@types/react': ^19.0.10
abort-controller@3.0.0: abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'} engines: {node: '>=6.5'}
@@ -3499,6 +3513,12 @@ snapshots:
vscode-oniguruma: 1.7.0 vscode-oniguruma: 1.7.0
vscode-textmate: 5.2.0 vscode-textmate: 5.2.0
'@vencord/discord-types@1.0.0(@types/react@18.3.1)':
dependencies:
'@types/react': 18.3.1
moment: 2.30.1
type-fest: 4.41.0
abort-controller@3.0.0: abort-controller@3.0.0:
dependencies: dependencies:
event-target-shim: 5.0.1 event-target-shim: 5.0.1

View File

@@ -1,4 +1,4 @@
.nx-error-card { .vc-error-card {
border: 1px solid var(--border-subtle); border: 1px solid var(--border-subtle);
background-color: var(--background-feedback-critical); background-color: var(--background-feedback-critical);
padding: 24px; padding: 24px;

View File

@@ -23,7 +23,7 @@ import type { HTMLProps } from "react";
export function ErrorCard(props: React.PropsWithChildren<HTMLProps<HTMLDivElement>>) { export function ErrorCard(props: React.PropsWithChildren<HTMLProps<HTMLDivElement>>) {
return ( return (
<div {...props} className={classes(props.className, "nx-error-card")}> <div {...props} className={classes(props.className, "vc-error-card")}>
{props.children} {props.children}
</div> </div>
); );

View File

@@ -0,0 +1,5 @@
.vc-form-divider {
border-top: thin solid var(--border-subtle);
width: 100%;
height: 1px;
}

View File

@@ -0,0 +1,13 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./FormDivider.css";
import { classes } from "@utils/misc";
export function FormDivider({ className }: { className?: string; }) {
return <div className={classes("vc-form-divider", className)} />;
}

View File

@@ -0,0 +1,29 @@
.vc-form-switch-wrapper {
margin-bottom: 20px;
}
.vc-form-switch {
display: flex;
width: 100%;
> :last-child {
margin-left: auto;
}
}
.vc-form-switch-disabled {
opacity: 0.5;
pointer-events: none;
cursor: not-allowed;
}
.vc-form-switch-text {
display: flex;
flex-direction: column;
justify-content: center;
gap: 8px;
}
.vc-form-switch-border {
margin-top: 20px;
}

View File

@@ -0,0 +1,46 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./FormSwitch.css";
import { classes } from "@utils/misc";
import { Text } from "@webpack/common";
import type { PropsWithChildren, ReactNode } from "react";
import { FormDivider } from "./FormDivider";
import { Switch } from "./Switch";
export interface FormSwitchProps {
title: ReactNode;
description?: ReactNode;
value: boolean;
onChange(value: boolean): void;
className?: string;
disabled?: boolean;
hideBorder?: boolean;
}
export function FormSwitch({ onChange, title, value, description, disabled, className, hideBorder }: FormSwitchProps) {
return (
<div className="vc-form-switch-wrapper">
<div className={classes("vc-form-switch", className, disabled && "vc-form-switch-disabled")}>
<div className={"vc-form-switch-text"}>
<Text variant="text-md/medium">{title}</Text>
{description && <Text variant="text-sm/normal">{description}</Text>}
</div>
<Switch checked={value} onChange={onChange} disabled={disabled} />
</div>
{!hideBorder && <FormDivider className="vc-form-switch-border" />}
</div>
);
}
/** Compatibility with Discord's old FormSwitch */
export function FormSwitchCompat(props: PropsWithChildren<any>) {
return <FormSwitch {...props} title={props.children ?? ""} description={props.note} />;
}

View File

@@ -31,7 +31,7 @@ type IconProps = JSX.IntrinsicElements["svg"];
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) { function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
return ( return (
<svg <svg
className={classes(className, "nx-icon")} className={classes(className, "vc-icon")}
role="img" role="img"
width={width} width={width}
height={height} height={height}
@@ -51,7 +51,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
<Icon <Icon
height={height} height={height}
width={width} width={width}
className={classes(className, "nx-link-icon")} className={classes(className, "vc-link-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<g fill="none" fillRule="evenodd"> <g fill="none" fillRule="evenodd">
@@ -69,7 +69,7 @@ export function CopyIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-copy-icon")} className={classes(props.className, "vc-copy-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<g fill="currentColor"> <g fill="currentColor">
@@ -88,7 +88,7 @@ export function OpenExternalIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-open-external-icon")} className={classes(props.className, "vc-open-external-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<polygon <polygon
@@ -104,7 +104,7 @@ export function ImageIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-image-icon")} className={classes(props.className, "vc-image-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path fill="currentColor" d="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" /> <path fill="currentColor" d="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
@@ -116,7 +116,7 @@ export function InfoIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-info-icon")} className={classes(props.className, "vc-info-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
@@ -133,7 +133,7 @@ export function OwnerCrownIcon(props: IconProps) {
<Icon <Icon
aria-label={getIntlMessage("GUILD_OWNER")} aria-label={getIntlMessage("GUILD_OWNER")}
{...props} {...props}
className={classes(props.className, "nx-owner-crown-icon")} className={classes(props.className, "vc-owner-crown-icon")}
role="img" role="img"
viewBox="0 0 16 16" viewBox="0 0 16 16"
> >
@@ -154,7 +154,7 @@ export function ScreenshareIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-screenshare-icon")} className={classes(props.className, "vc-screenshare-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
@@ -169,7 +169,7 @@ export function ImageVisible(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-image-visible")} className={classes(props.className, "vc-image-visible")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path fill="currentColor" d="M5 21q-.825 0-1.413-.587Q3 19.825 3 19V5q0-.825.587-1.413Q4.175 3 5 3h14q.825 0 1.413.587Q21 4.175 21 5v14q0 .825-.587 1.413Q19.825 21 19 21Zm0-2h14V5H5v14Zm1-2h12l-3.75-5-3 4L9 13Zm-1 2V5v14Z" /> <path fill="currentColor" d="M5 21q-.825 0-1.413-.587Q3 19.825 3 19V5q0-.825.587-1.413Q4.175 3 5 3h14q.825 0 1.413.587Q21 4.175 21 5v14q0 .825-.587 1.413Q19.825 21 19 21Zm0-2h14V5H5v14Zm1-2h12l-3.75-5-3 4L9 13Zm-1 2V5v14Z" />
@@ -181,7 +181,7 @@ export function ImageInvisible(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-image-invisible")} className={classes(props.className, "vc-image-invisible")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path fill="currentColor" d="m21 18.15-2-2V5H7.85l-2-2H19q.825 0 1.413.587Q21 4.175 21 5Zm-1.2 4.45L18.2 21H5q-.825 0-1.413-.587Q3 19.825 3 19V5.8L1.4 4.2l1.4-1.4 18.4 18.4ZM6 17l3-4 2.25 3 .825-1.1L5 7.825V19h11.175l-2-2Zm7.425-6.425ZM10.6 13.4Z" /> <path fill="currentColor" d="m21 18.15-2-2V5H7.85l-2-2H19q.825 0 1.413.587Q21 4.175 21 5Zm-1.2 4.45L18.2 21H5q-.825 0-1.413-.587Q3 19.825 3 19V5.8L1.4 4.2l1.4-1.4 18.4 18.4ZM6 17l3-4 2.25 3 .825-1.1L5 7.825V19h11.175l-2-2Zm7.425-6.425ZM10.6 13.4Z" />
@@ -193,7 +193,7 @@ export function Microphone(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-microphone")} className={classes(props.className, "vc-microphone")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path fillRule="evenodd" clipRule="evenodd" d="M14.99 11C14.99 12.66 13.66 14 12 14C10.34 14 9 12.66 9 11V5C9 3.34 10.34 2 12 2C13.66 2 15 3.34 15 5L14.99 11ZM12 16.1C14.76 16.1 17.3 14 17.3 11H19C19 14.42 16.28 17.24 13 17.72V21H11V17.72C7.72 17.23 5 14.41 5 11H6.7C6.7 14 9.24 16.1 12 16.1ZM12 4C11.2 4 11 4.66667 11 5V11C11 11.3333 11.2 12 12 12C12.8 12 13 11.3333 13 11V5C13 4.66667 12.8 4 12 4Z" fill="currentColor" /> <path fillRule="evenodd" clipRule="evenodd" d="M14.99 11C14.99 12.66 13.66 14 12 14C10.34 14 9 12.66 9 11V5C9 3.34 10.34 2 12 2C13.66 2 15 3.34 15 5L14.99 11ZM12 16.1C14.76 16.1 17.3 14 17.3 11H19C19 14.42 16.28 17.24 13 17.72V21H11V17.72C7.72 17.23 5 14.41 5 11H6.7C6.7 14 9.24 16.1 12 16.1ZM12 4C11.2 4 11 4.66667 11 5V11C11 11.3333 11.2 12 12 12C12.8 12 13 11.3333 13 11V5C13 4.66667 12.8 4 12 4Z" fill="currentColor" />
@@ -206,7 +206,7 @@ export function CogWheel(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-cog-wheel")} className={classes(props.className, "vc-cog-wheel")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
@@ -223,7 +223,7 @@ export function ReplyIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-reply-icon")} className={classes(props.className, "vc-reply-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
@@ -238,7 +238,7 @@ export function DeleteIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-delete-icon")} className={classes(props.className, "vc-delete-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
@@ -257,7 +257,7 @@ export function PlusIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-plus-icon")} className={classes(props.className, "vc-plus-icon")}
viewBox="0 0 18 18" viewBox="0 0 18 18"
> >
<polygon <polygon
@@ -273,7 +273,7 @@ export function NoEntrySignIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-no-entry-sign-icon")} className={classes(props.className, "vc-no-entry-sign-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
@@ -292,7 +292,7 @@ export function SafetyIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-safety-icon")} className={classes(props.className, "vc-safety-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
@@ -310,7 +310,7 @@ export function NotesIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-notes-icon")} className={classes(props.className, "vc-notes-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
@@ -331,7 +331,7 @@ export function FolderIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-folder-icon")} className={classes(props.className, "vc-folder-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
@@ -346,7 +346,7 @@ export function LogIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-log-icon")} className={classes(props.className, "vc-log-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
@@ -363,7 +363,7 @@ export function RestartIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-restart-icon")} className={classes(props.className, "vc-restart-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
@@ -378,7 +378,7 @@ export function PaintbrushIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-paintbrush-icon")} className={classes(props.className, "vc-paintbrush-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path
@@ -395,7 +395,7 @@ export function PencilIcon(props: IconProps) {
return ( return (
<Icon <Icon
{...props} {...props}
className={classes(props.className, "nx-pencil-icon")} className={classes(props.className, "vc-pencil-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path <path

View File

@@ -1,43 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Before you go complaining that this component is unecessary,
* This code was made *before* Vencord updated their UI, and it's
* been used in so many places that deprecating it is not worth
* the hastle - also cry about it.
*/
import "@components/NxCard.css";
import { classes } from "@utils/misc";
import { Text } from "@webpack/common";
export function NxCard({ children, className = "", ...props }) {
return (
<div className={`${"nx-card"} ${className}`} {...props}>
{children}
</div>
);
}
export function NxCardTitle({ children, className = "", ...props }) {
return (
<Text variant="heading-md/bold" className={classes("nx-card-title", className)} {...props}>{children}</Text>
);
}

View File

@@ -6,6 +6,15 @@
color: var(--text-secondary); color: var(--text-secondary);
} }
.nx-card-small {
padding: 12px !important;
}
.nx-card-grand {
background-color: var(--background-base-lower) !important;
box-shadow: var(--elevation-low);
}
.nx-card-warning { .nx-card-warning {
background-color: var(--background-feedback-warning) !important; background-color: var(--background-feedback-warning) !important;
} }
@@ -28,7 +37,7 @@
background-color: unset !important; background-color: unset !important;
} }
.nx-card-title { .nx-title {
font-family: var(--font-display); font-family: var(--font-display);
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
@@ -39,3 +48,11 @@
line-height: 2; line-height: 2;
margin-top: -8px; margin-top: -8px;
} }
.nx-text {
color: var(--text-secondary);
}
.nx-text-small {
color: var(--text-muted) !important;
}

View File

@@ -0,0 +1,61 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "@components/NxComponents.css";
import { classes } from "@utils/misc";
import { Text } from "@webpack/common";
import { HTMLProps, PropsWithChildren } from "react";
interface NxCardProps extends PropsWithChildren<Omit<HTMLProps<HTMLDivElement>, "size">> {
variant?: "default" | "grand" | "warning" | "help" | "positive" | "danger" | "special";
size?: "small" | "medium";
}
export function NxCard(props: NxCardProps) {
props.variant ??= "default";
props.size ??= "medium";
return (
<div {...props} className={classes(props.className, "nx-card", props.size !== "medium" && "nx-card-small", props.variant !== "default" && `nx-card-${props.variant}`)}>
{props.children}
</div>
);
}
export function NxTitle(props: PropsWithChildren<HTMLProps<HTMLElement>>) {
return (
<Text variant="heading-md/bold" {...props} className={classes(props.className, "nx-title")}>{props.children}</Text>
);
}
interface NxTextProps extends PropsWithChildren<Omit<HTMLProps<HTMLElement>, "size">> {
size?: "small" | "medium";
}
export function NxText(props: NxTextProps) {
props.size ??= "medium";
return (
<Text {...props} variant={props.size === "medium" ? "text-md/normal" : "text-sm/normal"} className={classes(props.className, "nx-text", props.size === "small" && "nx-text-small")}>{props.children}</Text>
);
}

View File

@@ -9,9 +9,11 @@ export * from "./CodeBlock";
export { default as ErrorBoundary } from "./ErrorBoundary"; export { default as ErrorBoundary } from "./ErrorBoundary";
export * from "./ErrorCard"; export * from "./ErrorCard";
export * from "./Flex"; export * from "./Flex";
export * from "./FormDivider";
export * from "./FormSwitch";
export * from "./Grid"; export * from "./Grid";
export * from "./Heart"; export * from "./Heart";
export * from "./Icons"; export * from "./Icons";
export * from "./Link"; export * from "./Link";
export * from "./settings"; export * from "./settings";
export * from "./Switch";

View File

@@ -1,4 +1,4 @@
.nx-addon-card { .vc-addon-card {
background-color: var(--background-secondary-alt); background-color: var(--background-secondary-alt);
color: var(--interactive-active); color: var(--interactive-active);
border-radius: 8px; border-radius: 8px;
@@ -12,27 +12,27 @@
box-sizing: border-box; box-sizing: border-box;
} }
.visual-refresh .nx-addon-card { .visual-refresh .vc-addon-card {
background-color: var(--card-primary-bg); background-color: var(--card-primary-bg);
border: 1px solid var(--border-subtle); border: 1px solid var(--border-subtle);
} }
.nx-addon-card-disabled { .vc-addon-card-disabled {
opacity: 0.6; opacity: 0.6;
} }
.nx-addon-card:hover { .vc-addon-card:hover {
background-color: var(--background-tertiary); background-color: var(--background-tertiary);
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: var(--elevation-high); box-shadow: var(--elevation-high);
} }
.visual-refresh .nx-addon-card:hover { .visual-refresh .vc-addon-card:hover {
/* same as non-hover, here to overwrite the non-refresh hover background */ /* same as non-hover, here to overwrite the non-refresh hover background */
background-color: var(--card-primary-bg); background-color: var(--card-primary-bg);
} }
.nx-addon-header { .vc-addon-header {
margin-top: auto; margin-top: auto;
display: flex; display: flex;
width: 100%; width: 100%;
@@ -42,7 +42,7 @@
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
.nx-addon-note { .vc-addon-note {
height: 36px; height: 36px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -54,11 +54,11 @@
box-orient: vertical; box-orient: vertical;
} }
.nx-addon-name-author { .vc-addon-name-author {
width: 100%; width: 100%;
} }
.nx-addon-name { .vc-addon-name {
display: flex; display: flex;
width: 100%; width: 100%;
align-items: center; align-items: center;
@@ -66,22 +66,22 @@
gap: 8px; gap: 8px;
} }
.nx-addon-author { .vc-addon-author {
font-size: 0.8em; font-size: 0.8em;
} }
.nx-addon-author::before { .vc-addon-author::before {
content: "by "; content: "by ";
} }
.nx-addon-title-container { .vc-addon-title-container {
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
height: 1.25em; height: 1.25em;
position: relative; position: relative;
} }
.nx-addon-title { .vc-addon-title {
position: absolute; position: absolute;
inset: 0; inset: 0;
overflow: hidden; overflow: hidden;
@@ -102,7 +102,7 @@
} }
} }
.nx-addon-title:hover { .vc-addon-title:hover {
overflow: visible; overflow: visible;
animation: vc-addon-title var(--duration) linear infinite; animation: vc-addon-title var(--duration) linear infinite;
} }

View File

@@ -20,11 +20,11 @@ import "./AddonCard.css";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { AddonBadge } from "@components/settings/PluginBadge"; import { AddonBadge } from "@components/settings/PluginBadge";
import { Switch } from "@components/settings/Switch"; import { Switch } from "@components/Switch";
import { Text, useRef } from "@webpack/common"; import { Text, useRef } from "@webpack/common";
import type { MouseEventHandler, ReactNode } from "react"; import type { MouseEventHandler, ReactNode } from "react";
const cl = classNameFactory("nx-addon-"); const cl = classNameFactory("vc-addon-");
interface Props { interface Props {
name: ReactNode; name: ReactNode;

View File

@@ -18,7 +18,7 @@
export function AddonBadge({ text, color }) { export function AddonBadge({ text, color }) {
return ( return (
<div className="nx-addon-badge" style={{ <div className="vc-addon-badge" style={{
backgroundColor: color, backgroundColor: color,
justifySelf: "flex-end", justifySelf: "flex-end",
marginLeft: "auto" marginLeft: "auto"

View File

@@ -1,4 +1,4 @@
.nx-settings-quickActions-container { .vc-settings-quickActions-container {
display: grid; display: grid;
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-rows: auto auto; grid-template-rows: auto auto;
@@ -6,7 +6,7 @@
margin-bottom: 1em; margin-bottom: 1em;
} }
.nx-settings-quickActions-title { .vc-settings-quickActions-title {
color: var(--text-primary); color: var(--text-primary);
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -14,36 +14,36 @@
padding-bottom: 12px; padding-bottom: 12px;
} }
.nx-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(200px, 1fr));
gap: 0.5em; gap: 0.5em;
} }
.nx-settings-quickActions-containerButtons-3 { .vc-settings-quickActions-containerButtons-3 {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 0.5em; gap: 0.5em;
} }
.nx-settings-quickActions-button { .vc-settings-quickActions-button {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5em; gap: 0.5em;
} }
.nx-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;
transition-duration: 0s; transition-duration: 0s;
} }
.nx-settings-quickActions-img { .vc-settings-quickActions-img {
width: 24px !important; width: 24px !important;
height: 24px !important; height: 24px !important;
} }
.nx-settings-quickActions-info-button { .vc-settings-quickActions-info-button {
height: 24px; height: 24px;
width: 24px; width: 24px;
padding: 0; padding: 0;
@@ -54,17 +54,17 @@
margin-top: -2px !important; margin-top: -2px !important;
} }
.nx-settings-quickActions-info-button:hover { .vc-settings-quickActions-info-button:hover {
color: var(--text-muted); color: var(--text-muted);
} }
.nx-settings-quickActions-info-button:focus-visible { .vc-settings-quickActions-info-button:focus-visible {
border-radius: 100%; border-radius: 100%;
outline: 2px solid var(--focus-primary); outline: 2px solid var(--focus-primary);
outline-offset: -2px; outline-offset: -2px;
} }
.nx-settings-quickActions-help { .vc-settings-quickActions-help {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;

View File

@@ -8,7 +8,7 @@ import "./QuickAction.css";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { InfoIcon } from "@components/Icons"; import { InfoIcon } from "@components/Icons";
import { NxCard, NxCardTitle } from "@components/NxCard"; import { NxCard, NxText, NxTitle } from "@components/NxComponents";
import { openInviteModal } from "@utils/discord"; import { openInviteModal } from "@utils/discord";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { closeAllModals } from "@utils/modal"; import { closeAllModals } from "@utils/modal";
@@ -16,7 +16,7 @@ import { findByPropsLazy } from "@webpack";
import { Alerts, Button, FluxDispatcher, GuildStore, NavigationRouter } from "@webpack/common"; import { Alerts, Button, FluxDispatcher, GuildStore, NavigationRouter } from "@webpack/common";
import type { ComponentType, PropsWithChildren, ReactNode } from "react"; import type { ComponentType, PropsWithChildren, ReactNode } from "react";
const cl = classNameFactory("nx-settings-quickActions-"); const cl = classNameFactory("vc-settings-quickActions-");
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled"); const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
export interface QuickActionProps { export interface QuickActionProps {
@@ -46,7 +46,7 @@ export function QuickAction(props: QuickActionProps) {
export function QuickActionContainer({ title, children, columns = "3" }: PropsWithChildren<QuickActionContainerProps>) { export function QuickActionContainer({ title, children, columns = "3" }: PropsWithChildren<QuickActionContainerProps>) {
return ( return (
<NxCard className={cl("container")}> <NxCard className={cl("container")}>
<NxCardTitle className={cl("title")}> <NxTitle className={cl("title")}>
{title} {title}
<button <button
role="switch" role="switch"
@@ -59,8 +59,8 @@ export function QuickActionContainer({ title, children, columns = "3" }: PropsWi
<img height="64px" width="64px" src="https://cdn.discordapp.com/emojis/1348781960453161011.gif" draggable="false"></img> <img height="64px" width="64px" src="https://cdn.discordapp.com/emojis/1348781960453161011.gif" draggable="false"></img>
<p>No one's around to help.</p> <p>No one's around to help.</p>
</div> </div>
<NxCard className="nx-card-help"> <NxCard variant="help">
If you're looking for actual help, please go ask in our Discord server (not Vencord's)! We'll always be there to help you out. <NxText>If you're looking for actual help, please go ask in our Discord server (not Vencord's)! We'll always be there to help you out.</NxText>
</NxCard> </NxCard>
</> </>
), ),
@@ -84,7 +84,7 @@ export function QuickActionContainer({ title, children, columns = "3" }: PropsWi
> >
<InfoIcon /> <InfoIcon />
</button> </button>
</NxCardTitle> </NxTitle>
<span className={cl("containerButtons-" + columns)}>{children}</span> <span className={cl("containerButtons-" + columns)}>{children}</span>
</NxCard> </NxCard>
); );

View File

@@ -1,18 +1,18 @@
.nx-donate-button { .vc-donate-button {
overflow: visible !important; overflow: visible !important;
} }
.nx-donate-button .vc-heart-icon { .vc-donate-button .vc-heart-icon {
transition: transform 0.3s; transition: transform 0.3s;
} }
.nx-donate-button:hover .vc-heart-icon { .vc-donate-button:hover .vc-heart-icon {
transform: scale(1.1); transform: scale(1.1);
z-index: 10; z-index: 10;
position: relative; position: relative;
} }
.nx-special-card-special { .vc-special-card-special {
padding: 16px; padding: 16px;
margin-bottom: 16px; margin-bottom: 16px;
background-size: cover !important; background-size: cover !important;
@@ -21,12 +21,12 @@
background-origin: border-box !important; background-origin: border-box !important;
} }
.nx-special-card-flex { .vc-special-card-flex {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
.nx-special-card-flex-main { .vc-special-card-flex-main {
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -35,25 +35,25 @@
text-shadow: 2px 2px 2px #70e4; text-shadow: 2px 2px 2px #70e4;
} }
.nx-special-title { .vc-special-title {
color: white; color: white;
font-size: 1.2em; font-size: 1.2em;
font-weight: bold; font-weight: bold;
} }
.nx-special-text { .vc-special-text {
color: white; color: white;
font-size: 1em; font-size: 1em;
white-space: pre-line; white-space: pre-line;
} }
.nx-special-seperator { .vc-special-seperator {
margin-top: 0.75em; margin-top: 0.75em;
border-top: 1px solid white; border-top: 1px solid white;
opacity: 0.4; opacity: 0.4;
} }
.nx-special-hyperlink > .nx-special-hyperlink-text { .vc-special-hyperlink > .vc-special-hyperlink-text {
color: white; color: white;
font-size: 1em; font-size: 1em;
font-weight: bold; font-weight: bold;
@@ -62,7 +62,7 @@
cursor: pointer; cursor: pointer;
} }
.nx-special-hyperlink { .vc-special-hyperlink {
margin-top: 1em; margin-top: 1em;
cursor: pointer; cursor: pointer;
@@ -71,7 +71,7 @@
} }
} }
.nx-special-image-container { .vc-special-image-container {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@@ -80,6 +80,6 @@
height: 70px; height: 70px;
} }
.nx-special-image { .vc-special-image {
width: 80%; width: 80%;
} }

View File

@@ -22,7 +22,7 @@ import { classNameFactory } from "@api/Styles";
import { Card, Forms, React } from "@webpack/common"; import { Card, Forms, React } from "@webpack/common";
import type { PropsWithChildren } from "react"; import type { PropsWithChildren } from "react";
const cl = classNameFactory("nx-special-"); const cl = classNameFactory("vc-special-");
interface StyledCardProps { interface StyledCardProps {
title: string; title: string;

View File

@@ -9,7 +9,7 @@
U+2193, U+2212, U+2215, U+FEFF, U+FFFD; U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
} }
.nx-settings-header { .vc-settings-header {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
gap: 128px; gap: 128px;
@@ -20,13 +20,13 @@
padding: 24px; padding: 24px;
} }
.nx-settings-header-minimal > div > .nx-settings-logo-container { .vc-settings-header-minimal > div > .vc-settings-logo-container {
width: 100%; width: 100%;
height: 20%; height: 20%;
min-height: 36px; min-height: 36px;
} }
@keyframes nx-settings-logo-boioioing { @keyframes vc-settings-logo-boioioing {
0% { 0% {
transform: scale(1) scaleX(1) scaleY(1); transform: scale(1) scaleX(1) scaleY(1);
} }
@@ -48,7 +48,7 @@
} }
} }
.nx-settings-logo { .vc-settings-logo {
width: auto; width: auto;
height: 100%; height: 100%;
object-fit: contain; object-fit: contain;
@@ -56,33 +56,33 @@
.nx-mascot { .nx-mascot {
/* When adjusting make sure image reaches out of card top for ~16px */ /* When adjusting make sure image reaches out of card top for ~16px */
transform: scale(2) translateX(-24px) translateY(-8px); transform: scale(2) translateX(-24px) translateY(-4px);
} }
.nx-settings-header > div { .vc-settings-header > div {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1em; gap: 1em;
} }
.nx-settings-header-minimal > div { .vc-settings-header-minimal > div {
display: flex !important; display: flex !important;
flex-direction: row; flex-direction: row;
min-width: 100% !important; min-width: 100% !important;
max-height: 30px; max-height: 30px;
} }
.nx-settings-header-minimal .nx-settings-logo { .vc-settings-header-minimal .vc-settings-logo {
transform: translateY(-4px); transform: translateY(-4px);
} }
.nx-settings-buttonRow { .vc-settings-buttonRow {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
gap: 0.5em; gap: 0.5em;
} }
.nx-settings-buttonRow-minimal { .vc-settings-buttonRow-minimal {
display: inline-flex; display: inline-flex;
min-width: fit-content !important; min-width: fit-content !important;
max-height: fit-content; max-height: fit-content;

View File

@@ -4,10 +4,10 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
export * from "../Switch";
export * from "./AddonCard"; export * from "./AddonCard";
export * from "./DonateButton"; export * from "./DonateButton";
export * from "./PluginBadge"; export * from "./PluginBadge";
export * from "./QuickAction"; export * from "./QuickAction";
export * from "./SpecialCard"; export * from "./SpecialCard";
export * from "./Switch";
export * from "./tabs"; export * from "./tabs";

View File

@@ -1,10 +1,30 @@
:root { :root {
--nx-green: #0f9; --nx-green: #0f9;
--nx-purple: #70e; --nx-purple: #70e;
--nx-vc-pink: #d3869b;
--nx-green-background: #00ff9914; --nx-green-background: #00ff9914;
--nx-purple-background: #7700ee14; --nx-purple-background: #7700ee14;
--nx-vc-pink-background: #d3869b14;
} }
.nx-removeSwitchDivider div[class*="divider"] { .nx-code {
display: none; border-radius: 4px;
font-size: 85%;
height: auto;
margin: -.2em 0;
padding: 0 .2em;
text-indent: 0;
white-space: pre-wrap;
background: var(--background-base-lowest);
}
.nx-code-new {
border-radius: 4px;
background: var(--background-code);
border: 1px solid var(--border-normal);
font-size: .875rem;
line-height: 1.125rem;
text-indent: 0;
white-space: pre-wrap;
font-family: var(--font-code);
} }

View File

@@ -25,7 +25,7 @@ import type { ComponentType, PropsWithChildren } from "react";
export function SettingsTab({ title, children }: PropsWithChildren<{ title: string; }>) { export function SettingsTab({ title, children }: PropsWithChildren<{ title: string; }>) {
return ( return (
<Forms.FormSection> <section>
<Text <Text
variant="heading-lg/semibold" variant="heading-lg/semibold"
tag="h2" tag="h2"
@@ -35,7 +35,7 @@ export function SettingsTab({ title, children }: PropsWithChildren<{ title: stri
</Text> </Text>
{children} {children}
</Forms.FormSection> </section>
); );
} }

View File

@@ -67,18 +67,18 @@ function NotificationsTab() {
<TabBar <TabBar
type="top" type="top"
look="brand" look="brand"
className={classes("nx-settings-tab-bar", Margins.top16)} className={classes("nx-notifications-tab-bar", Margins.top16)}
selectedItem={currentTab} selectedItem={currentTab}
onItemSelect={setCurrentTab} onItemSelect={setCurrentTab}
> >
<TabBar.Item <TabBar.Item
className="nx-settings-tab-bar-item" className="nx-notifications-tab-bar-item"
id={NotificationTab.LOG} id={NotificationTab.LOG}
> >
Notification Log Notification Log
</TabBar.Item> </TabBar.Item>
<TabBar.Item <TabBar.Item
className="nx-settings-tab-bar-item" className="nx-notifications-tab-bar-item"
id={NotificationTab.SETTINGS} id={NotificationTab.SETTINGS}
> >
Settings Settings

View File

@@ -46,7 +46,7 @@ export function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacemen
} }
if (!replacement.match) throw new Error("No 'replacement.match' field"); if (!replacement.match) throw new Error("No 'replacement.match' field");
if (!replacement.replace) throw new Error("No 'replacement.replace' field"); if (replacement.replace == null) throw new Error("No 'replacement.replace' field");
setFind(find instanceof RegExp ? `/${find.source}/` : find); setFind(find instanceof RegExp ? `/${find.source}/` : find);
setParsedFind(find); setParsedFind(find);

View File

@@ -4,8 +4,9 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { FormSwitch } from "@components/FormSwitch";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { Forms, Parser, Switch, TextInput, useEffect, useState } from "@webpack/common"; import { Forms, Parser, TextInput, useEffect, useState } from "@webpack/common";
const RegexGuide = { const RegexGuide = {
"\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)", "\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)",
@@ -69,15 +70,14 @@ export function ReplacementInput({ replacement, setReplacement, replacementError
</div> </div>
)} )}
<Switch <FormSwitch
className={Margins.top16} className={Margins.top16}
value={isFunc} value={isFunc}
onChange={setIsFunc} onChange={setIsFunc}
note="'replacement' will be evaled if this is toggled" title={"Treat as Function"}
description="'replacement' will be evaled if this is toggled"
hideBorder hideBorder
> />
Treat as Function
</Switch>
</> </>
); );
} }

View File

@@ -1,14 +1,14 @@
.nx-author-modal-root { .vc-author-modal-root {
padding: 1em; padding: 1em;
} }
.nx-author-modal-header { .vc-author-modal-header {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 1em; margin-bottom: 1em;
} }
.nx-author-modal-name { .vc-author-modal-name {
text-transform: none; text-transform: none;
flex-grow: 0; flex-grow: 0;
background: var(--background-base-lowest); background: var(--background-base-lowest);
@@ -20,7 +20,7 @@
text-wrap: nowrap; text-wrap: nowrap;
} }
.nx-author-modal-name::before { .vc-author-modal-name::before {
content: ""; content: "";
display: block; display: block;
position: absolute; position: absolute;
@@ -34,19 +34,19 @@
border-bottom-left-radius: 9999px; border-bottom-left-radius: 9999px;
} }
.nx-author-modal-avatar { .vc-author-modal-avatar {
height: 32px; height: 32px;
width: 32px; width: 32px;
border-radius: 50%; border-radius: 50%;
} }
.nx-author-modal-links { .vc-author-modal-links {
margin-left: auto; margin-left: auto;
gap: 4px; gap: 4px;
display: flex; display: flex;
} }
.nx-author-modal-plugins { .vc-author-modal-plugins {
display: grid; display: grid;
gap: 0.5em; gap: 0.5em;
margin-top: 0.75em; margin-top: 0.75em;

View File

@@ -22,7 +22,7 @@ import Plugins from "~plugins";
import { GithubButton, WebsiteButton } from "./LinkIconButton"; import { GithubButton, WebsiteButton } from "./LinkIconButton";
import { PluginCard } from "./PluginCard"; import { PluginCard } from "./PluginCard";
const cl = classNameFactory("nx-author-modal-"); const cl = classNameFactory("vc-author-modal-");
export function openContributorModal(user: User) { export function openContributorModal(user: User) {
openModal(modalProps => openModal(modalProps =>

View File

@@ -1,4 +1,4 @@
.nx-settings-modal-link-icon { .vc-settings-modal-link-icon {
height: 32px; height: 32px;
width: 32px; width: 32px;
border-radius: 50%; border-radius: 50%;
@@ -6,7 +6,7 @@
box-sizing: border-box box-sizing: border-box
} }
.nx-settings-modal-links { .vc-settings-modal-links {
display: flex; display: flex;
gap: 0.2em; gap: 0.2em;
} }

View File

@@ -12,12 +12,12 @@ import { MaskedLink, Tooltip } from "@webpack/common";
export function GithubLinkIcon() { export function GithubLinkIcon() {
const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF"; const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF";
return <GithubIcon aria-hidden fill={theme} className={"nx-settings-modal-link-icon"} />; return <GithubIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />;
} }
export function WebsiteLinkIcon() { export function WebsiteLinkIcon() {
const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF"; const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF";
return <WebsiteIcon aria-hidden fill={theme} className={"nx-settings-modal-link-icon"} />; return <WebsiteIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />;
} }
interface Props { interface Props {

View File

@@ -1,19 +1,19 @@
.nx-plugin-modal-title { .vc-plugin-modal-title {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
gap: 8px; gap: 8px;
align-items: center; align-items: center;
} }
.nx-plugin-modal-title div { .vc-plugin-modal-title div {
display: inline-block; display: inline-block;
transform: translateY(1px); transform: translateY(1px);
} }
.nx-plugin-modal-info { .vc-plugin-modal-info {
align-items: center; align-items: center;
} }
.nx-plugin-modal-description { .vc-plugin-modal-description {
flex-grow: 1; flex-grow: 1;
} }

View File

@@ -23,7 +23,7 @@ import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { NxCard } from "@components/NxCard"; import { NxCard, NxText } from "@components/NxComponents";
import { debounce } from "@shared/debounce"; import { debounce } from "@shared/debounce";
import { proxyLazy } from "@utils/lazy"; import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
@@ -40,7 +40,7 @@ import { PluginMeta } from "~plugins";
import { OptionComponentMap } from "./components"; import { OptionComponentMap } from "./components";
import { openContributorModal } from "./ContributorModal"; import { openContributorModal } from "./ContributorModal";
const cl = classNameFactory("nx-plugin-modal-"); const cl = classNameFactory("vc-plugin-modal-");
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
const UserRecord: Constructor<Partial<User>> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any; const UserRecord: Constructor<Partial<User>> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any;
@@ -88,7 +88,9 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
function renderSettings() { function renderSettings() {
if (!hasSettings || !plugin.options) if (!hasSettings || !plugin.options)
return <NxCard className={classes("nx-card-help", Margins.top16)}>There are no settings for this plugin.</NxCard>; return <NxCard variant="help" className={Margins.top16}>
<NxText>There are no settings for this plugin.</NxText>
</NxCard>;
const options = Object.entries(plugin.options).map(([key, setting]) => { const options = Object.entries(plugin.options).map(([key, setting]) => {
if (setting.type === OptionType.CUSTOM || setting.hidden) return null; if (setting.type === OptionType.CUSTOM || setting.hidden) return null;
@@ -104,20 +106,21 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
const Component = OptionComponentMap[setting.type]; const Component = OptionComponentMap[setting.type];
return ( return (
<ErrorBoundary noop key={key}>
<Component <Component
id={key} id={key}
key={key}
option={setting} option={setting}
onChange={debounce(onChange)} onChange={debounce(onChange)}
pluginSettings={pluginSettings} pluginSettings={pluginSettings}
definedSettings={plugin.settings} definedSettings={plugin.settings}
/> />
</ErrorBoundary>
); );
}); });
return ( return (
<NxCard className={Margins.top16}> <NxCard variant="grand" className={classes(Margins.top16, Margins.bottom8)}>
<div className="nx-plugins-settings"> <div className="vc-plugins-settings">
{options} {options}
</div> </div>
</NxCard> </NxCard>
@@ -177,25 +180,25 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
</ModalHeader> </ModalHeader>
<ModalContent className={Margins.bottom16}> <ModalContent className={Margins.bottom16}>
<Forms.FormSection> <section>
<Flex className={cl("info")}> <Flex className={cl("info")}>
<Forms.FormText className={cl("description")}>{plugin.description}</Forms.FormText> <Forms.FormText className={cl("description")}>{plugin.description}</Forms.FormText>
</Flex> </Flex>
</Forms.FormSection> </section>
{!!plugin.settingsAboutComponent && ( {!!plugin.settingsAboutComponent && (
<div className={Margins.top16}> <div className={Margins.top16}>
<Forms.FormSection> <section>
<ErrorBoundary message="An error occurred while rendering this plugin's custom Info Component"> <ErrorBoundary message="An error occurred while rendering this plugin's custom Info Component">
<plugin.settingsAboutComponent /> <plugin.settingsAboutComponent />
</ErrorBoundary> </ErrorBoundary>
</Forms.FormSection> </section>
</div> </div>
)} )}
<Forms.FormSection> <section>
{renderSettings()} {renderSettings()}
</Forms.FormSection> </section>
</ModalContent> </ModalContent>
</ModalRoot> </ModalRoot>
); );

View File

@@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Switch } from "@components/settings/Switch"; import { Switch } from "@components/Switch";
import { PluginOptionBoolean } from "@utils/types"; import { PluginOptionBoolean } from "@utils/types";
import { React, useState } from "@webpack/common"; import { React, useState } from "@webpack/common";

View File

@@ -11,7 +11,7 @@ import { DefinedSettings, PluginOptionBase } from "@utils/types";
import { Text } from "@webpack/common"; import { Text } from "@webpack/common";
import { PropsWithChildren } from "react"; import { PropsWithChildren } from "react";
export const cl = classNameFactory("nx-plugins-setting-"); export const cl = classNameFactory("vc-plugins-setting-");
interface SettingBaseProps<T> { interface SettingBaseProps<T> {
option: T; option: T;

View File

@@ -1,35 +1,35 @@
.nx-plugins-setting-section { .vc-plugins-setting-section {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5em; gap: 0.5em;
} }
.nx-plugins-setting-content { .vc-plugins-setting-content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5em; gap: 0.5em;
} }
.nx-plugins-setting-inline { .vc-plugins-setting-inline {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
} }
.nx-plugins-setting-label { .vc-plugins-setting-label {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.25em; gap: 0.25em;
} }
.nx-plugins-setting-title { .vc-plugins-setting-title {
color: var(--header-primary); color: var(--header-primary);
} }
.nx-plugins-setting-description { .vc-plugins-setting-description {
color: var(--header-secondary); color: var(--header-secondary);
} }
.nx-plugins-setting-error { .vc-plugins-setting-error {
color: var(--text-danger); color: var(--text-danger);
} }

View File

@@ -21,8 +21,9 @@ import "./styles.css";
import * as DataStore from "@api/DataStore"; import * as DataStore from "@api/DataStore";
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { NxCard, NxCardTitle } from "@components/NxCard"; import { NxCard, NxText, NxTitle } from "@components/NxComponents";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { ChangeList } from "@utils/ChangeList"; import { ChangeList } from "@utils/ChangeList";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
@@ -37,7 +38,7 @@ import Plugins, { ExcludedPlugins } from "~plugins";
import { PluginCard } from "./PluginCard"; import { PluginCard } from "./PluginCard";
export const cl = classNameFactory("nx-plugins-"); export const cl = classNameFactory("vc-plugins-");
export const logger = new Logger("PluginSettings", "#a6d189"); export const logger = new Logger("PluginSettings", "#a6d189");
const InputStyles = findByPropsLazy("inputWrapper", "inputError", "error"); const InputStyles = findByPropsLazy("inputWrapper", "inputError", "error");
@@ -46,16 +47,18 @@ function ReloadRequiredCard({ required }: { required: boolean; }) {
return ( return (
<> <>
<NxCard className={cl("info-card")}> <NxCard className={cl("info-card")}>
<NxCardTitle>Plugin Management</NxCardTitle> <NxTitle>Plugin Management</NxTitle>
<span>Press the cog wheel or info icon to get more info on a plugin</span> <NxText>
<span> &mdash; Plugins with a cog wheel have settings you can modify!</span> Press the cog wheel or info icon to get more info on a plugin &mdash;
Plugins with a cog wheel have settings you can modify!
</NxText>
</NxCard> </NxCard>
{required ? ( {required ? (
<NxCard className={classes("nx-card-warning", Margins.bottom16)}> <NxCard variant="warning" className={Margins.bottom16}>
<Flex flexDirection="row" style={{ justifyContent: "space-between" }}> <Flex flexDirection="row" style={{ justifyContent: "space-between" }}>
<div> <div>
<NxCardTitle>Restart required!</NxCardTitle> <NxTitle>Restart required!</NxTitle>
<span>Restart now to apply new plugins and their settings</span> <NxText>Restart now to apply new plugins and their settings</NxText>
</div> </div>
<Button onClick={() => location.reload()} className={cl("restart-button")}> <Button onClick={() => location.reload()} className={cl("restart-button")}>
Restart Restart
@@ -258,8 +261,11 @@ function PluginSettings() {
</Forms.FormTitle> */} </Forms.FormTitle> */}
<div className={classes(Margins.bottom20, cl("filter-controls"))}> <div className={classes(Margins.bottom20, cl("filter-controls"))}>
<ErrorBoundary noop>
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} /> <TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} />
</ErrorBoundary>
<div className={InputStyles.inputWrapper}> <div className={InputStyles.inputWrapper}>
<ErrorBoundary noop>
<Select <Select
options={[ options={[
{ label: "Show All", value: SearchStatus.ALL, default: true }, { label: "Show All", value: SearchStatus.ALL, default: true },
@@ -274,6 +280,7 @@ function PluginSettings() {
isSelected={v => v === searchValue.status} isSelected={v => v === searchValue.status}
closeOnSelect={true} closeOnSelect={true}
/> />
</ErrorBoundary>
</div> </div>
</div> </div>

View File

@@ -16,14 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.nx-plugins-grid { .vc-plugins-grid {
margin-top: 16px; margin-top: 16px;
display: grid; display: grid;
grid-gap: 16px; grid-gap: 16px;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
} }
.nx-plugins-info-button { .vc-plugins-info-button {
height: 24px; height: 24px;
width: 24px; width: 24px;
padding: 0; padding: 0;
@@ -31,18 +31,18 @@
margin-right: 8px; margin-right: 8px;
} }
.nx-plugins-settings-button:hover { .vc-plugins-settings-button:hover {
color: var(--interactive-hover); color: var(--interactive-hover);
} }
.nx-plugins-filter-controls { .vc-plugins-filter-controls {
display: grid; display: grid;
height: 40px; height: 40px;
gap: 10px; gap: 10px;
grid-template-columns: 1fr 200px; grid-template-columns: 1fr 200px;
} }
.nx-addon-badge { .vc-addon-badge {
padding: 0 6px; padding: 0 6px;
font-family: var(--font-display); font-family: var(--font-display);
font-weight: 500; font-weight: 500;
@@ -54,23 +54,23 @@
text-align: center; text-align: center;
} }
.nx-plugins-dep-name { .vc-plugins-dep-name {
margin: 0 auto; margin: 0 auto;
} }
.nx-plugins-info-card { .vc-plugins-info-card {
margin-bottom: 1em; margin-bottom: 1em;
} }
.nx-plugins-restart-button { .vc-plugins-restart-button {
background-color: var(--yellow-300) !important; background-color: var(--yellow-300) !important;
} }
.nx-plugins-info-icon:not(:hover, :focus) { .vc-plugins-info-icon:not(:hover, :focus) {
color: var(--text-muted); color: var(--text-muted);
} }
.nx-plugins-noResults { .vc-plugins-noResults {
margin-top: 16px; margin-top: 16px;
display: block; display: block;
width: 100%; width: 100%;
@@ -78,7 +78,7 @@
color: var(--text-muted); color: var(--text-muted);
} }
.nx-plugins-settings { .vc-plugins-settings {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1.25em; gap: 1.25em;

View File

@@ -1,28 +1,28 @@
.nx-settings-tab-bar { .vc-settings-tab-bar {
margin-top: 20px; margin-top: 20px;
margin-bottom: 10px; margin-bottom: 10px;
border-bottom: 1px solid var(--border-subtle); border-bottom: 1px solid var(--border-subtle);
} }
.nx-settings-tab-bar-item { .vc-settings-tab-bar-item {
margin-right: 32px; margin-right: 32px;
padding-bottom: 16px; padding-bottom: 16px;
margin-bottom: -2px; margin-bottom: -2px;
} }
.nx-settings-card { .vc-settings-card {
padding: 1em; padding: 1em;
margin-bottom: 1em; margin-bottom: 1em;
} }
.nx-backup-restore-card { .vc-backup-restore-card {
background-color: var(--info-warning-background); background-color: var(--info-warning-background);
border-color: var(--info-warning-foreground); border-color: var(--info-warning-foreground);
color: var(--info-warning-text); color: var(--info-warning-text);
margin-bottom: 16px; margin-bottom: 16px;
} }
.nx-settings-theme-links { .vc-settings-theme-links {
/* Needed to fix bad themes that hide certain textarea elements for whatever eldritch reason */ /* Needed to fix bad themes that hide certain textarea elements for whatever eldritch reason */
display: inline-block !important; display: inline-block !important;
color: var(--text-default) !important; color: var(--text-default) !important;
@@ -38,27 +38,27 @@
white-space: nowrap; white-space: nowrap;
} }
.nx-settings-theme-links::placeholder { .vc-settings-theme-links::placeholder {
color: var(--text-muted) !important; color: var(--text-muted) !important;
} }
.nx-settings-theme-links:focus { .vc-settings-theme-links:focus {
background-color: var(--background-tertiary); background-color: var(--background-tertiary);
} }
.nx-cloud-settings-sync-grid { .vc-cloud-settings-sync-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
grid-gap: 1em; grid-gap: 1em;
} }
.nx-cloud-erase-data-danger-btn { .vc-cloud-erase-data-danger-btn {
color: var(--white-500); color: var(--white-500);
background-color: var(--button-danger-background); background-color: var(--button-danger-background);
} }
.nx-text-selectable, .vc-text-selectable,
.nx-text-selectable :where([class*="text" i], [class*="title" i]) { .vc-text-selectable :where([class*="text" i], [class*="title" i]) {
/* make text selectable, silly discord makes the entirety of settings not selectable */ /* make text selectable, silly discord makes the entirety of settings not selectable */
user-select: text; user-select: text;

View File

@@ -19,7 +19,7 @@
import "@components/settings/tabs/styles.css"; import "@components/settings/tabs/styles.css";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { NxCard } from "@components/NxCard"; import { NxCard, NxText } from "@components/NxComponents";
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 { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync"; import { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync";
@@ -28,8 +28,8 @@ import { Button, Text } from "@webpack/common";
export function BackupAndRestoreTab() { export function BackupAndRestoreTab() {
return ( return (
<SettingsTab title="Backup & Restore"> <SettingsTab title="Backup & Restore">
<NxCard className={`nx-card-warning ${Margins.bottom16}`}> <NxCard variant="warning" className={Margins.bottom16}>
<span>Importing a settings file will overwrite your current settings.</span> <NxText>Importing a settings file will overwrite your current settings.</NxText>
</NxCard> </NxCard>
<Text variant="text-md/normal" className={Margins.bottom8}> <Text variant="text-md/normal" className={Margins.bottom8}>
You can import and export your Nexulien settings as a JSON file. You can import and export your Nexulien settings as a JSON file.

View File

@@ -19,14 +19,15 @@
import { showNotification } from "@api/Notifications"; import { showNotification } from "@api/Notifications";
import { Settings, useSettings } from "@api/Settings"; import { Settings, useSettings } from "@api/Settings";
import { CheckedTextInput } from "@components/CheckedTextInput"; import { CheckedTextInput } from "@components/CheckedTextInput";
import { FormSwitch } from "@components/FormSwitch";
import { Grid } from "@components/Grid"; import { Grid } from "@components/Grid";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { NxCard, NxCardTitle } from "@components/NxCard"; import { NxCard, NxText, NxTitle } from "@components/NxComponents";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { authorizeCloud, checkCloudUrlCsp, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud"; import { authorizeCloud, checkCloudUrlCsp, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync"; import { deleteCloudSettings, getCloudSettings, putCloudSettings } from "@utils/settingsSync";
import { Alerts, Button, Forms, Switch, Tooltip } from "@webpack/common"; import { Alerts, Button, Forms, Tooltip } from "@webpack/common";
function validateUrl(url: string) { function validateUrl(url: string) {
try { try {
@@ -70,22 +71,23 @@ function SettingsSyncSection() {
const sectionEnabled = cloud.authenticated && cloud.settingsSync; const sectionEnabled = cloud.authenticated && cloud.settingsSync;
return ( return (
<NxCard className={Margins.top16}> <section className={Margins.top16}>
<NxCardTitle>Settings Sync</NxCardTitle> <NxCard>
<NxTitle>Settings Sync</NxTitle>
<Forms.FormText variant="text-md/normal" className={Margins.bottom20}> <Forms.FormText variant="text-md/normal" className={Margins.bottom20}>
Synchronize your settings to the cloud. This allows easy synchronization across multiple devices with Synchronize your settings to the cloud. This allows easy synchronization across multiple devices with
minimal effort. minimal effort.
</Forms.FormText> </Forms.FormText>
<Switch <FormSwitch
key="cloud-sync" key="cloud-sync"
disabled={!cloud.authenticated} title="Settings Sync"
value={cloud.settingsSync} value={cloud.settingsSync}
onChange={v => { cloud.settingsSync = v; }} onChange={v => { cloud.settingsSync = v; }}
className="nx-removeSwitchDivider" className="nx-removeSwitchDivider"
> disabled={!cloud.authenticated}
Settings Sync />
</Switch> <div className="vc-cloud-settings-sync-grid">
<div className="nx-cloud-settings-sync-grid">
<Button <Button
size={Button.Sizes.SMALL} size={Button.Sizes.SMALL}
disabled={!sectionEnabled} disabled={!sectionEnabled}
@@ -117,6 +119,7 @@ function SettingsSyncSection() {
</Button> </Button>
</div> </div>
</NxCard> </NxCard>
</section>
); );
} }
@@ -125,15 +128,20 @@ function CloudTab() {
return ( return (
<SettingsTab title="Vencord Cloud"> <SettingsTab title="Vencord Cloud">
<NxCard className={Margins.bottom16}> <section className={Margins.top16}>
<NxCard className={Margins.bottom20}>
<NxText>
Vencord 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. It may or may not work with Nexulien; use with caution.
</NxText>
</NxCard> </NxCard>
<Switch <FormSwitch
key="backend" key="backend"
title="Enable Cloud Integrations"
description="This will request authorization if you have not yet set up cloud integrations."
value={settings.cloud.authenticated} value={settings.cloud.authenticated}
onChange={v => { onChange={v => {
if (v) if (v)
@@ -141,15 +149,12 @@ function CloudTab() {
else else
settings.cloud.authenticated = v; settings.cloud.authenticated = v;
}} }}
note="This will request authorization if you have not yet set up cloud integrations." hideBorder={!settings.cloud.authenticated}
className={!settings.cloud.authenticated ? "nx-removeSwitchDivider" : ""} />
>
Enable Cloud Integrations
</Switch>
{settings.cloud.authenticated ? <> {settings.cloud.authenticated ? <>
<NxCard> <NxCard>
<NxCardTitle>Backend URL</NxCardTitle> <NxTitle>Backend URL</NxTitle>
<Forms.FormText className={Margins.bottom8}> <Forms.FormText className={Margins.bottom8}>
Which backend to use when using cloud integrations. Which backend to use when using cloud integrations.
</Forms.FormText> </Forms.FormText>
@@ -185,16 +190,17 @@ function CloudTab() {
body: "Once your data is erased, we cannot recover it. There's no going back!", body: "Once your data is erased, we cannot recover it. There's no going back!",
onConfirm: eraseAllData, onConfirm: eraseAllData,
confirmText: "Erase it!", confirmText: "Erase it!",
confirmColor: "nx-cloud-erase-data-danger-btn", confirmColor: "vc-cloud-erase-data-danger-btn",
cancelText: "Nevermind" cancelText: "Nevermind"
})} })}
> >
Erase All Data Erase All Data
</Button> </Button>
</Grid> </Grid>
</NxCard> </NxCard >
<SettingsSyncSection /> <SettingsSyncSection />
</> : <></>} </> : <></>}
</section>
</SettingsTab> </SettingsTab>
); );
} }

View File

@@ -8,12 +8,11 @@ import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons"; import { FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { NxCard, NxCardTitle } from "@components/NxCard"; import { NxCard, NxText, NxTitle } from "@components/NxComponents";
import { QuickAction, QuickActionContainer } from "@components/settings/QuickAction"; import { QuickAction, QuickActionContainer } from "@components/settings/QuickAction";
import { openPluginModal } from "@components/settings/tabs/plugins/PluginModal"; import { openPluginModal } from "@components/settings/tabs/plugins/PluginModal";
import { UserThemeHeader } from "@main/themes"; import { UserThemeHeader } from "@main/themes";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { findLazy } from "@webpack"; import { findLazy } from "@webpack";
import { Forms, useEffect, useRef, useState } from "@webpack/common"; import { Forms, useEffect, useRef, useState } from "@webpack/common";
import ClientThemePlugin from "plugins/clientTheme"; import ClientThemePlugin from "plugins/clientTheme";
@@ -21,7 +20,7 @@ import type { ComponentType, Ref, SyntheticEvent } from "react";
import { ThemeCard } from "./ThemeCard"; import { ThemeCard } from "./ThemeCard";
const cl = classNameFactory("nx-settings-theme-"); const cl = classNameFactory("vc-settings-theme-");
type FileInput = ComponentType<{ type FileInput = ComponentType<{
ref: Ref<HTMLInputElement>; ref: Ref<HTMLInputElement>;
@@ -86,7 +85,7 @@ export function LocalThemesTab() {
return ( return (
<> <>
<NxCard className={cl("info-card")}> <NxCard className={cl("info-card")}>
<NxCardTitle>Find Themes:</NxCardTitle> <NxTitle>Find Themes:</NxTitle>
<div style={{ marginBottom: ".5em", display: "flex", flexDirection: "column" }}> <div style={{ marginBottom: ".5em", display: "flex", flexDirection: "column" }}>
<ul> <ul>
<li> <li>
@@ -101,16 +100,20 @@ export function LocalThemesTab() {
</li> </li>
</ul> </ul>
</div> </div>
<span>If using the BD site, click on "Download" and place the downloaded .theme.css file into your themes folder.</span> <NxText>If using the BD site, click on "Download" and place the downloaded <code className="nx-code-new">.theme.css</code> file into your themes folder.</NxText>
</NxCard> </NxCard>
<NxCard className={classes("nx-card-help", Margins.bottom16)}> <NxCard variant="help" className={Margins.bottom16}>
<NxCardTitle>External Resources</NxCardTitle> <NxTitle>External Resources</NxTitle>
<span>For security reasons, loading resources (styles, fonts, images, ...) from most sites is blocked.</span> <NxText>
<span> Make sure all your assets are hosted on GitHub, GitLab, Codeberg, Imgur, Discord or Google Fonts.</span> For security reasons, loading resources (styles, fonts, images, ...) from most sites is blocked.
Make sure all your assets are hosted on GitHub, GitLab, Codeberg, Imgur, Discord or Google Fonts.
</NxText>
</NxCard> </NxCard>
<Forms.FormSection title="Local Themes">
<section>
<Forms.FormTitle tag="h5">Local Themes</Forms.FormTitle>
<QuickActionContainer title="Manage Local Themes"> <QuickActionContainer title="Manage Local Themes">
<> <>
{IS_WEB ? {IS_WEB ?
@@ -175,7 +178,7 @@ export function LocalThemesTab() {
/> />
))} ))}
</div> </div>
</Forms.FormSection> </section>
</> </>
); );
} }

View File

@@ -5,9 +5,8 @@
*/ */
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { NxCard, NxCardTitle } from "@components/NxCard"; import { NxCard, NxText, NxTitle } from "@components/NxComponents";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { Forms, TextArea, useState } from "@webpack/common"; import { Forms, TextArea, useState } from "@webpack/common";
export function OnlineThemesTab() { export function OnlineThemesTab() {
@@ -28,30 +27,33 @@ export function OnlineThemesTab() {
return ( return (
<> <>
<NxCard className={`${classes("nx-card-warning", Margins.bottom16)}`}> <NxCard variant="warning" className={Margins.bottom16}>
<span> <NxText>
This section is for advanced users. If you are having difficulties using it, use the This section is for advanced users. If you are having difficulties using it, use the
Local Themes tab instead. Local Themes tab instead.
</span> </NxText>
</NxCard> </NxCard>
<NxCard className="nx-settings-card"> <NxCard className="vc-settings-card">
<NxCardTitle tag="h5">Paste links to css files here</NxCardTitle> <NxTitle>Paste links to css files here</NxTitle>
<span>One link per line</span><br></br> <NxText>
<span>You can prefix lines with @light or @dark to toggle them based on your Discord theme</span><br></br> <span>&mdash;&nbsp;One link per line</span><br></br>
<span>Make sure to use direct links to files (raw or github.io)!</span> <span>&mdash;&nbsp;You can prefix lines with <code className="nx-code-new">@light</code> or <code className="nx-code-new">@dark</code> to toggle them based on your Discord theme</span><br></br>
<span>&mdash;&nbsp;Make sure to use direct links to files (raw or github.io)!</span>
</NxText>
</NxCard> </NxCard>
<Forms.FormSection title="Online Themes" tag="h5"> <section>
<Forms.FormTitle tag="h5">Online Themes</Forms.FormTitle>
<TextArea <TextArea
value={themeText} value={themeText}
onChange={setThemeText} onChange={setThemeText}
className={"nx-settings-theme-links"} className={"vc-settings-theme-links"}
placeholder="Enter Theme Links..." placeholder="Enter Theme Links..."
spellCheck={false} spellCheck={false}
onBlur={onBlur} onBlur={onBlur}
rows={10} rows={10}
/> />
</Forms.FormSection> </section>
</> </>
); );
} }

View File

@@ -19,7 +19,7 @@
import "./styles.css"; import "./styles.css";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { NxCard, NxCardTitle } from "@components/NxCard"; import { NxCard, NxTitle } from "@components/NxComponents";
import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { getStylusWebStoreUrl } from "@utils/web"; import { getStylusWebStoreUrl } from "@utils/web";
import { React, TabBar, useState } from "@webpack/common"; import { React, TabBar, useState } from "@webpack/common";
@@ -41,18 +41,18 @@ function ThemesTab() {
<TabBar <TabBar
type="top" type="top"
look="brand" look="brand"
className="nx-settings-tab-bar" className="vc-settings-tab-bar"
selectedItem={currentTab} selectedItem={currentTab}
onItemSelect={setCurrentTab} onItemSelect={setCurrentTab}
> >
<TabBar.Item <TabBar.Item
className="nx-settings-tab-bar-item" className="vc-settings-tab-bar-item"
id={ThemeTab.LOCAL} id={ThemeTab.LOCAL}
> >
Local Themes Local Themes
</TabBar.Item> </TabBar.Item>
<TabBar.Item <TabBar.Item
className="nx-settings-tab-bar-item" className="vc-settings-tab-bar-item"
id={ThemeTab.ONLINE} id={ThemeTab.ONLINE}
> >
Online Themes Online Themes
@@ -71,7 +71,7 @@ function UserscriptThemesTab() {
return ( return (
<SettingsTab title="Themes"> <SettingsTab title="Themes">
<NxCard> <NxCard>
<NxCardTitle>Themes are not supported on the Userscript!</NxCardTitle> <NxTitle>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

@@ -1,10 +1,10 @@
.nx-settings-theme-grid { .vc-settings-theme-grid {
display: grid; display: grid;
grid-gap: 16px; grid-gap: 16px;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
} }
.nx-settings-theme-card { .vc-settings-theme-card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: var(--background-base-lower-alt); background-color: var(--background-base-lower-alt);
@@ -16,12 +16,12 @@
transition-property: box-shadow, transform, background, opacity; transition-property: box-shadow, transform, background, opacity;
} }
.nx-settings-theme-info-card { .vc-settings-theme-info-card {
margin-top: 16px; margin-top: 16px;
margin-bottom: 16px; margin-bottom: 16px;
} }
.nx-settings-theme-card-text { .vc-settings-theme-card-text {
text-overflow: ellipsis; text-overflow: ellipsis;
height: 1.2em; height: 1.2em;
margin-bottom: 2px; margin-bottom: 2px;
@@ -29,21 +29,21 @@
overflow: hidden; overflow: hidden;
} }
.nx-settings-theme-author::before { .vc-settings-theme-author::before {
content: "by "; content: "by ";
} }
.nx-settings-theme-validator-card { .vc-settings-theme-validator-card {
margin-top: 16px; margin-top: 16px;
} }
.nx-settings-csp-list { .vc-settings-csp-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
} }
.nx-settings-csp-row { .vc-settings-csp-row {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;

View File

@@ -7,7 +7,7 @@
import { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { NxCard, NxCardTitle } from "@components/NxCard"; import { NxCard, NxTitle } from "@components/NxComponents";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { relaunch } from "@utils/native"; import { relaunch } from "@utils/native";
@@ -34,7 +34,7 @@ export function HashLink({ repo, hash, disabled = false }: { repo: string, hash:
export function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) { export function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) {
return ( return (
<NxCard style={{ padding: "0 0.5em" }} className="nx-updater-changes"> <NxCard style={{ padding: "0 0.5em" }} className="vc-updater-changes">
{updates.map(({ hash, author, message }) => ( {updates.map(({ hash, author, message }) => (
<div <div
key={hash} key={hash}
@@ -81,7 +81,7 @@ export function Updatable(props: CommonProps) {
return ( return (
<> <>
<NxCard className={classes(isOutdated ? "nx-card-warning" : (!updates && updateError ? "nx-card-danger" : "nx-card-positive"), Margins.bottom16)}> <NxCard variant={isOutdated ? "warning" : (!updates && updateError ? "danger" : "positive")} className={Margins.bottom16}>
{!updates && updateError ? ( {!updates && updateError ? (
<> <>
<Forms.FormText>Failed to check for updates. Check the console for more info</Forms.FormText> <Forms.FormText>Failed to check for updates. Check the console for more info</Forms.FormText>
@@ -154,9 +154,9 @@ export function Repository({ repo, repoPending, err }: CommonProps) {
return ( return (
<> <>
<NxCard> <NxCard>
<NxCardTitle>Repository</NxCardTitle> <NxTitle>Repository</NxTitle>
<Forms.FormText className="nx-text-selectable"> <Forms.FormText className="vc-text-selectable">
{repoPending {repoPending
? repo ? repo
: err : err
@@ -167,7 +167,7 @@ export function Repository({ repo, repoPending, err }: CommonProps) {
</Link> </Link>
) )
} }
{" "}(<code className="nx-updater-repo-hash"><HashLink hash={gitHash} repo={repo} disabled={repoPending} /></code>) {" "}(<code className="nx-code"><HashLink hash={gitHash} repo={repo} disabled={repoPending} /></code>)
</Forms.FormText> </Forms.FormText>
</NxCard> </NxCard>
</> </>

View File

@@ -19,11 +19,12 @@
import "./styles.css"; import "./styles.css";
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { FormSwitch } from "@components/FormSwitch";
import { handleSettingsTabError, SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab"; import { handleSettingsTabError, SettingsTab, wrapTab } from "@components/settings/tabs/BaseTab";
import { ModalCloseButton, ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { ModalCloseButton, ModalContent, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
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, Switch } from "@webpack/common"; import { Forms, React } from "@webpack/common";
import { CommonProps, Newer, Repository, Updatable } from "./Components"; import { CommonProps, Newer, Repository, Updatable } from "./Components";
@@ -45,21 +46,19 @@ function Updater() {
<SettingsTab title="Nexulien Updater"> <SettingsTab title="Nexulien Updater">
<Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle> <Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle>
<Switch <FormSwitch
title="Automatically update"
description="Automatically update Vencord without confirmation prompt"
value={settings.autoUpdate} value={settings.autoUpdate}
onChange={(v: boolean) => settings.autoUpdate = v} onChange={(v: boolean) => settings.autoUpdate = v}
note="Automatically update Vencord without confirmation prompt" />
> <FormSwitch
Automatically update title="Get notified when an automatic update completes"
</Switch> description="Show a notification when Vencord automatically updates"
<Switch
value={settings.autoUpdateNotification} value={settings.autoUpdateNotification}
onChange={(v: boolean) => settings.autoUpdateNotification = v} onChange={(v: boolean) => settings.autoUpdateNotification = v}
note="Show a notification when Vencord automatically updates"
disabled={!settings.autoUpdate} disabled={!settings.autoUpdate}
> />
Get notified when an automatic update completes
</Switch>
<Forms.FormTitle tag="h5">Updates</Forms.FormTitle> <Forms.FormTitle tag="h5">Updates</Forms.FormTitle>
@@ -85,8 +84,8 @@ export const openUpdaterModal = IS_UPDATER_DISABLED
try { try {
openModal(wrapTab((modalProps: ModalProps) => ( openModal(wrapTab((modalProps: ModalProps) => (
<ModalRoot {...modalProps} size={ModalSize.MEDIUM}> <ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
<ModalContent className="nx-updater-modal"> <ModalContent className="vc-updater-modal">
<ModalCloseButton onClick={modalProps.onClose} className="nx-updater-modal-close-button" /> <ModalCloseButton onClick={modalProps.onClose} className="vc-updater-modal-close-button" />
<UpdaterTab /> <UpdaterTab />
</ModalContent> </ModalContent>
</ModalRoot> </ModalRoot>

View File

@@ -1,4 +1,4 @@
.nx-updater-changes { .vc-updater-changes {
background: var(--background-code); background: var(--background-code);
border: 1px solid var(--border-normal); border: 1px solid var(--border-normal);
margin-top: 16px !important; margin-top: 16px !important;
@@ -6,21 +6,10 @@
border-radius: 4px; border-radius: 4px;
} }
.nx-updater-modal { .vc-updater-modal {
padding: 1.5em !important; padding: 1.5em !important;
} }
.nx-updater-modal-close-button { .vc-updater-modal-close-button {
float: right; float: right;
} }
.nx-updater-repo-hash {
border-radius: 4px;
font-size: 85%;
height: auto;
margin: -.2em 0;
padding: 0 .2em;
text-indent: 0;
white-space: pre-wrap;
background: var(--background-base-lowest);
}

View File

@@ -5,6 +5,7 @@
*/ */
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { identity } from "@utils/misc"; import { identity } from "@utils/misc";
import { Forms, Select } from "@webpack/common"; import { Forms, Select } from "@webpack/common";
@@ -15,6 +16,7 @@ export function VibrancySettings() {
return ( return (
<> <>
<Forms.FormTitle tag="h5">Window vibrancy style (requires restart)</Forms.FormTitle> <Forms.FormTitle tag="h5">Window vibrancy style (requires restart)</Forms.FormTitle>
<ErrorBoundary noop>
<Select <Select
className={Margins.bottom20} className={Margins.bottom20}
placeholder="Window vibrancy style" placeholder="Window vibrancy style"
@@ -74,7 +76,9 @@ export function VibrancySettings() {
]} ]}
select={v => settings.macosVibrancyStyle = v} select={v => settings.macosVibrancyStyle = v}
isSelected={v => settings.macosVibrancyStyle === v} isSelected={v => settings.macosVibrancyStyle === v}
serialize={identity} /> serialize={identity}
/>
</ErrorBoundary>
</> </>
); );
} }

View File

@@ -15,7 +15,8 @@ import { Button, Forms, Select, Slider, Text } from "@webpack/common";
export function NotificationSection() { export function NotificationSection() {
return ( return (
<Forms.FormSection className={Margins.top16} title="Vencord Notifications" tag="h5"> <section className={Margins.top16}>
<Forms.FormTitle tag="h5">Vencord Notifications</Forms.FormTitle>
<Flex> <Flex>
<Button onClick={openNotificationSettingsModal}> <Button onClick={openNotificationSettingsModal}>
Notification Settings Notification Settings
@@ -24,7 +25,7 @@ export function NotificationSection() {
View Notification Log View Notification Log
</Button> </Button>
</Flex> </Flex>
</Forms.FormSection> </section>
); );
} }

View File

@@ -21,8 +21,9 @@ import "@components/settings/styles.css";
import { Settings, useSettings } from "@api/Settings"; import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { FormSwitch } from "@components/FormSwitch";
import { FolderIcon, GithubIcon, PaintbrushIcon, RestartIcon } from "@components/index"; import { FolderIcon, GithubIcon, PaintbrushIcon, RestartIcon } from "@components/index";
import { NxCard } from "@components/NxCard"; import { NxCard, NxText } from "@components/NxComponents";
import { NxMascot } from "@components/settings/Mascot"; import { NxMascot } from "@components/settings/Mascot";
import { QuickAction, QuickActionContainer } from "@components/settings/QuickAction"; import { QuickAction, QuickActionContainer } from "@components/settings/QuickAction";
import { SpecialCard } from "@components/settings/SpecialCard"; import { SpecialCard } from "@components/settings/SpecialCard";
@@ -33,14 +34,14 @@ import { gitRemote } from "@shared/vencordUserAgent";
import { IS_MAC, IS_WINDOWS } from "@utils/constants"; import { IS_MAC, IS_WINDOWS } from "@utils/constants";
import { openInviteModal } from "@utils/discord"; import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { isPluginDev } from "@utils/misc"; import { classes, isPluginDev } from "@utils/misc";
import { closeAllModals } from "@utils/modal"; import { closeAllModals } from "@utils/modal";
import { relaunch } from "@utils/native"; import { relaunch } from "@utils/native";
import { Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, React, Switch, UserStore } from "@webpack/common"; import { Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, React, UserStore } from "@webpack/common";
import { VibrancySettings } from "./MacVibrancySettings"; import { VibrancySettings } from "./MacVibrancySettings";
const cl = classNameFactory("nx-settings-"); const cl = classNameFactory("vc-settings-");
const CONTRIB_IMAGE = "https://cdn.discordapp.com/emojis/1337858798664024156.png"; const CONTRIB_IMAGE = "https://cdn.discordapp.com/emojis/1337858798664024156.png";
const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1337878381517078649.png?size=2048"; const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1337878381517078649.png?size=2048";
@@ -94,14 +95,13 @@ function Switches() {
}>; }>;
return Switches.map(s => s && ( return Switches.map(s => s && (
<Switch <FormSwitch
key={s.key} key={s.key}
title={s.title}
description={s.note}
value={settings[s.key]} value={settings[s.key]}
onChange={v => settings[s.key] = v} onChange={v => settings[s.key] = v}
note={s.note} />
>
{s.title}
</Switch>
)); ));
} }
@@ -116,15 +116,19 @@ function VencordSettings() {
<> <>
<SettingsTab title="Nexulien Settings"> <SettingsTab title="Nexulien Settings">
<HeaderCard /> <HeaderCard />
{isPluginDev(user?.id) && !hideContributorCard && ( {isPluginDev(user?.id) && !hideContributorCard && (
<SpecialCard <SpecialCard
title="Thank you for contributing!" title="Contributions"
subtitle="Thank you for contributing!"
description="Since you've contributed to Nexulien, you now have a cool new badge!" description="Since you've contributed to Nexulien, you now have a cool new badge!"
cardImage={CONTRIB_IMAGE} cardImage={CONTRIB_IMAGE}
backgroundImage={CONTRIB_BACKGROUND_IMAGE} backgroundImage={CONTRIB_BACKGROUND_IMAGE}
backgroundGradient="linear-gradient(to left, var(--nx-green), var(--nx-purple))" backgroundGradient="linear-gradient(to left, var(--nx-green), var(--nx-purple))"
/> />
)} )}
<section>
<QuickActionContainer title="Quick Actions" columns="2"> <QuickActionContainer title="Quick Actions" columns="2">
<QuickAction <QuickAction
Icon={PaintbrushIcon} Icon={PaintbrushIcon}
@@ -150,33 +154,31 @@ function VencordSettings() {
action={() => VencordNative.native.openExternal("https://github.com/" + gitRemote)} action={() => VencordNative.native.openExternal("https://github.com/" + gitRemote)}
/> />
</QuickActionContainer> </QuickActionContainer>
</section>
<Forms.FormSection className={Margins.top16} title="Settings" tag="h5"> <section className={Margins.top16}>
<Forms.FormTitle tag="h5">Settings</Forms.FormTitle>
{showHint ? {showHint ?
<NxCard className={`nx-card-help ${Margins.bottom16}`}> <NxCard variant="help" size="small" className={Margins.bottom16}>
<NxText size="small">
If you'd like to change the position of the Nexulien section, change the header card size, or just hide this hint, you can do so in the If you'd like to change the position of the Nexulien section, change the header card size, or just hide this hint, you can do so in the
{" "}<button {" "}<a onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}>
style={{ all: undefined, color: "var(--text-link)", display: "inline-block", backgroundColor: "transparent", padding: 0, fontSize: 16 }}
onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}
>
settings of the Settings plugin settings of the Settings plugin
</button>! </a>!
</NxText>
</NxCard> : <></>} </NxCard> : <></>}
<Switches /> <Switches />
</Forms.FormSection>
{needsVibrancySettings && <VibrancySettings />} {needsVibrancySettings && <VibrancySettings />}
</SettingsTab> </section>
{BackupAndRestoreTab()} {BackupAndRestoreTab()}
</SettingsTab>
</> </>
); );
} }
function nexulien() { function nexulien() {
const audioElement = document.createElement("audio"); const audioElement = document.createElement("audio");
const logo = document.getElementById("nx-settings-logo"); const logo = document.getElementById("vc-settings-logo");
const audioArray = [ const audioArray = [
"https://raw.githubusercontent.com/Nexulien/Assets/main/tts/bonzi.wav", // 🟣🐒 "https://raw.githubusercontent.com/Nexulien/Assets/main/tts/bonzi.wav", // 🟣🐒
@@ -190,7 +192,7 @@ function nexulien() {
audioElement.volume = 0.5; audioElement.volume = 0.5;
audioElement.play(); audioElement.play();
window.setTimeout(function () { window.setTimeout(function () {
logo!.style = "animation: nx-settings-logo-boioioing 0.4s cubic-bezier(0.215, 0.610, 0.355, 1);"; logo!.style = "animation: vc-settings-logo-boioioing 0.4s cubic-bezier(0.215, 0.610, 0.355, 1);";
}, 200); }, 200);
window.setTimeout(function () { window.setTimeout(function () {
logo!.removeAttribute("style"); logo!.removeAttribute("style");
@@ -213,8 +215,8 @@ function HeaderCard() {
return ( return (
<> <>
{headerCardSize !== "none" ? {headerCardSize !== "none" &&
<NxCard className={cl("card", "header", headerCardSize === "minimal" ? "header-minimal" : "")}> <NxCard className={classes(cl("card", "header", headerCardSize === "minimal" && "header-minimal"), "nx-card-grand")}>
<div> <div>
<span className={cl("logo-container")} onClick={() => nexulien()}> <span className={cl("logo-container")} onClick={() => nexulien()}>
<svg width="250" height="50" viewBox="0 0 250 50" fill="none" xmlns="http://www.w3.org/2000/svg" className={cl("logo")} id={cl("logo")}> <svg width="250" height="50" viewBox="0 0 250 50" fill="none" xmlns="http://www.w3.org/2000/svg" className={cl("logo")} id={cl("logo")}>
@@ -229,13 +231,13 @@ function HeaderCard() {
</svg> </svg>
</span> </span>
{headerCardSize === "default" ? <> {headerCardSize === "default" ? <NxText>
{/* ↓ Factual Information */} {/* ↓ Factual Information */}
<span>...the best (worst) discord client mod.</span> <span>...the best (worst) discord client mod.</span><br /><br />
<span>Nexulien doesn't need donations! Please go support <a href="https://github.com/sponsors/Vendicated" target="_blank" rel="noreferrer">Vendicated</a> instead!</span> <span>Nexulien doesn't need donations! Please go support <a href="https://github.com/sponsors/Vendicated" target="_blank" rel="noreferrer">Vendicated</a> instead!</span>
</> : <></>} </NxText> : <></>}
<div className={cl("buttonRow", headerCardSize === "minimal" ? "buttonRow-minimal" : "")}> <div className={cl("buttonRow", headerCardSize === "minimal" && "buttonRow-minimal")}>
<Button <Button
size={headerCardSize === "minimal" ? Button.Sizes.SMALL : Button.Sizes.MEDIUM} size={headerCardSize === "minimal" ? Button.Sizes.SMALL : Button.Sizes.MEDIUM}
onClick={() => window.open("https://github.com/Nexulien")} onClick={() => window.open("https://github.com/Nexulien")}
@@ -258,9 +260,9 @@ function HeaderCard() {
</div> </div>
</div> </div>
{headerCardSize === "default" ? <NxMascot /> : <></>} {headerCardSize === "default" && <NxMascot />}
</NxCard> </NxCard>
: <></>} }
</> </>
); );
} }

View File

@@ -11,6 +11,24 @@ import * as Webpack from "@webpack";
import { wreq } from "@webpack"; import { wreq } from "@webpack";
import { AnyModuleFactory } from "webpack"; import { AnyModuleFactory } from "webpack";
function getWebpackChunkMap() {
const sym = Symbol();
let v: Record<PropertyKey, string> | null = null;
Object.defineProperty(Object.prototype, sym, {
get() {
v = this;
return "";
},
configurable: true
});
wreq.u(sym);
delete Object.prototype[sym];
return v;
}
export async function loadLazyChunks() { export async function loadLazyChunks() {
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader"); const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
@@ -153,17 +171,10 @@ export async function loadLazyChunks() {
} }
// All chunks Discord has mapped to asset files, even if they are not used anymore // All chunks Discord has mapped to asset files, even if they are not used anymore
const allChunks = [] as PropertyKey[]; const chunkMap = getWebpackChunkMap();
if (!chunkMap) throw new Error("Failed to get chunk map");
// Matches "id" or id:
for (const currentMatch of String(wreq.u).matchAll(/(?:"([\deE]+?)"(?![,}]))|(?:([\deE]+?):)/g)) {
const id = currentMatch[1] ?? currentMatch[2];
if (id == null) continue;
const numId = Number(id);
allChunks.push(Number.isNaN(numId) ? id : numId);
}
const allChunks = Object.keys(chunkMap).map(id => Number.isNaN(Number(id)) ? id : Number(id));
if (allChunks.length === 0) throw new Error("Failed to get all chunks"); if (allChunks.length === 0) throw new Error("Failed to get all chunks");
// Chunks which our regex could not catch to load // Chunks which our regex could not catch to load

6
src/globals.d.ts vendored
View File

@@ -16,8 +16,6 @@
* 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 { LoDashStatic } from "lodash";
declare global { declare global {
/** /**
* This exists only at build time, so references to it in patches should insert it * This exists only at build time, so references to it in patches should insert it
@@ -65,9 +63,7 @@ declare global {
export var Vesktop: any; export var Vesktop: any;
export var VesktopNative: any; export var VesktopNative: any;
interface Window extends Record<PropertyKey, any> { interface Window extends Record<PropertyKey, any> { }
_: LoDashStatic;
}
} }
export { }; export { };

View File

@@ -113,7 +113,7 @@ const patchCsp = (headers: PolicyMap) => {
pushDirective("script-src", "'unsafe-inline'", "'unsafe-eval'"); pushDirective("script-src", "'unsafe-inline'", "'unsafe-eval'");
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) { for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
pushDirective(directive, "blob:", "data:", "vencord:"); pushDirective(directive, "blob:", "data:", "vencord:", "vesktop:");
} }
for (const [host, directives] of Object.entries(NativeSettings.store.customCspRules)) { for (const [host, directives] of Object.entries(NativeSettings.store.customCspRules)) {

7
src/modules.d.ts vendored
View File

@@ -16,8 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/// <reference types="standalone-electron-types"/>
declare module "~plugins" { declare module "~plugins" {
const plugins: Record<string, import("./utils/types").Plugin>; const plugins: Record<string, import("./utils/types").Plugin>;
export default plugins; export default plugins;
@@ -28,11 +26,6 @@ declare module "~plugins" {
export const ExcludedPlugins: Record<string, "web" | "discordDesktop" | "vesktop" | "desktop" | "dev">; export const ExcludedPlugins: Record<string, "web" | "discordDesktop" | "vesktop" | "desktop" | "dev">;
} }
declare module "~pluginNatives" {
const pluginNatives: Record<string, Record<string, (event: Electron.IpcMainInvokeEvent, ...args: unknown[]) => unknown>>;
export default pluginNatives;
}
declare module "~git-hash" { declare module "~git-hash" {
const hash: string; const hash: string;
export default hash; export default hash;

12
src/nativeModules.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/// <reference types="standalone-electron-types"/>
declare module "~pluginNatives" {
const pluginNatives: Record<string, Record<string, (event: Electron.IpcMainInvokeEvent, ...args: unknown[]) => unknown>>;
export default pluginNatives;
}

View File

@@ -0,0 +1,87 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./badgeModal.css";
import { classNameFactory } from "@api/Styles";
import { Flex } from "@components/Flex";
import { Heart } from "@components/Heart";
import { Link } from "@components/Link";
import { NxCard, NxText } from "@components/NxComponents";
import DonateButton from "@components/settings/DonateButton";
import { classes } from "@utils/misc";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { Forms } from "@webpack/common";
import { NxSpark } from "./NxSpark";
const cl = classNameFactory("nx-badge-modal-");
export function BadgeModal({ badge, props, nxBadge }: { badge: Record<"tooltip" | "badge", string>, props: ModalProps, nxBadge: boolean; }) {
return (
<ModalRoot {...props} className={classes(cl("root"), !nxBadge ? cl("root-vc") : "")}>
<ModalHeader>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<Forms.FormTitle
tag="h2"
style={{
width: "100%",
textAlign: "center",
margin: 0
}}
>
{!nxBadge ? <>
<Heart />
Vencord Donor
</> : "Special Badge"}
</Forms.FormTitle>
</Flex>
</ModalHeader>
<ModalContent>
<NxCard variant="grand" className={cl("header")}>
<span className={classes(cl("badge"), !nxBadge ? cl("vc-badge") : "")}>
<img src={badge.badge} draggable="false"></img>
</span>
<span className={cl("badge-divider")}></span>
<div>
<Forms.FormTitle
tag="h1"
style={{
margin: 0
}}
>
{badge.tooltip} {nxBadge ? <NxSpark></NxSpark> : ""}
</Forms.FormTitle>
<Forms.FormText>
{!nxBadge ?
"This Badge was given to this user as a special perk for Vencord Donors." :
"This Badge was granted to this user by the owner of Nexulien."
}
</Forms.FormText>
</div>
</NxCard>
<NxCard size="small" className={cl("description")}>
<NxText size="small">
{!nxBadge ?
"Please consider supporting the development of Vencord by becoming a donor! It would mean a lot to them." :
"Currently the only way to get a badge is by asking @zoid.one, or getting a PR accepted in the assets repo."
}
</NxText>
</NxCard>
</ModalContent>
<ModalFooter>
<Flex style={{ width: "100%", justifyContent: "center" }}>
{!nxBadge ?
<DonateButton /> :
<Forms.FormText>
<Link href="https://github.com/Nexulien/assets">Visit the assets repo</Link>
</Forms.FormText>
}
</Flex>
</ModalFooter>
</ModalRoot>
);
}

View File

@@ -1,36 +1,34 @@
.nx-badge-modal-root {
background: linear-gradient(var(--nx-green-background), transparent 75%), var(--modal-background);
background-origin: border-box !important;
}
.nx-badge-modal-header { .nx-badge-modal-header {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin: 16px;
gap: 16px; gap: 16px;
margin-bottom: 16px; margin-bottom: 16px;
padding: 8px 16px;
} }
.nx-badge-modal-badge { .nx-badge-modal-badge {
display: flex; display: flex;
background: linear-gradient(135deg, #70e, #0f9);
/* background: linear-gradient(135deg, #70e, #0f9); */
border-radius: 50%; border-radius: 50%;
height: 96px; height: 96px;
width: 96px;
min-width: 96px;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} margin-left: 8px;
align-self: center;
.nx-badge-modal-badge::after {
position: absolute;
content: "";
width: 88px;
height: 88px;
border-radius: 50%;
background-color: var(--modal-background);;
z-index: 1;
} }
.nx-badge-modal-badge > img { .nx-badge-modal-badge > img {
display: block; display: block;
height: 64px; height: 64px;
width: 64px;
z-index: 2; z-index: 2;
border-radius: 2px;
} }
.nx-badge-modal-header > div { .nx-badge-modal-header > div {
@@ -40,14 +38,21 @@
margin-bottom: 16px; margin-bottom: 16px;
} }
.nx-badge-modal-description { .nx-badge-modal-header > div > div[data-text-variant="text-sm/normal"] {
padding: 16px; color: var(--text-secondary) !important;
background-color: var(--input-background);
border-radius: 8px;
border: 1px solid var(--input-border);
color: var(--text-normal);
} }
.nx-badge-modal-badge.yucky-vencord { .nx-badge-modal-badge-divider {
background: none; width: 1px;
height: 48px;
background-color: var(--border-subtle);
align-self: center;
}
.nx-badge-modal-root-vc {
background: linear-gradient(var(--nx-vc-pink-background), transparent 50%), var(--modal-background);
}
.nx-badge-modal-root-vc .nx-badge-modal-description {
background-color: var(--nx-vc-pink-background) !important;
} }

View File

@@ -17,25 +17,19 @@
*/ */
import "./fixDiscordBadgePadding.css"; import "./fixDiscordBadgePadding.css";
import "./badgeModal.css";
import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges"; import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Heart } from "@components/Heart";
import { Link } from "@components/Link";
import DonateButton from "@components/settings/DonateButton";
import { openContributorModal } from "@components/settings/tabs"; import { openContributorModal } from "@components/settings/tabs";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { copyWithToast, shouldShowContributorBadge } from "@utils/misc"; import { copyWithToast, shouldShowContributorBadge } from "@utils/misc";
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal"; import { closeModal, openModal } from "@utils/modal";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { User } from "@vencord/discord-types"; import { User } from "@vencord/discord-types";
import { ContextMenuApi, Forms, Menu, Toasts, UserStore } from "@webpack/common"; import { ContextMenuApi, Menu, Toasts, UserStore } from "@webpack/common";
import { NxSpark } from "./NxSpark"; import { BadgeModal } from "./BadgeModal";
const CONTRIBUTOR_BADGE = "https://raw.githubusercontent.com/Nexulien/assets/main/badges/contributor.png"; const CONTRIBUTOR_BADGE = "https://raw.githubusercontent.com/Nexulien/assets/main/badges/contributor.png";
@@ -124,6 +118,13 @@ export default definePlugin({
replace: "...$self.getBadgeMouseEventHandlers($1),$&" replace: "...$self.getBadgeMouseEventHandlers($1),$&"
} }
] ]
},
{
find: "profileCardUsernameRow,children:",
replacement: {
match: /badges:(\i)(?<=displayProfile:(\i).+?)/,
replace: "badges:[...$self.getBadges($2),...$1]"
}
} }
], ],
@@ -189,7 +190,6 @@ export default definePlugin({
}, },
getDonorBadges(userId: string) { getDonorBadges(userId: string) {
if (userId !== "343383572805058560") {
return DonorBadges[userId]?.map(badge => ({ return DonorBadges[userId]?.map(badge => ({
image: badge.badge, image: badge.badge,
description: badge.tooltip, description: badge.tooltip,
@@ -206,58 +206,11 @@ export default definePlugin({
closeModal(modalKey); closeModal(modalKey);
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated"); VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
}}> }}>
<ModalRoot {...props}> <BadgeModal badge={badge} props={props} nxBadge={false}></BadgeModal>
<ModalHeader>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<Forms.FormTitle
tag="h2"
style={{
width: "100%",
textAlign: "center",
margin: 0
}}
>
<Heart />
Vencord Donor
</Forms.FormTitle>
</Flex>
</ModalHeader>
<ModalContent className={Margins.bottom16}>
<div className="nx-badge-modal-header">
<span className="nx-badge-modal-badge yucky-vencord">
<img src={badge.badge} draggable="false"></img>
</span>
<div>
<Forms.FormTitle
tag="h1"
style={{
margin: 0
}}
>
{badge.tooltip}
</Forms.FormTitle>
<Forms.FormText>
This Badge was given to this user as a special perk for Vencord Donors.
</Forms.FormText>
</div>
</div>
<div className="nx-badge-modal-description">
<Forms.FormText>
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot to them!
</Forms.FormText>
</div>
</ModalContent>
<ModalFooter>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<DonateButton />
</Flex>
</ModalFooter>
</ModalRoot>
</ErrorBoundary> </ErrorBoundary>
)); ));
}, },
})); }));
}
}, },
getNexulienBadges(userId: string) { getNexulienBadges(userId: string) {
@@ -280,54 +233,7 @@ export default definePlugin({
closeModal(modalKey); closeModal(modalKey);
VencordNative.native.openExternal("https://github.com/Nexulien/assets/blob/main/badges.json"); VencordNative.native.openExternal("https://github.com/Nexulien/assets/blob/main/badges.json");
}}> }}>
<ModalRoot {...props}> <BadgeModal badge={badge} props={props} nxBadge={true}></BadgeModal>
<ModalHeader>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<Forms.FormTitle
tag="h2"
style={{
width: "100%",
textAlign: "center",
margin: 0
}}
>
Special Badge
</Forms.FormTitle>
</Flex>
</ModalHeader>
<ModalContent className={Margins.bottom16}>
<div className="nx-badge-modal-header">
<span className="nx-badge-modal-badge">
<img src={badge.badge} draggable="false"></img>
</span>
<div>
<Forms.FormTitle
tag="h1"
style={{
margin: 0
}}
>
{badge.tooltip} <NxSpark />
</Forms.FormTitle>
<Forms.FormText>
This Badge was granted to this user by the owner of Nexulien.
</Forms.FormText>
</div>
</div>
<div className="nx-badge-modal-description">
<Forms.FormText>
Currently the only way to get one is by asking @thezoidmaster, or getting a PR accepted in the assets repo.
</Forms.FormText>
</div>
</ModalContent>
<ModalFooter>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<Forms.FormText>
<Link href="https://github.com/Nexulien/assets">Visit the assets repo</Link>
</Forms.FormText>
</Flex>
</ModalFooter>
</ModalRoot>
</ErrorBoundary> </ErrorBoundary>
)); ));
}, },

View File

@@ -71,6 +71,15 @@ export default definePlugin({
} }
], ],
// The TRACK event takes an optional `resolve` property that is called when the tracking event was submitted to the server.
// A few spots in Discord await this callback before continuing (most notably the Voice Debug Logging toggle).
// Since we NOOP the AnalyticsActionHandlers module, there is no handler for the TRACK event, so we have to handle it ourselves
flux: {
TRACK(event) {
event?.resolve?.();
}
},
startAt: StartAt.Init, startAt: StartAt.Init,
start() { start() {
// Sentry is initialized in its own WebpackInstance. // Sentry is initialized in its own WebpackInstance.

View File

@@ -39,7 +39,7 @@ export default definePlugin({
find: ".versionHash", find: ".versionHash",
replacement: [ replacement: [
{ {
match: /\[\(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) => {
props = props.replace(/children:\[.+\]/, ""); props = props.replace(/children:\[.+\]/, "");
return `${m},$self.makeInfoElements(${component}, ${props})`; return `${m},$self.makeInfoElements(${component}, ${props})`;
@@ -67,8 +67,9 @@ export default definePlugin({
{ {
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}", find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
replacement: { replacement: {
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/, // Skip the check Discord performs to make sure the section being selected in the user settings context menu is valid
replace: "$2.open($1);return;" match: /(?<=function\((\i),(\i),\i\)\{)(?=let \i=Object.values\(\i\.\i\).+?(\(0,\i\.openUserSettings\))\()/,
replace: (_, settingsPanel, section, openUserSettings) => `${openUserSettings}(${settingsPanel},{section:${section}});return;`
} }
} }
], ],
@@ -80,37 +81,37 @@ export default definePlugin({
{ {
section: SectionTypes.HEADER, section: SectionTypes.HEADER,
label: "Nexulien", label: "Nexulien",
className: "nx-settings-header" className: "vc-settings-header"
}, },
{ {
section: "settings/tabs", section: "settings/tabs",
label: "Settings", label: "Settings",
element: VencordTab, element: VencordTab,
className: "nx-settings" className: "vc-settings"
}, },
{ {
section: "NexulienPlugins", section: "NexulienPlugins",
label: "Plugins", label: "Plugins",
element: PluginsTab, element: PluginsTab,
className: "nx-plugins" className: "vc-plugins"
}, },
{ {
section: "NexulienThemes", section: "NexulienThemes",
label: "Themes", label: "Themes",
element: ThemesTab, element: ThemesTab,
className: "nx-themes" className: "vc-themes"
}, },
!IS_UPDATER_DISABLED && { !IS_UPDATER_DISABLED && {
section: "NexulienUpdater", section: "NexulienUpdater",
label: "Updater", label: "Updater",
element: UpdaterTab, element: UpdaterTab,
className: "nx-updater" className: "vc-updater"
}, },
{ {
section: "NexulienNotifications", section: "NexulienNotifications",
label: "Notifications", label: "Notifications",
element: NotificationsTab, element: NotificationsTab,
className: "nx-settings" className: "nx-notifications"
}, },
{ {
section: "VencordCloud", section: "VencordCloud",

View File

@@ -7,49 +7,12 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs, IS_MAC } from "@utils/constants"; import { Devs, IS_MAC } from "@utils/constants";
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types"; import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
import { Activity, ActivityAssets, ActivityButton } from "@vencord/discord-types";
import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "@vencord/discord-types/enums";
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common"; import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
const Native = VencordNative.pluginHelpers.AppleMusicRichPresence as PluginNative<typeof import("./native")>; const Native = VencordNative.pluginHelpers.AppleMusicRichPresence as PluginNative<typeof import("./native")>;
interface ActivityAssets {
large_image?: string;
large_text?: string;
small_image?: string;
small_text?: string;
}
interface ActivityButton {
label: string;
url: string;
}
interface Activity {
state?: string;
details?: string;
timestamps?: {
start?: number;
end?: number;
};
assets?: ActivityAssets;
buttons?: Array<string>;
name: string;
application_id: string;
metadata?: {
button_urls?: Array<string>;
};
type: number;
flags: number;
}
const enum ActivityType {
PLAYING = 0,
LISTENING = 2,
}
const enum ActivityFlag {
INSTANCE = 1 << 0,
}
export interface TrackData { export interface TrackData {
name: string; name: string;
album?: string; album?: string;
@@ -90,6 +53,25 @@ const settings = definePluginSettings({
{ label: "Listening", value: ActivityType.LISTENING } { label: "Listening", value: ActivityType.LISTENING }
], ],
}, },
statusDisplayType: {
description: "Show the track / artist name in the member list",
type: OptionType.SELECT,
options: [
{
label: "Don't show (shows generic listening message)",
value: "off",
default: true
},
{
label: "Show artist name",
value: "artist"
},
{
label: "Show track name",
value: "track"
}
]
},
refreshInterval: { refreshInterval: {
type: OptionType.SLIDER, type: OptionType.SLIDER,
description: "The interval between activity refreshes (seconds)", description: "The interval between activity refreshes (seconds)",
@@ -258,7 +240,12 @@ export default definePlugin({
metadata: !isRadio && buttons.length ? { button_urls: buttons.map(v => v.url) } : undefined, metadata: !isRadio && buttons.length ? { button_urls: buttons.map(v => v.url) } : undefined,
type: settings.store.activityType, type: settings.store.activityType,
flags: ActivityFlag.INSTANCE, status_display_type: {
"off": ActivityStatusDisplayType.NAME,
"artist": ActivityStatusDisplayType.STATE,
"track": ActivityStatusDisplayType.DETAILS
}[settings.store.statusDisplayType],
flags: ActivityFlags.INSTANCE,
}; };
} }
}); });

View File

@@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { canonicalizeMatch } from "@utils/patches"; import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
import { execFile } from "child_process"; import { execFile } from "child_process";
import { promisify } from "util"; import { promisify } from "util";
@@ -26,24 +26,6 @@ interface RemoteData {
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null; let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null;
const APPLE_MUSIC_BUNDLE_REGEX = /<script type="module" crossorigin src="([a-zA-Z0-9.\-/]+)"><\/script>/;
const APPLE_MUSIC_TOKEN_REGEX = canonicalizeMatch(/\b(\i)="([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)"(?=.+?Bearer \$\{\1\})/);
let cachedToken: string | undefined = undefined;
const getToken = async () => {
if (cachedToken) return cachedToken;
const html = await fetch("https://music.apple.com/").then(r => r.text());
const bundleUrl = new URL(html.match(APPLE_MUSIC_BUNDLE_REGEX)![1], "https://music.apple.com/");
const bundle = await fetch(bundleUrl).then(r => r.text());
const token = bundle.match(APPLE_MUSIC_TOKEN_REGEX)![2];
cachedToken = token;
return token;
};
async function fetchRemoteData({ id, name, artist, album }: { id: string, name: string, artist: string, album: string; }) { async function fetchRemoteData({ id, name, artist, album }: { id: string, name: string, artist: string, album: string; }) {
if (id === cachedRemoteData?.id) { if (id === cachedRemoteData?.id) {
if ("data" in cachedRemoteData) return cachedRemoteData.data; if ("data" in cachedRemoteData) return cachedRemoteData.data;
@@ -51,36 +33,34 @@ async function fetchRemoteData({ id, name, artist, album }: { id: string, name:
} }
try { try {
const dataUrl = new URL("https://amp-api-edge.music.apple.com/v1/catalog/us/search"); const dataUrl = new URL("https://itunes.apple.com/search");
dataUrl.searchParams.set("platform", "web");
dataUrl.searchParams.set("l", "en-US");
dataUrl.searchParams.set("limit", "1");
dataUrl.searchParams.set("with", "serverBubbles");
dataUrl.searchParams.set("types", "songs");
dataUrl.searchParams.set("term", `${name} ${artist} ${album}`); dataUrl.searchParams.set("term", `${name} ${artist} ${album}`);
dataUrl.searchParams.set("include[songs]", "artists"); dataUrl.searchParams.set("media", "music");
dataUrl.searchParams.set("entity", "song");
const token = await getToken();
const songData = await fetch(dataUrl, { const songData = await fetch(dataUrl, {
headers: { headers: {
"accept": "*/*", "user-agent": VENCORD_USER_AGENT,
"accept-language": "en-US,en;q=0.9",
"authorization": `Bearer ${token}`,
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
"origin": "https://music.apple.com",
}, },
}) })
.then(r => r.json()) .then(r => r.json())
.then(data => data.results.song.data[0]); .then(data => data.results.find(song => song.collectionName === album) || data.results[0]);
const artistArtworkURL = await fetch(songData.artistViewUrl)
.then(r => r.text())
.then(data => {
const match = data.match(/<meta property="og:image" content="(.+?)">/);
return match ? match[1].replace(/[0-9]+x.+/, "220x220bb-60.png") : undefined;
})
.catch(() => void 0);
cachedRemoteData = { cachedRemoteData = {
id, id,
data: { data: {
appleMusicLink: songData.attributes.url, appleMusicLink: songData.trackViewUrl,
songLink: `https://song.link/i/${songData.id}`, songLink: `https://song.link/i/${new URL(songData.trackViewUrl).searchParams.get("i")}`,
albumArtwork: songData.attributes.artwork.url.replace("{w}x{h}", "512x512"), albumArtwork: (songData.artworkUrl100).replace("100x100", "512x512"),
artistArtwork: songData.relationships.artists.data[0].attributes.artwork.url.replace("{w}x{h}", "512x512"), artistArtwork: artistArtworkURL
} }
}; };

View File

@@ -79,7 +79,7 @@ export default definePlugin({
ws.onmessage = this.handleEvent; ws.onmessage = this.handleEvent;
const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 1000)); // check if open after 1s const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 5000)); // check if open after 5s
if (!connectionSuccessful) { if (!connectionSuccessful) {
showNotice("Failed to connect to arRPC, is it running?", "Retry", () => { // show notice about failure to connect, with retry/ignore showNotice("Failed to connect to arRPC, is it running?", "Retry", () => { // show notice about failure to connect, with retry/ignore
popNotice(); popNotice();

View File

@@ -0,0 +1,5 @@
# AutoDNDWhilePlaying
This plugin automatically updates your online status (online, idle, dnd) when launching games.
It will change your status back to the prior status when the game is closed.

View File

@@ -0,0 +1,61 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { getUserSettingLazy } from "@api/UserSettings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
let savedStatus: string | null;
const StatusSettings = getUserSettingLazy<string>("status", "status")!;
const settings = definePluginSettings({
statusToSet: {
type: OptionType.SELECT,
description: "Status to set while playing a game",
options: [
{
label: "Online",
value: "online",
},
{
label: "Idle",
value: "idle",
},
{
label: "Do Not Disturb",
value: "dnd",
default: true
},
{
label: "Invisible",
value: "invisible",
}
]
}
});
export default definePlugin({
name: "AutoDNDWhilePlaying",
description: "Automatically updates your online status (online, idle, dnd) when launching games",
authors: [Devs.thororen],
settings,
flux: {
RUNNING_GAMES_CHANGE({ games }) {
const status = StatusSettings.getSetting();
if (games.length > 0) {
if (status !== settings.store.statusToSet) {
savedStatus = status;
StatusSettings.updateSetting(settings.store.statusToSet);
}
} else if (savedStatus && savedStatus !== settings.store.statusToSet) {
StatusSettings.updateSetting(savedStatus);
}
}
}
});

View File

@@ -21,24 +21,39 @@ import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Animations, useStateFromStores } from "@webpack/common"; import { Animations, useStateFromStores } from "@webpack/common";
import type { CSSProperties } from "react"; import type { CSSProperties } from "react";
import { ExpandedGuildFolderStore, settings } from "."; import { ExpandedGuildFolderStore, settings, SortedGuildStore } from ".";
const ChannelRTCStore = findStoreLazy("ChannelRTCStore"); const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
const GuildsBar = findComponentByCodeLazy('("guildsnav")'); const GuildsBar = findComponentByCodeLazy('("guildsnav")');
function getExpandedFolderIds() {
const expandedFolders = ExpandedGuildFolderStore.getExpandedFolders();
const folders = SortedGuildStore.getGuildFolders();
const expandedFolderIds = new Set<string>();
for (const folder of folders) {
if (expandedFolders.has(folder.folderId) && folder.guildIds?.length) {
expandedFolderIds.add(folder.folderId);
}
}
return expandedFolderIds;
}
export default ErrorBoundary.wrap(guildsBarProps => { export default ErrorBoundary.wrap(guildsBarProps => {
const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders()); const expandedFolderIds = useStateFromStores([ExpandedGuildFolderStore, SortedGuildStore], () => getExpandedFolderIds());
const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext()); const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext());
const Sidebar = ( const Sidebar = (
<GuildsBar <GuildsBar
{...guildsBarProps} {...guildsBarProps}
isBetterFolders={true} isBetterFolders={true}
betterFoldersExpandedIds={expandedFolders} betterFoldersExpandedIds={expandedFolderIds}
/> />
); );
const visible = !!expandedFolders.size; const visible = !!expandedFolderIds.size;
const guilds = document.querySelector(guildsBarProps.className.split(" ").map(c => `.${c}`).join("")); const guilds = document.querySelector(guildsBarProps.className.split(" ").map(c => `.${c}`).join(""));
// We need to display none if we are in fullscreen. Yes this seems horrible doing with css, but it's literally how Discord does it. // We need to display none if we are in fullscreen. Yes this seems horrible doing with css, but it's literally how Discord does it.

View File

@@ -22,7 +22,7 @@ import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord"; import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; import { findByPropsLazy, findStoreLazy } from "@webpack";
import { FluxDispatcher } from "@webpack/common"; import { FluxDispatcher } from "@webpack/common";
import { ReactNode } from "react"; import { ReactNode } from "react";
@@ -35,8 +35,7 @@ enum FolderIconDisplay {
} }
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore"); export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
const SortedGuildStore = findStoreLazy("SortedGuildStore"); export const SortedGuildStore = findStoreLazy("SortedGuildStore");
const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand"); const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
let lastGuildId = null as string | null; let lastGuildId = null as string | null;

View File

@@ -31,7 +31,7 @@ import { fetchNamesFromDataStore, getDefaultName, GetOsColor, GetPlatformIcon, s
const AuthSessionsStore = findStoreLazy("AuthSessionsStore"); const AuthSessionsStore = findStoreLazy("AuthSessionsStore");
const UserSettingsModal = findByPropsLazy("saveAccountChanges", "open"); const UserSettingsModal = findByPropsLazy("saveAccountChanges", "open");
const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer"); const TimestampClasses = findByPropsLazy("timestamp", "blockquoteContainer");
const SessionIconClasses = findByPropsLazy("sessionIcon"); const SessionIconClasses = findByPropsLazy("sessionIcon");
const BlobMask = findComponentByCodeLazy("!1,lowerBadgeSize:"); const BlobMask = findComponentByCodeLazy("!1,lowerBadgeSize:");
@@ -92,7 +92,7 @@ export default definePlugin({
<span>{title}</span> <span>{title}</span>
{(savedSession == null || savedSession.isNew) && ( {(savedSession == null || savedSession.isNew) && (
<div <div
className="nx-plugins-badge" className="vc-plugins-badge"
style={{ style={{
backgroundColor: "#ED4245", backgroundColor: "#ED4245",
marginLeft: "2px" marginLeft: "2px"
@@ -108,7 +108,7 @@ export default definePlugin({
renderTimestamp: ErrorBoundary.wrap(({ session, timeLabel }: { session: Session, timeLabel: string; }) => { renderTimestamp: ErrorBoundary.wrap(({ session, timeLabel }: { session: Session, timeLabel: string; }) => {
return ( return (
<Tooltip text={session.approx_last_used_time.toLocaleString()} tooltipClassName={TimestampClasses.timestampTooltip}> <Tooltip text={session.approx_last_used_time.toLocaleString()}>
{props => ( {props => (
<span {...props} className={TimestampClasses.timestamp}> <span {...props} className={TimestampClasses.timestamp}>
{timeLabel} {timeLabel}

View File

@@ -42,7 +42,7 @@ export default function PluginsSubmenu() {
return ( return (
<> <>
<Menu.MenuControlItem <Menu.MenuControlItem
id="nx-plugins-search" id="vc-plugins-search"
control={(props, ref) => ( control={(props, ref) => (
<Menu.MenuSearchControl <Menu.MenuSearchControl
{...props} {...props}

View File

@@ -32,7 +32,8 @@ const settings = definePluginSettings({
organizeMenu: { organizeMenu: {
description: "Organizes the settings cog context menu into categories", description: "Organizes the settings cog context menu into categories",
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
default: true default: true,
restartNeeded: true
}, },
eagerLoad: { eagerLoad: {
description: "Removes the loading delay when opening the menu for the first time", description: "Removes the loading delay when opening the menu for the first time",
@@ -119,12 +120,14 @@ export default definePlugin({
}, },
predicate: () => settings.store.eagerLoad predicate: () => settings.store.eagerLoad
}, },
{ // Settings cog context menu {
// Settings cog context menu
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}", find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
replacement: [ replacement: [
{ {
match: /(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/, match: /=\[\];return (\i)(?=\.forEach)/,
replace: "$self.wrapMenu($1)" replace: "=$self.wrapMap([]);return $self.transformSettingsEntries($1)",
predicate: () => settings.store.organizeMenu
}, },
{ {
match: /case \i\.\i\.DEVELOPER_OPTIONS:return \i;/, match: /case \i\.\i\.DEVELOPER_OPTIONS:return \i;/,
@@ -151,9 +154,7 @@ export default definePlugin({
return <Layer {...props} />; return <Layer {...props} />;
}, },
wrapMenu(list: SettingsEntry[]) { transformSettingsEntries(list: SettingsEntry[]) {
if (!settings.store.organizeMenu) return list;
const items = [{ label: null as string | null, items: [] as SettingsEntry[] }]; const items = [{ label: null as string | null, items: [] as SettingsEntry[] }];
for (const item of list) { for (const item of list) {
@@ -166,15 +167,13 @@ export default definePlugin({
} }
} }
return { return items;
filter(predicate: (item: SettingsEntry) => boolean) {
for (const category of items) {
category.items = category.items.filter(predicate);
}
return this;
}, },
map(render: (item: SettingsEntry) => ReactElement<any>) {
return items wrapMap(toWrap: any[]) {
// @ts-expect-error
toWrap.map = function (render: (item: SettingsEntry) => ReactElement<any>) {
return this
.filter(a => a.items.length > 0) .filter(a => a.items.length > 0)
.map(({ label, items }) => { .map(({ label, items }) => {
const children = items.map(render); const children = items.map(render);
@@ -184,7 +183,6 @@ export default definePlugin({
key={label} key={label}
id={label.replace(/\W/, "_")} id={label.replace(/\W/, "_")}
label={label} label={label}
action={children[0].props.action}
> >
{children} {children}
</Menu.MenuItem> </Menu.MenuItem>
@@ -193,7 +191,8 @@ export default definePlugin({
return children; return children;
} }
}); });
}
}; };
return toWrap;
} }
}); });

View File

@@ -1,4 +1,4 @@
.align-chat-input [class*="panels"] [class*="inner_"], [class*="panels"] [class*="inner_"],
.align-chat-input [class*="rtcConnectionStatus_"] { [class*="rtcConnectionStatus_"] {
height: fit-content; height: fit-content !important;
} }

View File

@@ -95,6 +95,6 @@ export default definePlugin({
deps: [channelId] deps: [channelId]
}); });
return <p style={{ margin: 0 }}>Connected for <span style={{ fontFamily: "var(--font-code)" }}>{formatDuration(time)}</span></p>; return <p style={{ margin: 0, fontFamily: "var(--font-code)" }}>{formatDuration(time)}</p>;
} }
}); });

View File

@@ -27,6 +27,8 @@ import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Activity } from "@vencord/discord-types";
import { ActivityType } from "@vencord/discord-types/enums";
import { findByCodeLazy, findComponentByCodeLazy } from "@webpack"; import { findByCodeLazy, findComponentByCodeLazy } from "@webpack";
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, React, UserStore } from "@webpack/common"; import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, React, UserStore } from "@webpack/common";
@@ -39,41 +41,7 @@ async function getApplicationAsset(key: string): Promise<string> {
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0]; return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
} }
interface ActivityAssets { export const enum TimestampMode {
large_image?: string;
large_text?: string;
small_image?: string;
small_text?: string;
}
interface Activity {
state?: string;
details?: string;
timestamps?: {
start?: number;
end?: number;
};
assets?: ActivityAssets;
buttons?: Array<string>;
name: string;
application_id: string;
metadata?: {
button_urls?: Array<string>;
};
type: ActivityType;
url?: string;
flags: number;
}
const enum ActivityType {
PLAYING = 0,
STREAMING = 1,
LISTENING = 2,
WATCHING = 3,
COMPETING = 5
}
const enum TimestampMode {
NONE, NONE,
NOW, NOW,
TIME, TIME,

View File

@@ -89,7 +89,8 @@ function CreateDecorationModal(props: ModalProps) {
<div className={cl("create-decoration-modal-form-preview-container")}> <div className={cl("create-decoration-modal-form-preview-container")}>
<div className={cl("create-decoration-modal-form")}> <div className={cl("create-decoration-modal-form")}>
{error !== null && <Text color="text-danger" variant="text-xs/normal">{error.message}</Text>} {error !== null && <Text color="text-danger" variant="text-xs/normal">{error.message}</Text>}
<Forms.FormSection title="File"> <section>
<Forms.FormTitle tag="h5">File</Forms.FormTitle>
<FileUpload <FileUpload
filename={file?.name} filename={file?.name}
placeholder="Choose a file" placeholder="Choose a file"
@@ -100,8 +101,9 @@ function CreateDecorationModal(props: ModalProps) {
<Forms.FormText className={Margins.top8}> <Forms.FormText className={Margins.top8}>
File should be APNG or PNG. File should be APNG or PNG.
</Forms.FormText> </Forms.FormText>
</Forms.FormSection> </section>
<Forms.FormSection title="Name"> <section>
<Forms.FormTitle tag="h5">Name</Forms.FormTitle>
<TextInput <TextInput
placeholder="Companion Cube" placeholder="Companion Cube"
value={name} value={name}
@@ -110,7 +112,7 @@ function CreateDecorationModal(props: ModalProps) {
<Forms.FormText className={Margins.top8}> <Forms.FormText className={Margins.top8}>
This name will be used when referring to this decoration. This name will be used when referring to this decoration.
</Forms.FormText> </Forms.FormText>
</Forms.FormSection> </section>
</div> </div>
<div> <div>
<AvatarDecorationModalPreview <AvatarDecorationModalPreview

View File

@@ -113,7 +113,7 @@ function SettingsAboutComponent() {
const [color2, setColor2] = useState(existingColors[1]); const [color2, setColor2] = useState(existingColors[1]);
return ( return (
<Forms.FormSection> <section>
<Forms.FormTitle tag="h3">Usage</Forms.FormTitle> <Forms.FormTitle tag="h3">Usage</Forms.FormTitle>
<Forms.FormText> <Forms.FormText>
After enabling this plugin, you will see custom colors in After enabling this plugin, you will see custom colors in
@@ -191,7 +191,7 @@ function SettingsAboutComponent() {
/> />
</div> </div>
</Forms.FormText> </Forms.FormText>
</Forms.FormSection>); </section>);
} }
export default definePlugin({ export default definePlugin({

View File

@@ -1,3 +1,5 @@
# Fix Images Quality # Fix Images Quality
Prevents images from being loaded as webp, which can cause quality loss Improves quality of images in chat by forcing png format.
This plugin does not change how others see your images!

View File

@@ -9,14 +9,14 @@ import definePlugin from "@utils/types";
export default definePlugin({ export default definePlugin({
name: "FixImagesQuality", name: "FixImagesQuality",
description: "Prevents images from being loaded as webp, which can cause quality loss", description: "Improves quality of images in chat by forcing png format",
authors: [Devs.Nuckyz], authors: [Devs.Nuckyz],
patches: [ patches: [
{ {
find: ".handleImageLoad)", find: ".handleImageLoad)",
replacement: { replacement: {
match: /(?<=null;return )\i\.\i&&\(\i\|\|!\i\.isAnimated.+?:(?=\i&&\(\i="png"\))/, match: /(?<=\i=)"webp"/,
replace: "" replace: '"png"'
} }
} }
] ]

View File

@@ -60,7 +60,7 @@ function makeIcon(showCurrentGame?: boolean) {
}; };
} }
function GameActivityToggleButton() { function GameActivityToggleButton(props: { nameplate?: any; }) {
const showCurrentGame = ShowCurrentGame.useSetting(); const showCurrentGame = ShowCurrentGame.useSetting();
return ( return (
@@ -70,6 +70,7 @@ function GameActivityToggleButton() {
role="switch" role="switch"
aria-checked={!showCurrentGame} aria-checked={!showCurrentGame}
redGlow={!showCurrentGame} redGlow={!showCurrentGame}
plated={props?.nameplate != null}
onClick={() => ShowCurrentGame.updateSetting(old => !old)} onClick={() => ShowCurrentGame.updateSetting(old => !old)}
/> />
); );
@@ -97,7 +98,7 @@ export default definePlugin({
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
replacement: { replacement: {
match: /className:\i\.buttons,.{0,50}children:\[/, match: /className:\i\.buttons,.{0,50}children:\[/,
replace: "$&$self.GameActivityToggleButton()," replace: "$&$self.GameActivityToggleButton(arguments[0]),"
} }
} }
], ],

View File

@@ -131,7 +131,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) {
} }
return ( return (
<Forms.FormSection> <section>
<Forms.FormTitle tag="h3">Filter List</Forms.FormTitle> <Forms.FormTitle tag="h3">Filter List</Forms.FormTitle>
<Forms.FormText className={Margins.bottom8}>Comma separated list of activity IDs to filter (Useful for filtering specific RPC activities and CustomRPC</Forms.FormText> <Forms.FormText className={Margins.bottom8}>Comma separated list of activity IDs to filter (Useful for filtering specific RPC activities and CustomRPC</Forms.FormText>
<TextInput <TextInput
@@ -140,7 +140,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) {
onChange={handleChange} onChange={handleChange}
placeholder="235834946571337729, 343383572805058560" placeholder="235834946571337729, 343383572805058560"
/> />
</Forms.FormSection> </section>
); );
} }

View File

@@ -48,7 +48,7 @@ export default definePlugin({
}, },
// Sections header // Sections header
{ {
find: "#{intl::FRIENDS_SECTION_ONLINE}", find: "#{intl::FRIENDS_SECTION_ONLINE}),className:",
replacement: { replacement: {
match: /,{id:(\i\.\i)\.PENDING,show:.+?className:(\i\.item)/, match: /,{id:(\i\.\i)\.PENDING,show:.+?className:(\i\.item)/,
replace: (rest, relationShipTypes, className) => `,{id:${relationShipTypes}.IMPLICIT,show:true,className:${className},content:"Implicit"}${rest}` replace: (rest, relationShipTypes, className) => `,{id:${relationShipTypes}.IMPLICIT,show:true,className:${className},content:"Implicit"}${rest}`

View File

@@ -222,7 +222,7 @@ export function subscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof Flux
for (const [event, handler] of Object.entries(p.flux)) { for (const [event, handler] of Object.entries(p.flux)) {
const wrappedHandler = p.flux[event] = function () { const wrappedHandler = p.flux[event] = function () {
try { try {
const res = handler.apply(p, arguments as any); const res = handler!.apply(p, arguments as any);
return res instanceof Promise return res instanceof Promise
? res.catch(e => logger.error(`${p.name}: Error while handling ${event}\n`, e)) ? res.catch(e => logger.error(`${p.name}: Error while handling ${event}\n`, e))
: res; : res;
@@ -242,7 +242,7 @@ export function unsubscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof Fl
logger.debug("Unsubscribing from flux events of plugin", p.name); logger.debug("Unsubscribing from flux events of plugin", p.name);
for (const [event, handler] of Object.entries(p.flux)) { for (const [event, handler] of Object.entries(p.flux)) {
fluxDispatcher.unsubscribe(event as FluxEvents, handler); fluxDispatcher.unsubscribe(event as FluxEvents, handler!);
} }
} }
} }

View File

@@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { FormSwitch } from "@components/FormSwitch";
import { insertTextIntoChatInputBox } from "@utils/discord"; import { insertTextIntoChatInputBox } from "@utils/discord";
import { import {
ModalContent, ModalContent,
@@ -25,7 +26,7 @@ import {
ModalRoot, ModalRoot,
openModal, openModal,
} from "@utils/modal"; } from "@utils/modal";
import { Button, Forms, React, Switch, TextInput } from "@webpack/common"; import { Button, Forms, React, TextInput } from "@webpack/common";
import { encrypt } from "../index"; import { encrypt } from "../index";
@@ -65,14 +66,13 @@ function EncModal(props: ModalProps) {
setPassword(e); setPassword(e);
}} }}
/> />
<Switch <FormSwitch
title="Don't use a Cover"
value={noCover} value={noCover}
onChange={(e: boolean) => { onChange={(e: boolean) => {
setNoCover(e); setNoCover(e);
}} }}
> />
Don't use a Cover
</Switch>
</ModalContent> </ModalContent>
<ModalFooter> <ModalFooter>

View File

@@ -21,40 +21,11 @@ import { Link } from "@components/Link";
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";
import { Activity, ActivityAssets, ActivityButton } from "@vencord/discord-types";
import { ActivityFlags, ActivityStatusDisplayType, ActivityType } from "@vencord/discord-types/enums";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common"; import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
interface ActivityAssets {
large_image?: string;
large_text?: string;
small_image?: string;
small_text?: string;
}
interface ActivityButton {
label: string;
url: string;
}
interface Activity {
state: string;
details?: string;
timestamps?: {
start?: number;
};
assets?: ActivityAssets;
buttons?: Array<string>;
name: string;
application_id: string;
status_display_type?: number;
metadata?: {
button_urls?: Array<string>;
};
type: number;
flags: number;
}
interface TrackData { interface TrackData {
name: string; name: string;
album: string; album: string;
@@ -63,16 +34,6 @@ interface TrackData {
imageUrl?: string; imageUrl?: string;
} }
// only relevant enum values
const enum ActivityType {
PLAYING = 0,
LISTENING = 2,
}
const enum ActivityFlag {
INSTANCE = 1 << 0,
}
const enum NameFormat { const enum NameFormat {
StatusName = "status-name", StatusName = "status-name",
ArtistFirst = "artist-first", ArtistFirst = "artist-first",
@@ -367,9 +328,9 @@ export default definePlugin({
details: trackData.name, details: trackData.name,
state: trackData.artist, state: trackData.artist,
status_display_type: { status_display_type: {
"off": 0, "off": ActivityStatusDisplayType.NAME,
"artist": 1, "artist": ActivityStatusDisplayType.STATE,
"track": 2 "track": ActivityStatusDisplayType.DETAILS
}[settings.store.statusDisplayType], }[settings.store.statusDisplayType],
assets, assets,
@@ -379,7 +340,7 @@ export default definePlugin({
}, },
type: settings.store.useListeningStatus ? ActivityType.LISTENING : ActivityType.PLAYING, type: settings.store.useListeningStatus ? ActivityType.LISTENING : ActivityType.PLAYING,
flags: ActivityFlag.INSTANCE, flags: ActivityFlags.INSTANCE,
}; };
} }
}); });

View File

@@ -19,8 +19,9 @@
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
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 { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, PermissionsBits, PermissionStore, UserStore, WindowStore } from "@webpack/common"; import { FluxDispatcher, MessageTypeSets, PermissionsBits, PermissionStore, UserStore, WindowStore } from "@webpack/common";
import NoReplyMentionPlugin from "plugins/noReplyMention"; import NoReplyMentionPlugin from "plugins/noReplyMention";
const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage"); const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage");
@@ -73,7 +74,7 @@ export default definePlugin({
WindowStore.removeChangeListener(focusChanged); WindowStore.removeChangeListener(focusChanged);
}, },
onMessageClick(msg: any, channel, event) { onMessageClick(msg, channel, event) {
const isMe = msg.author.id === UserStore.getCurrentUser().id; const isMe = msg.author.id === UserStore.getCurrentUser().id;
if (!isDeletePressed) { if (!isDeletePressed) {
if (event.detail < 2) return; if (event.detail < 2) return;
@@ -89,8 +90,7 @@ export default definePlugin({
} else { } else {
if (!settings.store.enableDoubleClickToReply) return; if (!settings.store.enableDoubleClickToReply) return;
const EPHEMERAL = 64; if (!MessageTypeSets.REPLYABLE.has(msg.type) || msg.hasFlag(MessageFlags.EPHEMERAL)) return;
if (msg.hasFlag(EPHEMERAL)) return;
const isShiftPress = event.shiftKey && !settings.store.requireModifier; const isShiftPress = event.shiftKey && !settings.store.requireModifier;
const shouldMention = Vencord.Plugins.isPluginEnabled(NoReplyMentionPlugin.name) const shouldMention = Vencord.Plugins.isPluginEnabled(NoReplyMentionPlugin.name)

View File

@@ -11,7 +11,7 @@ import { isNonNullish } from "@utils/guards";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Message } from "@vencord/discord-types"; import { Message } from "@vencord/discord-types";
import { findComponentByCodeLazy } from "@webpack"; import { findComponentByCodeLazy } from "@webpack";
import { SnowflakeUtils, Tooltip } from "@webpack/common"; import { AuthenticationStore, SnowflakeUtils, Tooltip } from "@webpack/common";
type FillValue = ("status-danger" | "status-warning" | "status-positive" | "text-muted"); type FillValue = ("status-danger" | "status-warning" | "status-positive" | "text-muted");
type Fill = [FillValue, FillValue, FillValue]; type Fill = [FillValue, FillValue, FillValue];
@@ -48,6 +48,11 @@ export default definePlugin({
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Show milliseconds", description: "Show milliseconds",
default: false default: false
},
ignoreSelf: {
type: OptionType.BOOLEAN,
description: "Don't add indicator to your own messages",
default: false
} }
}), }),
@@ -91,7 +96,7 @@ export default definePlugin({
}, },
latencyTooltipData(message: Message) { latencyTooltipData(message: Message) {
const { latency, detectDiscordKotlin, showMillis } = this.settings.store; const { latency, detectDiscordKotlin, showMillis, ignoreSelf } = this.settings.store;
const { id, nonce } = message; const { id, nonce } = message;
// Message wasn't received through gateway // Message wasn't received through gateway
@@ -100,6 +105,8 @@ export default definePlugin({
// Bots basically never send a nonce, and if someone does do it then it's usually not a snowflake // Bots basically never send a nonce, and if someone does do it then it's usually not a snowflake
if (message.author.bot) return null; if (message.author.bot) return null;
if (ignoreSelf && message.author.id === AuthenticationStore.getId()) return null;
let isDiscordKotlin = false; let isDiscordKotlin = false;
let delta = SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce); // milliseconds let delta = SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce); // milliseconds
if (!showMillis) { if (!showMillis) {

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