diff --git a/src/plugins/messageClickActions/ReactEmojiSetting.tsx b/src/plugins/messageClickActions/ReactEmojiSetting.tsx index 44f78ede7..d3a36053f 100644 --- a/src/plugins/messageClickActions/ReactEmojiSetting.tsx +++ b/src/plugins/messageClickActions/ReactEmojiSetting.tsx @@ -7,50 +7,146 @@ import "./styles.css"; import { Heading } from "@components/Heading"; +import { classNameFactory } from "@utils/css"; import type { IPluginOptionComponentProps } from "@utils/types"; +import type { Channel } from "@vencord/discord-types"; import { findComponentByCodeLazy } from "@webpack"; -import { TextInput, useState } from "@webpack/common"; +import { ChannelStore, IconUtils, Popout, SelectedChannelStore, TextInput, useRef, useState, useStateFromStores } from "@webpack/common"; import { settings } from "."; -type EmojiData = { - id?: string | null; - name: string; -}; +export const MAX_ADDITIONAL_REACT_EMOJIS = 8; +const cl = classNameFactory("vc-message-click-actions-"); type EmojiSelectPayload = { - id: string | null; - name: string; + id?: string | null; + name?: string | null; + optionallyDiverseSequence?: string; animated?: boolean; }; -const StatusEmojiPicker = findComponentByCodeLazy("setCustomStatusEmoji", "setIsEmojiPickerOpen", "selectedDefaultStatus"); -export const MAX_ADDITIONAL_REACT_EMOJIS = 8; +type ReactionEmojiPickerProps = { + channel?: Channel | null; + closePopout(): void; + onSelectEmoji(selection: { + emoji: EmojiSelectPayload | null; + willClose: boolean; + }): void; +}; -function getEmojiValue(emoji: EmojiData) { - return emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name; +const ReactionEmojiPicker = findComponentByCodeLazy( + "showAddEmojiButton:", + "pickerIntention:", + "messageId:" +); + +function parseCustomEmoji(value: string) { + return value.match(/^(?:<(?:(a):)?|:)?([\w-]+?)(?:~\d+)?:([0-9]+)>?$/); } -function parseEmojiValue(value: string): EmojiSelectPayload | null { +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() ?? ""; +} + +function toRenderedEmoji(value: string) { const trimmed = value.trim(); if (!trimmed) return null; - const customEmoji = trimmed.match(/^:?([\w-]+):(\d+)$/); + const customEmoji = parseCustomEmoji(trimmed); if (customEmoji) { return { - id: customEmoji[2], - name: customEmoji[1], - animated: false + kind: "custom" as const, + id: customEmoji[3], + name: customEmoji[2], + animated: customEmoji[1] === "a" }; } return { - id: null, + kind: "unicode" as const, 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 @@ -60,37 +156,47 @@ function parseEmojiList(value: string) { )).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 }: IPluginOptionComponentProps) { const [emoji, setEmoji] = useState(settings.store.reactEmoji ?? "💀"); - const [isEmojiPickerOpen, setIsEmojiPickerOpen] = useState(false); return (
Select Emoji For Reactions -
event.stopPropagation()} - onMouseDown={event => event.stopPropagation()} - > - { - if (!selectedEmoji) { - setEmoji(""); - setValue(""); - setIsEmojiPickerOpen(false); - return; - } - - const value = getEmojiValue(selectedEmoji); - setEmoji(value); - setValue(value); - setIsEmojiPickerOpen(false); +
+ { + setEmoji(newValue); + setValue(newValue); }} - selectedDefaultStatus={isEmojiPickerOpen ? null : "online"} - setIsEmojiPickerOpen={setIsEmojiPickerOpen} - defaultStatusVariant="small" - /> + > + +
); @@ -99,8 +205,6 @@ export function ReactEmojiSetting({ setValue }: IPluginOptionComponentProps) { export function AdditionalReactEmojisSetting({ setValue }: IPluginOptionComponentProps) { const { addAdditionalReacts } = settings.use(["addAdditionalReacts"]); const [emojiList, setEmojiList] = useState(settings.store.additionalReactEmojis ?? ""); - const [isEmojiPickerOpen, setIsEmojiPickerOpen] = useState(false); - const [pickerEmoji, setPickerEmoji] = useState(parseEmojiValue("😀")); if (!addAdditionalReacts) return null; @@ -122,8 +226,6 @@ export function AdditionalReactEmojisSetting({ setValue }: IPluginOptionComponen onChange={newValue => { setEmojiList(newValue); setValue(newValue); - const [first] = parseEmojiList(newValue); - setPickerEmoji(parseEmojiValue(first ?? "😀")); }} onClick={event => event.stopPropagation()} onMouseDown={event => event.stopPropagation()} @@ -131,39 +233,13 @@ export function AdditionalReactEmojisSetting({ setValue }: IPluginOptionComponen flex: 1 }} /> -
event.stopPropagation()} - onMouseDown={event => event.stopPropagation()} - style={{ - display: "inline-flex", - transform: "scale(1.2)", - transformOrigin: "center" + { + const nextValue = addEmojiToList(emojiList, newValue); + setEmojiList(nextValue); + setValue(nextValue); }} - > - { - if (!selectedEmoji) { - setIsEmojiPickerOpen(false); - return; - } - - const value = getEmojiValue(selectedEmoji); - const parsed = parseEmojiList(emojiList); - const merged = parsed.includes(value) - ? parsed - : [...parsed, value].slice(0, MAX_ADDITIONAL_REACT_EMOJIS); - const nextValue = merged.join(", "); - setEmojiList(nextValue); - setValue(nextValue); - setPickerEmoji(selectedEmoji); - setIsEmojiPickerOpen(false); - }} - selectedDefaultStatus={isEmojiPickerOpen ? null : "online"} - setIsEmojiPickerOpen={setIsEmojiPickerOpen} - defaultStatusVariant="small" - /> -
+ />
); diff --git a/src/plugins/messageClickActions/index.ts b/src/plugins/messageClickActions/index.ts index 4f7968494..42fd83fef 100644 --- a/src/plugins/messageClickActions/index.ts +++ b/src/plugins/messageClickActions/index.ts @@ -287,9 +287,9 @@ function normalizeEmoji(emoji: string): string | null { const trimmed = emoji.trim(); if (!trimmed) return null; - const customMatch = trimmed.match(/^:?([\w-]+):(\d+)$/); + const customMatch = trimmed.match(/^(?:<(?:(a):)?|:)?([\w-]+?)(?:~\d+)?:([0-9]+)>?$/); if (customMatch) { - return `${customMatch[1]}:${customMatch[2]}`; + return `${customMatch[2]}:${customMatch[3]}`; } return trimmed; diff --git a/src/plugins/messageClickActions/styles.css b/src/plugins/messageClickActions/styles.css index dc8358c3f..f8bad39f2 100644 --- a/src/plugins/messageClickActions/styles.css +++ b/src/plugins/messageClickActions/styles.css @@ -1,7 +1,22 @@ -.vc-message-click-actions-primary-emoji-picker { - margin-top: 8px; - margin-left: 6px; +.vc-message-click-actions-emoji-trigger { + min-width: 46px; + height: 46px; + padding: 0 8px; display: inline-flex; - transform: scale(1.3); - transform-origin: top left; + align-items: center; + justify-content: center; + border-radius: 8px; + background: var(--background-secondary-alt); + border: 1px solid var(--input-border); + cursor: pointer; + line-height: 1; +} + +.vc-message-click-actions-emoji-preview { + display: block; +} + +.vc-message-click-actions-unicode-preview { + font-size: 28px; + line-height: 1; }