diff --git a/src/api/HeaderBar.tsx b/src/api/HeaderBar.tsx index 3b2aa3447..161904c9b 100644 --- a/src/api/HeaderBar.tsx +++ b/src/api/HeaderBar.tsx @@ -13,7 +13,7 @@ import type { ComponentType, JSX, MouseEventHandler, ReactNode } from "react"; const logger = new Logger("HeaderBarAPI"); -const HeaderBarClasses = findCssClassesLazy("clickable", "withHighlight"); +const HeaderBarClasses = findCssClassesLazy("clickable", "selected", "badge", "badgeContainer"); const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '"aria-haspopup":') as ComponentType; export interface HeaderBarButtonProps { @@ -101,7 +101,7 @@ export function HeaderBarButton(props: HeaderBarButtonProps & { ref?: React.RefO {({ onMouseEnter, onMouseLeave }) => ( React.ReactNode; + className?: string; + initialExpanded?: boolean; +}>; + +/** + * A card component that can expand and collapse to show/hide content. The header (props.children) is always visible, and the content (props.renderContent) is only visible when expanded. + */ +export function ExpandableSection({ children, renderContent: Content, className, initialExpanded = false }: ExpandableSectionProps) { + const [expanded, setExpanded] = useState(initialExpanded); + + const Icon = expanded ? DownArrow : RightArrow; + + return ( + + setExpanded(c => !c)} > + {children} + + + + {expanded + ?
+ +
+ : null + } +
+ ); +} diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index 835ab7373..16f1e2bcf 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -714,7 +714,7 @@ export function CloudUploadIcon(props: IconProps) { ); } -export const ClockIcon = (props?: any) => { +export function ClockIcon(props: IconProps) { return ( { /> ); -}; +} + +export function DownArrow(props: IconProps) { + return ( + + + + ); +} + +export function RightArrow(props: IconProps) { + return ( + + + + ); +} export const QrCodeIcon = (props?: any) => { return ( diff --git a/src/equicordplugins/_api/headerBar.ts b/src/equicordplugins/_api/headerBar.ts index ff05d2ee6..e9f50be8f 100644 --- a/src/equicordplugins/_api/headerBar.ts +++ b/src/equicordplugins/_api/headerBar.ts @@ -16,7 +16,7 @@ export default definePlugin({ { find: '?"BACK_FORWARD_NAVIGATION":', replacement: { - match: /(?<="HELP"===.{0,150}className:\i\.\i\}\))(?=\])/, + match: /(?<="HELP"===.{0,75}\{\}\))(?=\])/, replace: ",...Vencord.Api.HeaderBar._addHeaderBarButtons()" } }, diff --git a/src/equicordplugins/equicordHelper/index.tsx b/src/equicordplugins/equicordHelper/index.tsx index 54b79e9e1..94a043fcb 100644 --- a/src/equicordplugins/equicordHelper/index.tsx +++ b/src/equicordplugins/equicordHelper/index.tsx @@ -206,7 +206,7 @@ export default definePlugin({ }, // Force Role Icon { - find: "Message Username", + find: "#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}", predicate: () => settings.store.forceRoleIcon, replacement: { match: /(?<=\}\):null\].{0,150}\?2:)0(?=\})/, diff --git a/src/equicordplugins/musicControls/settings.tsx b/src/equicordplugins/musicControls/settings.tsx index 299f8b7d8..b3bda9c26 100644 --- a/src/equicordplugins/musicControls/settings.tsx +++ b/src/equicordplugins/musicControls/settings.tsx @@ -170,7 +170,9 @@ export const settings = definePluginSettings({ component: () => (
Youtube Music - Pear Desktop is required for these settings + + Install Pear Desktop from here, then go to Plugins → API Server → Enabled +
) }, diff --git a/src/equicordplugins/questify/index.tsx b/src/equicordplugins/questify/index.tsx index ba25393b2..9a1f9c544 100644 --- a/src/equicordplugins/questify/index.tsx +++ b/src/equicordplugins/questify/index.tsx @@ -1388,8 +1388,8 @@ export default definePlugin({ }, { // Prevent SearchableSelect from force-scrolling into view, causing the dropdown to close. - match: /(?<=\.scrollIntoView\()/, - replace: "{block:\"nearest\",inline:\"nearest\"}" + match: /(&&\i.current\?\.scrollIntoView\(\))/, + replace: "" }, { // Passes a popoutClassName and optionClassName to the popout handler. @@ -1419,6 +1419,14 @@ export default definePlugin({ } ] }, + { + // Prevent the new version of SearchableSelect from force-scrolling into view. + find: '"data-mana-component":"combobox",', + replacement: { + match: /\i.current\?\.scrollIntoView\({.{0,50}?}\)/, + replace: "" + } + }, { // Formats the Orbs balance on the Quests page with locale string formatting. find: '("BalanceCounter")', diff --git a/src/equicordplugins/questify/utils/misc.tsx b/src/equicordplugins/questify/utils/misc.tsx index df2e3bec5..3434629e0 100644 --- a/src/equicordplugins/questify/utils/misc.tsx +++ b/src/equicordplugins/questify/utils/misc.tsx @@ -77,7 +77,7 @@ export function getQuestProgress(quest: Quest, task: QuestTask) { export function getQuestTarget(task: QuestTask) { const isWatch = task.type === QuestTaskType.WATCH_VIDEO || task.type === QuestTaskType.WATCH_VIDEO_ON_MOBILE; const raw = task.target; - const adjusted = raw - (isWatch ? videoQuestLeeway : 0); + const adjusted = Math.max(0, raw - (isWatch ? videoQuestLeeway : 0)); return { raw, adjusted }; } diff --git a/src/equicordplugins/timezones/index.tsx b/src/equicordplugins/timezones/index.tsx index ae5d08995..b40c3a515 100644 --- a/src/equicordplugins/timezones/index.tsx +++ b/src/equicordplugins/timezones/index.tsx @@ -271,7 +271,7 @@ export default definePlugin({ } }, { - find: '"Message Username"', + find: "#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}", replacement: { // thanks https://github.com/Syncxv/vc-timezones/pull/4 match: /(?<=isVisibleOnlyOnHover.+?)id:.{1,11},timestamp.{1,50}}\),/, diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index cd366010c..53b69c0c1 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -138,7 +138,7 @@ export default definePlugin({ replace: "...$1.props,$&" }, { - match: /(?<=forceOpen:.{0,40}?\i\((\i)\.id\).{0,100}?)children:/, + match: /(?<=__unsupportedReactNodeAsText:\i,forceOpen:.{0,70}?)children:(?=.{0,5}\$\{(\i)\.id\})/, replace: "children:$1.component?$self.renderBadgeComponent({...$1}) :" }, // handle onClick and onContextMenu diff --git a/src/plugins/_api/messageDecorations/index.tsx b/src/plugins/_api/messageDecorations/index.tsx index 590e50bd7..c21e691d1 100644 --- a/src/plugins/_api/messageDecorations/index.tsx +++ b/src/plugins/_api/messageDecorations/index.tsx @@ -30,7 +30,7 @@ export default definePlugin({ patches: [ { - find: '"Message Username"', + find: "#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}", replacement: { match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?renderPopout:.+?(?=\])/, replace: "$&,Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])" diff --git a/src/plugins/customCommands/CreateTagModal.tsx b/src/plugins/customCommands/CreateTagModal.tsx new file mode 100644 index 000000000..652f18503 --- /dev/null +++ b/src/plugins/customCommands/CreateTagModal.tsx @@ -0,0 +1,133 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2026 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { BaseText } from "@components/BaseText"; +import { Button } from "@components/Button"; +import { Card } from "@components/Card"; +import { InlineCode } from "@components/CodeBlock"; +import { ExpandableSection } from "@components/ExpandableCard"; +import { Flex } from "@components/Flex"; +import { HeadingSecondary } from "@components/Heading"; +import { InfoIcon } from "@components/Icons"; +import { Margins } from "@components/margins"; +import { Paragraph } from "@components/Paragraph"; +import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { TextArea, TextInput, useState } from "@webpack/common"; + +import { parseTagArguments } from "."; +import { addTag, getTag, Tag } from "./settings"; + +export function openCreateTagModal(initialValue: Tag = { name: "", message: "" }) { + openModal(modalProps => ( + + )); +} + +const EXAMPLE_RESPONSE = "Hello {{user}}! I am feeling {{mood = great}}."; + +function Modal({ initialValue, modalProps }: { initialValue: Tag; modalProps: ModalProps; }) { + const [name, setName] = useState(initialValue.name); + const [message, setMessage] = useState(initialValue.message.replaceAll("\\n", "\n")); + + const detectedArguments = parseTagArguments(message); + const hasReservedEphemeral = detectedArguments.some(arg => arg.name === "ephemeral"); + const nameAlreadyExists = name !== initialValue.name && getTag(name); + + return ( + + + Create Tag + + + + + + Create a new tag which will be registered as a slash command. + +
+ Name + +
+ +
+ Response +