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",
"private": "true",
"version": "1.12.13",
"version": "1.13.2",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"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 "./commands";
export * from "./messages";

View File

@@ -11,3 +11,586 @@ export const enum StickerFormatType {
LOTTIE = 3,
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",
"author": "Vencord Contributors",
"private": false,
"description": "Typescript definitions for the webpack modules of the Discord Web app",
"version": "1.0.0",
"license": "LGPL-3.0-or-later",
@@ -12,8 +13,10 @@
"directory": "packages/discord-types"
},
"dependencies": {
"@types/react": "^19.0.10",
"moment": "^2.22.2",
"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 "./Channel";
export * from "./Guild";

View File

@@ -2,9 +2,9 @@ import { CommandOption } from './Commands';
import { User, UserJSON } from '../User';
import { Embed, EmbedJSON } from './Embed';
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;
*/
export class Message extends DiscordRecord {
@@ -35,7 +35,7 @@ export class Message extends DiscordRecord {
customRenderedContent: unknown;
editedTimestamp: Date;
embeds: Embed[];
flags: number;
flags: MessageFlags;
giftCodes: string[];
id: string;
interaction: {
@@ -100,7 +100,7 @@ export class Message extends DiscordRecord {
stickers: unknown[];
timestamp: moment.Moment;
tts: boolean;
type: number;
type: MessageType;
webhookId: string | undefined;
/**
@@ -121,10 +121,13 @@ export class Message extends DiscordRecord {
removeReaction(emoji: ReactionEmoji, fromCurrentUser: boolean): Message;
getChannelId(): string;
hasFlag(flag: number): boolean;
hasFlag(flag: MessageFlags): boolean;
isCommandType(): boolean;
isEdited(): boolean;
isSystemDM(): boolean;
/** Vencord added */
deleted?: boolean;
}
/** A smaller Message object found in FluxDispatcher and elsewhere. */
@@ -193,3 +196,9 @@ export interface MessageReaction {
emoji: ReactionEmoji;
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;
};
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 = {
CENTER: "center";
TOP: "top";

View File

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

View File

@@ -1,7 +1,7 @@
{
"name": "@vencord/types",
"private": false,
"version": "1.11.5",
"version": "1.13.2",
"description": "",
"types": "index.d.ts",
"scripts": {
@@ -19,9 +19,14 @@
"dependencies": {
"@types/lodash": "4.17.15",
"@types/node": "^22.13.4",
"@types/react": "18.3.1",
"@types/react-dom": "18.3.1",
"standalone-electron-types": "^34.2.0",
"@vencord/discord-types": "^1.0.0",
"highlight.js": "11.11.1",
"moment": "^2.22.2",
"ts-pattern": "^5.6.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':
specifier: 18.3.1
version: 18.3.1
standalone-electron-types:
specifier: ^34.2.0
version: 34.2.0
'@vencord/discord-types':
specifier: ^1.0.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:
specifier: ^4.35.0
version: 4.38.0
@@ -742,6 +751,11 @@ packages:
'@vap/shiki@0.10.5':
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:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
@@ -3499,6 +3513,12 @@ snapshots:
vscode-oniguruma: 1.7.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:
dependencies:
event-target-shim: 5.0.1

View File

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

View File

@@ -23,7 +23,7 @@ import type { HTMLProps } from "react";
export function ErrorCard(props: React.PropsWithChildren<HTMLProps<HTMLDivElement>>) {
return (
<div {...props} className={classes(props.className, "nx-error-card")}>
<div {...props} className={classes(props.className, "vc-error-card")}>
{props.children}
</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>) {
return (
<svg
className={classes(className, "nx-icon")}
className={classes(className, "vc-icon")}
role="img"
width={width}
height={height}
@@ -51,7 +51,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
<Icon
height={height}
width={width}
className={classes(className, "nx-link-icon")}
className={classes(className, "vc-link-icon")}
viewBox="0 0 24 24"
>
<g fill="none" fillRule="evenodd">
@@ -69,7 +69,7 @@ export function CopyIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-copy-icon")}
className={classes(props.className, "vc-copy-icon")}
viewBox="0 0 24 24"
>
<g fill="currentColor">
@@ -88,7 +88,7 @@ export function OpenExternalIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-open-external-icon")}
className={classes(props.className, "vc-open-external-icon")}
viewBox="0 0 24 24"
>
<polygon
@@ -104,7 +104,7 @@ export function ImageIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-image-icon")}
className={classes(props.className, "vc-image-icon")}
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" />
@@ -116,7 +116,7 @@ export function InfoIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-info-icon")}
className={classes(props.className, "vc-info-icon")}
viewBox="0 0 24 24"
>
<path
@@ -133,7 +133,7 @@ export function OwnerCrownIcon(props: IconProps) {
<Icon
aria-label={getIntlMessage("GUILD_OWNER")}
{...props}
className={classes(props.className, "nx-owner-crown-icon")}
className={classes(props.className, "vc-owner-crown-icon")}
role="img"
viewBox="0 0 16 16"
>
@@ -154,7 +154,7 @@ export function ScreenshareIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-screenshare-icon")}
className={classes(props.className, "vc-screenshare-icon")}
viewBox="0 0 24 24"
>
<path
@@ -169,7 +169,7 @@ export function ImageVisible(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-image-visible")}
className={classes(props.className, "vc-image-visible")}
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" />
@@ -181,7 +181,7 @@ export function ImageInvisible(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-image-invisible")}
className={classes(props.className, "vc-image-invisible")}
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" />
@@ -193,7 +193,7 @@ export function Microphone(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-microphone")}
className={classes(props.className, "vc-microphone")}
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" />
@@ -206,7 +206,7 @@ export function CogWheel(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-cog-wheel")}
className={classes(props.className, "vc-cog-wheel")}
viewBox="0 0 24 24"
>
<path
@@ -223,7 +223,7 @@ export function ReplyIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-reply-icon")}
className={classes(props.className, "vc-reply-icon")}
viewBox="0 0 24 24"
>
<path
@@ -238,7 +238,7 @@ export function DeleteIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-delete-icon")}
className={classes(props.className, "vc-delete-icon")}
viewBox="0 0 24 24"
>
<path
@@ -257,7 +257,7 @@ export function PlusIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-plus-icon")}
className={classes(props.className, "vc-plus-icon")}
viewBox="0 0 18 18"
>
<polygon
@@ -273,7 +273,7 @@ export function NoEntrySignIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-no-entry-sign-icon")}
className={classes(props.className, "vc-no-entry-sign-icon")}
viewBox="0 0 24 24"
>
<path
@@ -292,7 +292,7 @@ export function SafetyIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-safety-icon")}
className={classes(props.className, "vc-safety-icon")}
viewBox="0 0 24 24"
>
<path
@@ -310,7 +310,7 @@ export function NotesIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-notes-icon")}
className={classes(props.className, "vc-notes-icon")}
viewBox="0 0 24 24"
>
<path
@@ -331,7 +331,7 @@ export function FolderIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-folder-icon")}
className={classes(props.className, "vc-folder-icon")}
viewBox="0 0 24 24"
>
<path
@@ -346,7 +346,7 @@ export function LogIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-log-icon")}
className={classes(props.className, "vc-log-icon")}
viewBox="0 0 24 24"
>
<path
@@ -363,7 +363,7 @@ export function RestartIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-restart-icon")}
className={classes(props.className, "vc-restart-icon")}
viewBox="0 0 24 24"
>
<path
@@ -378,7 +378,7 @@ export function PaintbrushIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-paintbrush-icon")}
className={classes(props.className, "vc-paintbrush-icon")}
viewBox="0 0 24 24"
>
<path
@@ -395,7 +395,7 @@ export function PencilIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "nx-pencil-icon")}
className={classes(props.className, "vc-pencil-icon")}
viewBox="0 0 24 24"
>
<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);
}
.nx-card-small {
padding: 12px !important;
}
.nx-card-grand {
background-color: var(--background-base-lower) !important;
box-shadow: var(--elevation-low);
}
.nx-card-warning {
background-color: var(--background-feedback-warning) !important;
}
@@ -28,7 +37,7 @@
background-color: unset !important;
}
.nx-card-title {
.nx-title {
font-family: var(--font-display);
font-size: 16px;
font-weight: 700;
@@ -39,3 +48,11 @@
line-height: 2;
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 * from "./ErrorCard";
export * from "./Flex";
export * from "./FormDivider";
export * from "./FormSwitch";
export * from "./Grid";
export * from "./Heart";
export * from "./Icons";
export * from "./Link";
export * from "./settings";
export * from "./Switch";

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ import "./QuickAction.css";
import { classNameFactory } from "@api/Styles";
import { InfoIcon } from "@components/Icons";
import { NxCard, NxCardTitle } from "@components/NxCard";
import { NxCard, NxText, NxTitle } from "@components/NxComponents";
import { openInviteModal } from "@utils/discord";
import { classes } from "@utils/misc";
import { closeAllModals } from "@utils/modal";
@@ -16,7 +16,7 @@ import { findByPropsLazy } from "@webpack";
import { Alerts, Button, FluxDispatcher, GuildStore, NavigationRouter } from "@webpack/common";
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");
export interface QuickActionProps {
@@ -46,7 +46,7 @@ export function QuickAction(props: QuickActionProps) {
export function QuickActionContainer({ title, children, columns = "3" }: PropsWithChildren<QuickActionContainerProps>) {
return (
<NxCard className={cl("container")}>
<NxCardTitle className={cl("title")}>
<NxTitle className={cl("title")}>
{title}
<button
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>
<p>No one's around to help.</p>
</div>
<NxCard className="nx-card-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.
<NxCard variant="help">
<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>
</>
),
@@ -84,7 +84,7 @@ export function QuickActionContainer({ title, children, columns = "3" }: PropsWi
>
<InfoIcon />
</button>
</NxCardTitle>
</NxTitle>
<span className={cl("containerButtons-" + columns)}>{children}</span>
</NxCard>
);

View File

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

View File

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

View File

@@ -9,7 +9,7 @@
U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
.nx-settings-header {
.vc-settings-header {
display: flex;
flex-direction: row;
gap: 128px;
@@ -20,13 +20,13 @@
padding: 24px;
}
.nx-settings-header-minimal > div > .nx-settings-logo-container {
.vc-settings-header-minimal > div > .vc-settings-logo-container {
width: 100%;
height: 20%;
min-height: 36px;
}
@keyframes nx-settings-logo-boioioing {
@keyframes vc-settings-logo-boioioing {
0% {
transform: scale(1) scaleX(1) scaleY(1);
}
@@ -48,7 +48,7 @@
}
}
.nx-settings-logo {
.vc-settings-logo {
width: auto;
height: 100%;
object-fit: contain;
@@ -56,33 +56,33 @@
.nx-mascot {
/* 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;
flex-direction: column;
gap: 1em;
}
.nx-settings-header-minimal > div {
.vc-settings-header-minimal > div {
display: flex !important;
flex-direction: row;
min-width: 100% !important;
max-height: 30px;
}
.nx-settings-header-minimal .nx-settings-logo {
.vc-settings-header-minimal .vc-settings-logo {
transform: translateY(-4px);
}
.nx-settings-buttonRow {
.vc-settings-buttonRow {
display: flex;
flex-direction: row;
gap: 0.5em;
}
.nx-settings-buttonRow-minimal {
.vc-settings-buttonRow-minimal {
display: inline-flex;
min-width: fit-content !important;
max-height: fit-content;

View File

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

View File

@@ -1,10 +1,30 @@
:root {
--nx-green: #0f9;
--nx-purple: #70e;
--nx-vc-pink: #d3869b;
--nx-green-background: #00ff9914;
--nx-purple-background: #7700ee14;
--nx-vc-pink-background: #d3869b14;
}
.nx-removeSwitchDivider div[class*="divider"] {
display: none;
.nx-code {
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; }>) {
return (
<Forms.FormSection>
<section>
<Text
variant="heading-lg/semibold"
tag="h2"
@@ -35,7 +35,7 @@ export function SettingsTab({ title, children }: PropsWithChildren<{ title: stri
</Text>
{children}
</Forms.FormSection>
</section>
);
}

View File

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

View File

@@ -4,8 +4,9 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { FormSwitch } from "@components/FormSwitch";
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 = {
"\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)",
@@ -69,15 +70,14 @@ export function ReplacementInput({ replacement, setReplacement, replacementError
</div>
)}
<Switch
<FormSwitch
className={Margins.top16}
value={isFunc}
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
>
Treat as Function
</Switch>
/>
</>
);
}

View File

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

View File

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

View File

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

View File

@@ -12,12 +12,12 @@ import { MaskedLink, Tooltip } from "@webpack/common";
export function GithubLinkIcon() {
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() {
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 {

View File

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

View File

@@ -23,7 +23,7 @@ import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { NxCard } from "@components/NxCard";
import { NxCard, NxText } from "@components/NxComponents";
import { debounce } from "@shared/debounce";
import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins";
@@ -40,7 +40,7 @@ import { PluginMeta } from "~plugins";
import { OptionComponentMap } from "./components";
import { openContributorModal } from "./ContributorModal";
const cl = classNameFactory("nx-plugin-modal-");
const cl = classNameFactory("vc-plugin-modal-");
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
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() {
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]) => {
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];
return (
<ErrorBoundary noop key={key}>
<Component
id={key}
key={key}
option={setting}
onChange={debounce(onChange)}
pluginSettings={pluginSettings}
definedSettings={plugin.settings}
/>
</ErrorBoundary>
);
});
return (
<NxCard className={Margins.top16}>
<div className="nx-plugins-settings">
<NxCard variant="grand" className={classes(Margins.top16, Margins.bottom8)}>
<div className="vc-plugins-settings">
{options}
</div>
</NxCard>
@@ -177,25 +180,25 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
</ModalHeader>
<ModalContent className={Margins.bottom16}>
<Forms.FormSection>
<section>
<Flex className={cl("info")}>
<Forms.FormText className={cl("description")}>{plugin.description}</Forms.FormText>
</Flex>
</Forms.FormSection>
</section>
{!!plugin.settingsAboutComponent && (
<div className={Margins.top16}>
<Forms.FormSection>
<section>
<ErrorBoundary message="An error occurred while rendering this plugin's custom Info Component">
<plugin.settingsAboutComponent />
</ErrorBoundary>
</Forms.FormSection>
</section>
</div>
)}
<Forms.FormSection>
<section>
{renderSettings()}
</Forms.FormSection>
</section>
</ModalContent>
</ModalRoot>
);

View File

@@ -16,7 +16,7 @@
* 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 { React, useState } from "@webpack/common";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex";
import { Link } from "@components/Link";
import { NxCard, NxCardTitle } from "@components/NxCard";
import { NxCard, NxTitle } from "@components/NxComponents";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
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; }) {
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 }) => (
<div
key={hash}
@@ -81,7 +81,7 @@ export function Updatable(props: CommonProps) {
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 ? (
<>
<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 (
<>
<NxCard>
<NxCardTitle>Repository</NxCardTitle>
<NxTitle>Repository</NxTitle>
<Forms.FormText className="nx-text-selectable">
<Forms.FormText className="vc-text-selectable">
{repoPending
? repo
: err
@@ -167,7 +167,7 @@ export function Repository({ repo, repoPending, err }: CommonProps) {
</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>
</NxCard>
</>

View File

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

View File

@@ -1,4 +1,4 @@
.nx-updater-changes {
.vc-updater-changes {
background: var(--background-code);
border: 1px solid var(--border-normal);
margin-top: 16px !important;
@@ -6,21 +6,10 @@
border-radius: 4px;
}
.nx-updater-modal {
.vc-updater-modal {
padding: 1.5em !important;
}
.nx-updater-modal-close-button {
.vc-updater-modal-close-button {
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 ErrorBoundary from "@components/ErrorBoundary";
import { Margins } from "@utils/margins";
import { identity } from "@utils/misc";
import { Forms, Select } from "@webpack/common";
@@ -15,6 +16,7 @@ export function VibrancySettings() {
return (
<>
<Forms.FormTitle tag="h5">Window vibrancy style (requires restart)</Forms.FormTitle>
<ErrorBoundary noop>
<Select
className={Margins.bottom20}
placeholder="Window vibrancy style"
@@ -74,7 +76,9 @@ export function VibrancySettings() {
]}
select={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() {
return (
<Forms.FormSection className={Margins.top16} title="Vencord Notifications" tag="h5">
<section className={Margins.top16}>
<Forms.FormTitle tag="h5">Vencord Notifications</Forms.FormTitle>
<Flex>
<Button onClick={openNotificationSettingsModal}>
Notification Settings
@@ -24,7 +25,7 @@ export function NotificationSection() {
View Notification Log
</Button>
</Flex>
</Forms.FormSection>
</section>
);
}

View File

@@ -21,8 +21,9 @@ import "@components/settings/styles.css";
import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { FormSwitch } from "@components/FormSwitch";
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 { QuickAction, QuickActionContainer } from "@components/settings/QuickAction";
import { SpecialCard } from "@components/settings/SpecialCard";
@@ -33,14 +34,14 @@ import { gitRemote } from "@shared/vencordUserAgent";
import { IS_MAC, IS_WINDOWS } from "@utils/constants";
import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins";
import { isPluginDev } from "@utils/misc";
import { classes, isPluginDev } from "@utils/misc";
import { closeAllModals } from "@utils/modal";
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";
const cl = classNameFactory("nx-settings-");
const cl = classNameFactory("vc-settings-");
const CONTRIB_IMAGE = "https://cdn.discordapp.com/emojis/1337858798664024156.png";
const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1337878381517078649.png?size=2048";
@@ -94,14 +95,13 @@ function Switches() {
}>;
return Switches.map(s => s && (
<Switch
<FormSwitch
key={s.key}
title={s.title}
description={s.note}
value={settings[s.key]}
onChange={v => settings[s.key] = v}
note={s.note}
>
{s.title}
</Switch>
/>
));
}
@@ -116,15 +116,19 @@ function VencordSettings() {
<>
<SettingsTab title="Nexulien Settings">
<HeaderCard />
{isPluginDev(user?.id) && !hideContributorCard && (
<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!"
cardImage={CONTRIB_IMAGE}
backgroundImage={CONTRIB_BACKGROUND_IMAGE}
backgroundGradient="linear-gradient(to left, var(--nx-green), var(--nx-purple))"
/>
)}
<section>
<QuickActionContainer title="Quick Actions" columns="2">
<QuickAction
Icon={PaintbrushIcon}
@@ -150,33 +154,31 @@ function VencordSettings() {
action={() => VencordNative.native.openExternal("https://github.com/" + gitRemote)}
/>
</QuickActionContainer>
</section>
<Forms.FormSection className={Margins.top16} title="Settings" tag="h5">
<section className={Margins.top16}>
<Forms.FormTitle tag="h5">Settings</Forms.FormTitle>
{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
{" "}<button
style={{ all: undefined, color: "var(--text-link)", display: "inline-block", backgroundColor: "transparent", padding: 0, fontSize: 16 }}
onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}
>
{" "}<a onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}>
settings of the Settings plugin
</button>!
</a>!
</NxText>
</NxCard> : <></>}
<Switches />
</Forms.FormSection>
{needsVibrancySettings && <VibrancySettings />}
</SettingsTab>
</section>
{BackupAndRestoreTab()}
</SettingsTab>
</>
);
}
function nexulien() {
const audioElement = document.createElement("audio");
const logo = document.getElementById("nx-settings-logo");
const logo = document.getElementById("vc-settings-logo");
const audioArray = [
"https://raw.githubusercontent.com/Nexulien/Assets/main/tts/bonzi.wav", // 🟣🐒
@@ -190,7 +192,7 @@ function nexulien() {
audioElement.volume = 0.5;
audioElement.play();
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);
window.setTimeout(function () {
logo!.removeAttribute("style");
@@ -213,8 +215,8 @@ function HeaderCard() {
return (
<>
{headerCardSize !== "none" ?
<NxCard className={cl("card", "header", headerCardSize === "minimal" ? "header-minimal" : "")}>
{headerCardSize !== "none" &&
<NxCard className={classes(cl("card", "header", headerCardSize === "minimal" && "header-minimal"), "nx-card-grand")}>
<div>
<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")}>
@@ -229,13 +231,13 @@ function HeaderCard() {
</svg>
</span>
{headerCardSize === "default" ? <>
{headerCardSize === "default" ? <NxText>
{/* ↓ 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>
</> : <></>}
</NxText> : <></>}
<div className={cl("buttonRow", headerCardSize === "minimal" ? "buttonRow-minimal" : "")}>
<div className={cl("buttonRow", headerCardSize === "minimal" && "buttonRow-minimal")}>
<Button
size={headerCardSize === "minimal" ? Button.Sizes.SMALL : Button.Sizes.MEDIUM}
onClick={() => window.open("https://github.com/Nexulien")}
@@ -258,9 +260,9 @@ function HeaderCard() {
</div>
</div>
{headerCardSize === "default" ? <NxMascot /> : <></>}
{headerCardSize === "default" && <NxMascot />}
</NxCard>
: <></>}
}
</>
);
}

View File

@@ -11,6 +11,24 @@ import * as Webpack from "@webpack";
import { wreq } 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() {
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
const allChunks = [] as PropertyKey[];
// 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 chunkMap = getWebpackChunkMap();
if (!chunkMap) throw new Error("Failed to get chunk map");
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");
// 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/>.
*/
import { LoDashStatic } from "lodash";
declare global {
/**
* 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 VesktopNative: any;
interface Window extends Record<PropertyKey, any> {
_: LoDashStatic;
}
interface Window extends Record<PropertyKey, any> { }
}
export { };

View File

@@ -113,7 +113,7 @@ const patchCsp = (headers: PolicyMap) => {
pushDirective("script-src", "'unsafe-inline'", "'unsafe-eval'");
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)) {

7
src/modules.d.ts vendored
View File

@@ -16,8 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/// <reference types="standalone-electron-types"/>
declare module "~plugins" {
const plugins: Record<string, import("./utils/types").Plugin>;
export default plugins;
@@ -28,11 +26,6 @@ declare module "~plugins" {
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" {
const hash: string;
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 {
display: flex;
flex-direction: row;
margin: 16px;
gap: 16px;
margin-bottom: 16px;
padding: 8px 16px;
}
.nx-badge-modal-badge {
display: flex;
background: linear-gradient(135deg, #70e, #0f9);
/* background: linear-gradient(135deg, #70e, #0f9); */
border-radius: 50%;
height: 96px;
width: 96px;
min-width: 96px;
align-items: center;
justify-content: center;
}
.nx-badge-modal-badge::after {
position: absolute;
content: "";
width: 88px;
height: 88px;
border-radius: 50%;
background-color: var(--modal-background);;
z-index: 1;
margin-left: 8px;
align-self: center;
}
.nx-badge-modal-badge > img {
display: block;
height: 64px;
width: 64px;
z-index: 2;
border-radius: 2px;
}
.nx-badge-modal-header > div {
@@ -40,14 +38,21 @@
margin-bottom: 16px;
}
.nx-badge-modal-description {
padding: 16px;
background-color: var(--input-background);
border-radius: 8px;
border: 1px solid var(--input-border);
color: var(--text-normal);
.nx-badge-modal-header > div > div[data-text-variant="text-sm/normal"] {
color: var(--text-secondary) !important;
}
.nx-badge-modal-badge.yucky-vencord {
background: none;
.nx-badge-modal-badge-divider {
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 "./badgeModal.css";
import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
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 { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
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 { 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";
@@ -124,6 +118,13 @@ export default definePlugin({
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) {
if (userId !== "343383572805058560") {
return DonorBadges[userId]?.map(badge => ({
image: badge.badge,
description: badge.tooltip,
@@ -206,58 +206,11 @@ export default definePlugin({
closeModal(modalKey);
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
}}>
<ModalRoot {...props}>
<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>
<BadgeModal badge={badge} props={props} nxBadge={false}></BadgeModal>
</ErrorBoundary>
));
},
}));
}
},
getNexulienBadges(userId: string) {
@@ -280,54 +233,7 @@ export default definePlugin({
closeModal(modalKey);
VencordNative.native.openExternal("https://github.com/Nexulien/assets/blob/main/badges.json");
}}>
<ModalRoot {...props}>
<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>
<BadgeModal badge={badge} props={props} nxBadge={true}></BadgeModal>
</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,
start() {
// Sentry is initialized in its own WebpackInstance.

View File

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

View File

@@ -7,49 +7,12 @@
import { definePluginSettings } from "@api/Settings";
import { Devs, IS_MAC } from "@utils/constants";
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";
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 {
name: string;
album?: string;
@@ -90,6 +53,25 @@ const settings = definePluginSettings({
{ 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: {
type: OptionType.SLIDER,
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,
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
*/
import { canonicalizeMatch } from "@utils/patches";
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
import { execFile } from "child_process";
import { promisify } from "util";
@@ -26,24 +26,6 @@ interface RemoteData {
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; }) {
if (id === cachedRemoteData?.id) {
if ("data" in cachedRemoteData) return cachedRemoteData.data;
@@ -51,36 +33,34 @@ async function fetchRemoteData({ id, name, artist, album }: { id: string, name:
}
try {
const dataUrl = new URL("https://amp-api-edge.music.apple.com/v1/catalog/us/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");
const dataUrl = new URL("https://itunes.apple.com/search");
dataUrl.searchParams.set("term", `${name} ${artist} ${album}`);
dataUrl.searchParams.set("include[songs]", "artists");
const token = await getToken();
dataUrl.searchParams.set("media", "music");
dataUrl.searchParams.set("entity", "song");
const songData = await fetch(dataUrl, {
headers: {
"accept": "*/*",
"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",
"user-agent": VENCORD_USER_AGENT,
},
})
.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 = {
id,
data: {
appleMusicLink: songData.attributes.url,
songLink: `https://song.link/i/${songData.id}`,
albumArtwork: songData.attributes.artwork.url.replace("{w}x{h}", "512x512"),
artistArtwork: songData.relationships.artists.data[0].attributes.artwork.url.replace("{w}x{h}", "512x512"),
appleMusicLink: songData.trackViewUrl,
songLink: `https://song.link/i/${new URL(songData.trackViewUrl).searchParams.get("i")}`,
albumArtwork: (songData.artworkUrl100).replace("100x100", "512x512"),
artistArtwork: artistArtworkURL
}
};

View File

@@ -79,7 +79,7 @@ export default definePlugin({
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) {
showNotice("Failed to connect to arRPC, is it running?", "Retry", () => { // show notice about failure to connect, with retry/ignore
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 type { CSSProperties } from "react";
import { ExpandedGuildFolderStore, settings } from ".";
import { ExpandedGuildFolderStore, settings, SortedGuildStore } from ".";
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
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 => {
const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders());
const expandedFolderIds = useStateFromStores([ExpandedGuildFolderStore, SortedGuildStore], () => getExpandedFolderIds());
const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext());
const Sidebar = (
<GuildsBar
{...guildsBarProps}
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(""));
// 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 { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { findByPropsLazy, findStoreLazy } from "@webpack";
import { FluxDispatcher } from "@webpack/common";
import { ReactNode } from "react";
@@ -35,8 +35,7 @@ enum FolderIconDisplay {
}
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
const SortedGuildStore = findStoreLazy("SortedGuildStore");
const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
export const SortedGuildStore = findStoreLazy("SortedGuildStore");
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
let lastGuildId = null as string | null;

View File

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

View File

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

View File

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

View File

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

View File

@@ -95,6 +95,6 @@ export default definePlugin({
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 { useAwaiter } from "@utils/react";
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 { 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];
}
interface ActivityAssets {
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 {
export const enum TimestampMode {
NONE,
NOW,
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")}>
{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
filename={file?.name}
placeholder="Choose a file"
@@ -100,8 +101,9 @@ function CreateDecorationModal(props: ModalProps) {
<Forms.FormText className={Margins.top8}>
File should be APNG or PNG.
</Forms.FormText>
</Forms.FormSection>
<Forms.FormSection title="Name">
</section>
<section>
<Forms.FormTitle tag="h5">Name</Forms.FormTitle>
<TextInput
placeholder="Companion Cube"
value={name}
@@ -110,7 +112,7 @@ function CreateDecorationModal(props: ModalProps) {
<Forms.FormText className={Margins.top8}>
This name will be used when referring to this decoration.
</Forms.FormText>
</Forms.FormSection>
</section>
</div>
<div>
<AvatarDecorationModalPreview

View File

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

View File

@@ -1,3 +1,5 @@
# 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({
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],
patches: [
{
find: ".handleImageLoad)",
replacement: {
match: /(?<=null;return )\i\.\i&&\(\i\|\|!\i\.isAnimated.+?:(?=\i&&\(\i="png"\))/,
replace: ""
match: /(?<=\i=)"webp"/,
replace: '"png"'
}
}
]

View File

@@ -60,7 +60,7 @@ function makeIcon(showCurrentGame?: boolean) {
};
}
function GameActivityToggleButton() {
function GameActivityToggleButton(props: { nameplate?: any; }) {
const showCurrentGame = ShowCurrentGame.useSetting();
return (
@@ -70,6 +70,7 @@ function GameActivityToggleButton() {
role="switch"
aria-checked={!showCurrentGame}
redGlow={!showCurrentGame}
plated={props?.nameplate != null}
onClick={() => ShowCurrentGame.updateSetting(old => !old)}
/>
);
@@ -97,7 +98,7 @@ export default definePlugin({
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
replacement: {
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 (
<Forms.FormSection>
<section>
<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>
<TextInput
@@ -140,7 +140,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) {
onChange={handleChange}
placeholder="235834946571337729, 343383572805058560"
/>
</Forms.FormSection>
</section>
);
}

View File

@@ -48,7 +48,7 @@ export default definePlugin({
},
// Sections header
{
find: "#{intl::FRIENDS_SECTION_ONLINE}",
find: "#{intl::FRIENDS_SECTION_ONLINE}),className:",
replacement: {
match: /,{id:(\i\.\i)\.PENDING,show:.+?className:(\i\.item)/,
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)) {
const wrappedHandler = p.flux[event] = function () {
try {
const res = handler.apply(p, arguments as any);
const res = handler!.apply(p, arguments as any);
return res instanceof Promise
? res.catch(e => logger.error(`${p.name}: Error while handling ${event}\n`, e))
: res;
@@ -242,7 +242,7 @@ export function unsubscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof Fl
logger.debug("Unsubscribing from flux events of plugin", p.name);
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/>.
*/
import { FormSwitch } from "@components/FormSwitch";
import { insertTextIntoChatInputBox } from "@utils/discord";
import {
ModalContent,
@@ -25,7 +26,7 @@ import {
ModalRoot,
openModal,
} from "@utils/modal";
import { Button, Forms, React, Switch, TextInput } from "@webpack/common";
import { Button, Forms, React, TextInput } from "@webpack/common";
import { encrypt } from "../index";
@@ -65,14 +66,13 @@ function EncModal(props: ModalProps) {
setPassword(e);
}}
/>
<Switch
<FormSwitch
title="Don't use a Cover"
value={noCover}
onChange={(e: boolean) => {
setNoCover(e);
}}
>
Don't use a Cover
</Switch>
/>
</ModalContent>
<ModalFooter>

View File

@@ -21,40 +21,11 @@ import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
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 { 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 {
name: string;
album: string;
@@ -63,16 +34,6 @@ interface TrackData {
imageUrl?: string;
}
// only relevant enum values
const enum ActivityType {
PLAYING = 0,
LISTENING = 2,
}
const enum ActivityFlag {
INSTANCE = 1 << 0,
}
const enum NameFormat {
StatusName = "status-name",
ArtistFirst = "artist-first",
@@ -367,9 +328,9 @@ export default definePlugin({
details: trackData.name,
state: trackData.artist,
status_display_type: {
"off": 0,
"artist": 1,
"track": 2
"off": ActivityStatusDisplayType.NAME,
"artist": ActivityStatusDisplayType.STATE,
"track": ActivityStatusDisplayType.DETAILS
}[settings.store.statusDisplayType],
assets,
@@ -379,7 +340,7 @@ export default definePlugin({
},
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 { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { MessageFlags } from "@vencord/discord-types/enums";
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";
const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage");
@@ -73,7 +74,7 @@ export default definePlugin({
WindowStore.removeChangeListener(focusChanged);
},
onMessageClick(msg: any, channel, event) {
onMessageClick(msg, channel, event) {
const isMe = msg.author.id === UserStore.getCurrentUser().id;
if (!isDeletePressed) {
if (event.detail < 2) return;
@@ -89,8 +90,7 @@ export default definePlugin({
} else {
if (!settings.store.enableDoubleClickToReply) return;
const EPHEMERAL = 64;
if (msg.hasFlag(EPHEMERAL)) return;
if (!MessageTypeSets.REPLYABLE.has(msg.type) || msg.hasFlag(MessageFlags.EPHEMERAL)) return;
const isShiftPress = event.shiftKey && !settings.store.requireModifier;
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 { Message } from "@vencord/discord-types";
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 Fill = [FillValue, FillValue, FillValue];
@@ -48,6 +48,11 @@ export default definePlugin({
type: OptionType.BOOLEAN,
description: "Show milliseconds",
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) {
const { latency, detectDiscordKotlin, showMillis } = this.settings.store;
const { latency, detectDiscordKotlin, showMillis, ignoreSelf } = this.settings.store;
const { id, nonce } = message;
// 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
if (message.author.bot) return null;
if (ignoreSelf && message.author.id === AuthenticationStore.getId()) return null;
let isDiscordKotlin = false;
let delta = SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce); // milliseconds
if (!showMillis) {

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