/* * Vencord, a Discord client mod * Copyright (c) 2026 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import "./styles.css"; import { Heading } from "@components/Heading"; import { classNameFactory } from "@utils/css"; import type { PluginSettingComponentProps } from "@utils/types"; import type { Channel } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; import { ChannelStore, IconUtils, Popout, SelectedChannelStore, TextInput, useRef, useState, useStateFromStores } from "@webpack/common"; import { settings } from "."; export const MAX_ADDITIONAL_REACT_EMOJIS = 8; const cl = classNameFactory("vc-message-click-actions-"); type EmojiSelectPayload = { id?: string | null; name?: string | null; optionallyDiverseSequence?: string; animated?: boolean; }; type ReactionEmojiPickerProps = { channel?: Channel | null; closePopout(): void; onSelectEmoji(selection: { emoji: EmojiSelectPayload | null; willClose: boolean; }): void; }; const ReactionEmojiPicker = findComponentByCodeLazy( "showAddEmojiButton:", "pickerIntention:", "messageId:" ); function parseCustomEmoji(value: string) { return value.match(/^(?:<(?:(a):)?|:)?([\w-]+?)(?:~\d+)?:([0-9]+)>?$/); } function getEmojiValue(emoji: EmojiSelectPayload | null | undefined) { if (!emoji) return ""; if (emoji.id && emoji.name) return `${emoji.name}:${emoji.id}`; if (emoji.optionallyDiverseSequence?.trim()) return emoji.optionallyDiverseSequence; return emoji.name?.trim() ?? ""; } type RenderedEmoji = | { kind: "custom"; id: string; name: string; animated: boolean; } | { kind: "unicode"; name: string; animated: boolean; }; function toRenderedEmoji(value: string): RenderedEmoji | null { const trimmed = value.trim(); if (!trimmed) return null; const customEmoji = parseCustomEmoji(trimmed); if (customEmoji) { return { kind: "custom", id: customEmoji[3]!, name: customEmoji[2]!, animated: customEmoji[1] === "a" }; } return { kind: "unicode", name: trimmed, animated: false }; } function getCustomEmojiSources(id: string, animated: boolean) { const staticUrl = IconUtils.getEmojiURL({ id, animated: false, size: 48 }); if (!animated) return [staticUrl]; const animatedUrl = IconUtils.getEmojiURL({ id, animated: true, size: 48 }); return animatedUrl === staticUrl ? [staticUrl] : [animatedUrl, staticUrl]; } function CustomEmojiPreview({ id, name, animated }: { id: string; name: string; animated: boolean; }) { const sources = getCustomEmojiSources(id, animated); const [srcIndex, setSrcIndex] = useState(0); return ( {name} setSrcIndex(current => current < sources.length - 1 ? current + 1 : current)} /> ); } function EmojiPickerButton({ onSelect, children }: { onSelect(value: string): void; children?: React.ReactNode; }) { const triggerRef = useRef(null); const channel = useStateFromStores([SelectedChannelStore, ChannelStore], () => { const channelId = SelectedChannelStore.getChannelId(); return channelId ? ChannelStore.getChannel(channelId) : null; }); return ( ( { const nextValue = getEmojiValue(emoji); if (nextValue) onSelect(nextValue); if (willClose) closePopout(); }} /> )} > {popoutProps => (
{children ?? "Pick Emoji"}
)}
); } function parseEmojiList(value: string) { return Array.from(new Set( value .split(/[\n,]/g) .map(entry => entry.trim()) .filter(Boolean) )).slice(0, MAX_ADDITIONAL_REACT_EMOJIS); } function addEmojiToList(list: string, emoji: string) { const parsedList = parseEmojiList(list); if (parsedList.includes(emoji)) return parsedList.join(", "); return [...parsedList, emoji] .slice(0, MAX_ADDITIONAL_REACT_EMOJIS) .join(", "); } function EmojiPreview({ value }: { value: string; }) { const renderedEmoji = toRenderedEmoji(value); if (!renderedEmoji) return <>Pick Emoji; if (renderedEmoji.kind === "custom") { return ( ); } return {renderedEmoji.name}; } export function ReactEmojiSetting({ setValue }: PluginSettingComponentProps) { const [emoji, setEmoji] = useState(settings.store.reactEmoji ?? "💀"); return (
Select Emoji For Reactions
{ setEmoji(newValue); setValue(newValue); }} >
); } export function AdditionalReactEmojisSetting({ setValue }: PluginSettingComponentProps) { const { addAdditionalReacts } = settings.use(["addAdditionalReacts"]); const [emojiList, setEmojiList] = useState(settings.store.additionalReactEmojis ?? ""); if (!addAdditionalReacts) return null; return (
Select Additional Emojis
{ setEmojiList(newValue); setValue(newValue); }} onClick={event => event.stopPropagation()} onMouseDown={event => event.stopPropagation()} style={{ flex: 1 }} /> { const nextValue = addEmojiToList(emojiList, newValue); setEmojiList(nextValue); setValue(nextValue); }} />
); }