diff --git a/src/plugins/showMeYourName/index.tsx b/src/plugins/showMeYourName/index.tsx index b3dd75307..a81010ac9 100644 --- a/src/plugins/showMeYourName/index.tsx +++ b/src/plugins/showMeYourName/index.tsx @@ -13,6 +13,7 @@ import { definePluginSettings } from "@api/Settings"; import { Button, TextButton } from "@components/Button"; import ErrorBoundary from "@components/ErrorBoundary"; import { Heading } from "@components/Heading"; +import ircColors from "@plugins/ircColors"; import mentionAvatars from "@plugins/mentionAvatars"; import { Devs, EquicordDevs } from "@utils/constants"; import { classNameFactory, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/index"; @@ -61,6 +62,48 @@ function adjustHex(color: string, percent: number): string { return `#${newColor}`; } +function normalizeHexColor(color: string): string | null { + const trimmed = color.trim(); + + if (/^#[\da-f]{3}$/iu.test(trimmed)) { + return `#${trimmed.slice(1).split("").map(c => c + c).join("")}`; + } + + if (/^#[\da-f]{6}$/iu.test(trimmed)) { + return trimmed; + } + + return null; +} + +function cssColorToHex(color: string): string | null { + const colorTestDiv = document.createElement("div"); + colorTestDiv.style.color = color; + document.body.appendChild(colorTestDiv); + + const computedColor = getComputedStyle(colorTestDiv).color; + colorTestDiv.remove(); + + const match = computedColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i); + if (!match) return null; + + const r = Math.max(0, Math.min(255, parseInt(match[1], 10))); + const g = Math.max(0, Math.min(255, parseInt(match[2], 10))); + const b = Math.max(0, Math.min(255, parseInt(match[3], 10))); + + return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, "0")}`; +} + +function toHexColor(color: string | number | null | undefined): string | null { + if (!color) return null; + if (typeof color === "number") { return `#${color.toString(16).padStart(6, "0")}`; } + + const normalized = normalizeHexColor(color); + if (normalized) return normalized; + + return cssColorToHex(color); +} + function validColor(color: string) { const trimmedColor = color.trim(); @@ -83,7 +126,8 @@ function validColor(color: string) { } function resolveColor( - user: User | GuildMember, + colorStrings: colorStringsType, + displayNameStyles: { effectId: number; colors: number[]; } | null | undefined, savedColor: string, canUseGradient: boolean, inGuild: boolean, @@ -92,44 +136,26 @@ function resolveColor( if (!savedColor.trim()) { return null; } let gradient: any = null; - let primaryColor: any = savedColor; - let secondaryColor: any = savedColor; - let tertiaryColor: any = savedColor; - let primaryAdjusted: any = savedColor; - let secondaryAdjusted: any = savedColor; - let tertiaryAdjusted: any = savedColor; + let primaryColor: any = null; + let secondaryColor: any = null; + let tertiaryColor: any = null; + let primaryAdjusted: any = null; + let secondaryAdjusted: any = null; + let tertiaryAdjusted: any = null; if (savedColor.toLowerCase().includes("role")) { const percentage = roleColorPattern.exec(savedColor)?.[1] || ""; if (percentage && isNaN(parseInt(percentage))) return null; - const colorStrings = (user as any)?.colorStrings || {}; - const displayStyles = !inGuild ? (user as any)?.displayNameStyles || {} : {}; + primaryColor = forceWhite ? "#ffffff" : (toHexColor(colorStrings?.primaryColor) || (!inGuild && toHexColor(displayNameStyles?.colors?.[0])) || "#ffffff"); + secondaryColor = forceWhite ? null : (toHexColor(colorStrings?.secondaryColor) || (!inGuild && toHexColor(displayNameStyles?.colors?.[1])) || null); + tertiaryColor = forceWhite ? null : (toHexColor(colorStrings?.tertiaryColor) || (!inGuild && toHexColor(displayNameStyles?.colors?.[2])) || null); - primaryColor = forceWhite ? "#ffffff" : colorStrings.primaryColor || displayStyles?.colors?.[0] || "#ffffff"; - if (typeof primaryColor === "number") primaryColor = `#${primaryColor.toString(16).padStart(6, "0")}`; - - secondaryColor = forceWhite ? null : colorStrings.secondaryColor || displayStyles?.colors?.[1] || null; - if (typeof secondaryColor === "number") secondaryColor = `#${secondaryColor.toString(16).padStart(6, "0")}`; - - tertiaryColor = forceWhite ? null : colorStrings.tertiaryColor || displayStyles?.colors?.[2] || null; - if (typeof tertiaryColor === "number") tertiaryColor = `#${tertiaryColor.toString(16).padStart(6, "0")}`; - - primaryAdjusted = primaryColor; - secondaryAdjusted = secondaryColor; - tertiaryAdjusted = tertiaryColor; - - if (primaryColor && percentage) { - primaryAdjusted = adjustHex(primaryColor, parseInt(percentage)); - } - - if (secondaryColor && percentage) { - secondaryAdjusted = adjustHex(secondaryColor, parseInt(percentage)); - } - - if (tertiaryColor && percentage) { - tertiaryAdjusted = adjustHex(tertiaryColor, parseInt(percentage)); - } + primaryAdjusted = percentage ? adjustHex(primaryColor, parseInt(percentage)) : primaryColor; + secondaryAdjusted = secondaryColor && percentage ? adjustHex(secondaryColor, parseInt(percentage)) : secondaryColor; + tertiaryAdjusted = tertiaryColor && percentage ? adjustHex(tertiaryColor, parseInt(percentage)) : tertiaryColor; + } else { + primaryColor = savedColor; } gradient = !canUseGradient || !secondaryColor || forceWhite @@ -164,7 +190,7 @@ function resolveColor( "--custom-gradient-color-2": secondaryColor || primaryColor, "--custom-gradient-color-3": tertiaryColor || primaryColor, "background-image": gradient, - "animation": "smyn-animation 1.5s linear infinite" + "animation": "smyn-animation var(--smyn-gradient-duration) linear infinite" }, static: { original: { @@ -300,6 +326,8 @@ interface mentionProps { interface messageProps { message: Message | null | undefined; + colorString?: string; + colorStrings: colorStringsType; userOverride?: User; isRepliedMessage?: boolean; withMentionPrefix?: boolean; @@ -312,6 +340,8 @@ interface memberListProfileReactionProps { tags?: any; } +type colorStringsType = { primaryColor: string | null, secondaryColor: string | null, tertiaryColor: string | null; } | null | undefined; + function getMemberListProfilesReactionsVoiceName( props: memberListProfileReactionProps, ): [string | null, JSX.Element | null, string | null] { @@ -343,7 +373,7 @@ function getMessageName(props: messageProps): [string | null, JSX.Element | null const member = isWebhook ? null : target && channel ? GuildMemberStore.getMember(channel.guild_id, target.id) : null; const author = user && member ? { ...user, ...member } : user || member || null; const mentionSymbol = hideDefaultAtSign && (!isRepliedMessage || replies) ? "" : withMentionPrefix ? "@" : ""; - return renderUsername(author, channel?.id || null, message?.id || null, isRepliedMessage ? "replies" : "messages", mentionSymbol, false, !!channel?.guild_id); + return renderUsername(author, channel?.id || null, message?.id || null, isRepliedMessage ? "replies" : "messages", mentionSymbol, false, !!channel?.guild_id, props.colorString, props.colorStrings); } function getMessageNameElement(props: messageProps): JSX.Element | null { @@ -362,7 +392,20 @@ function getMentionNameElement(props: mentionProps): JSX.Element | null { const member = channel ? GuildMemberStore.getMember(channel.guild_id, userId) : null; const author = user && member ? { ...user, ...member } : user || member || null; const mentionSymbol = hideDefaultAtSign && mentions ? "" : "@"; - return renderUsername(author, channelId || null, nestedProps?.messageId || null, "mentions", mentionSymbol, false, !!channel?.guild_id)[1]; + + let colorString: string | undefined = undefined; + let colorStrings: colorStringsType = undefined; + + if (isPluginEnabled(ircColors.name)) { + const color = ircColors.calculateNameColorForMessageContext({ message: { author: { id: userId } }, channel }); + + if (color) { + colorString = color; + colorStrings = { primaryColor: color, secondaryColor: null, tertiaryColor: null }; + } + } + + return renderUsername(author, channelId || null, nestedProps?.messageId || null, "mentions", mentionSymbol, false, !!channel?.guild_id, colorString, colorStrings)[1]; } function getEffectType(effectId: number | null | undefined): string | null { @@ -408,7 +451,9 @@ function renderUsername( type: "messages" | "replies" | "mentions" | "membersList" | "profilesPopout" | "profilesTooltip" | "reactionsTooltip" | "reactionsPopout" | "voiceChannel", mentionSymbol: string, hookless: boolean, - inGuild: boolean + inGuild: boolean, + colorString?: string, + colorStrings?: { primaryColor: string | null, secondaryColor: string | null, tertiaryColor: string | null; } | null ): [string | null, JSX.Element | null, string | null] { const isMessage = type === "messages"; const isReply = type === "replies"; @@ -435,27 +480,38 @@ function renderUsername( ? hoveringReactionPopoutSet.has((author as User).id) : false; - const authorDisplayNameStyles = (!inGuild && (author as any)?.displayNameStyles) || null; + if (colorString && !colorStrings) { + colorStrings = { + primaryColor: colorString, + secondaryColor: null, + tertiaryColor: null + }; + } + + const ircColorsEnabled = isPluginEnabled(ircColors.name); + + const authorColorStrings = colorStrings || (author as any)?.colorStrings || null; + const authorDisplayNameStyles = (!inGuild && !ircColorsEnabled && (author as any)?.displayNameStyles) || null; const effectType = authorDisplayNameStyles ? getEffectType(authorDisplayNameStyles.effectId) : null; const effectCSSVars = authorDisplayNameStyles ? computeEffectCSSVars(authorDisplayNameStyles) : {}; const hasEffect = !!effectType; const needsEffectDataAttr = effectType === "neon" || effectType === "toon" || effectType === "pop"; const shouldShowEffect = hasEffect && isHovering; const shouldAnimateEffect = shouldShowEffect && !AccessibilityStore.useReducedMotion; - const shouldUseDMDefault = !inGuild && !isHovering; + const shouldUseDMDefault = !inGuild && !ircColorsEnabled && !isHovering; const canUseGradient = ((author as GuildMember)?.guildId ? (GuildStore.getGuild((author as GuildMember).guildId) ?? {}).premiumFeatures?.features.includes("ENHANCED_ROLE_COLORS") : !inGuild); const useTopRoleStyle = isMention || isReactionsPopout || channel?.isDM() || channel?.isGroupDM(); - const topRoleStyle = author ? resolveColor(author, "Role", canUseGradient, inGuild, shouldUseDMDefault) : null; + const topRoleStyle = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, "Role", canUseGradient, inGuild, shouldUseDMDefault) : null; const hasGradient = !!topRoleStyle?.gradient && Object.keys(topRoleStyle.gradient).length > 0; const textMutedValue = getComputedStyle(document.documentElement)?.getPropertyValue("--text-muted")?.trim() || "#72767d"; const options = splitTemplate(includedNames); - const resolvedUsernameColor = author ? resolveColor(author, usernameColor.trim(), canUseGradient, inGuild, shouldUseDMDefault) : null; - const resolvedDisplayNameColor = author ? resolveColor(author, displayNameColor.trim(), canUseGradient, inGuild, shouldUseDMDefault) : null; - const resolvedNicknameColor = author ? resolveColor(author, nicknameColor.trim(), canUseGradient, inGuild, shouldUseDMDefault) : null; - const resolvedFriendNameColor = author ? resolveColor(author, friendNameColor.trim(), canUseGradient, inGuild, shouldUseDMDefault) : null; - const resolvedCustomNameColor = author ? resolveColor(author, customNameColor.trim(), canUseGradient, inGuild, shouldUseDMDefault) : null; + const resolvedUsernameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, usernameColor.trim(), canUseGradient, inGuild, shouldUseDMDefault) : null; + const resolvedDisplayNameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, displayNameColor.trim(), canUseGradient, inGuild, shouldUseDMDefault) : null; + const resolvedNicknameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, nicknameColor.trim(), canUseGradient, inGuild, shouldUseDMDefault) : null; + const resolvedFriendNameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, friendNameColor.trim(), canUseGradient, inGuild, shouldUseDMDefault) : null; + const resolvedCustomNameColor = author ? resolveColor(authorColorStrings, authorDisplayNameStyles, customNameColor.trim(), canUseGradient, inGuild, shouldUseDMDefault) : null; const affixColor = { color: textMutedValue, "-webkit-text-fill-color": textMutedValue, isolation: "isolate", "white-space": "pre", "font-family": "var(--font-primary)", "letter-spacing": "normal" }; const { username, display, nick, friend, custom } = getProcessedNames(author, truncateAllNamesWithStreamerMode, discriminators, inGuild, friendNameOnlyInDirectMessages, customNameOnlyInDirectMessages); @@ -580,6 +636,7 @@ function renderUsername( } const remainingNames = [first, second, third, fourth, fifth].filter(Boolean); + first = remainingNames.shift(); second = remainingNames.shift(); third = remainingNames.shift(); @@ -591,37 +648,40 @@ function renderUsername( const shouldAnimateSecondaryNames = animateGradients && !ignoreGradients; const firstDataText = mentionSymbol + first.name; - const secondDataText = second && shouldAnimateSecondaryNames ? (second.prefix + second.name + second.suffix) : ""; - const thirdDataText = third && shouldAnimateSecondaryNames ? (third.prefix + third.name + third.suffix) : ""; - const fourthDataText = fourth && shouldAnimateSecondaryNames ? (fourth.prefix + fourth.name + fourth.suffix) : ""; - const fifthDataText = fifth && shouldAnimateSecondaryNames ? (fifth.prefix + fifth.name + fifth.suffix) : ""; + const secondDataText = second && shouldAnimateSecondaryNames ? second.name : ""; + const thirdDataText = third && shouldAnimateSecondaryNames ? third.name : ""; + const fourthDataText = fourth && shouldAnimateSecondaryNames ? fourth.name : ""; + const fifthDataText = fifth && shouldAnimateSecondaryNames ? fifth.name : ""; const allDataText = [firstDataText, secondDataText, thirdDataText, fourthDataText, fifthDataText].filter(Boolean).join(nameSeparator).trim(); // Only mentions and reactions popouts should patch in the gradient glow or else a double glow will appear on messages. - const hoveringClass = (isHovering ? " show-me-your-name-gradient-hovered" : ""); + const hoveringClass = (isHovering ? " smyn-gradient-hovered" : ""); const gradientClasses = useTopRoleStyle - ? "show-me-your-name-gradient show-me-your-name-gradient-inherit-bg" + hoveringClass - : "show-me-your-name-gradient show-me-your-name-gradient-unset-bg" + hoveringClass; + ? "smyn-gradient smyn-gradient-inherit-bg" + hoveringClass + : "smyn-gradient smyn-gradient-unset-bg" + hoveringClass; - const firstGroupClasses = "show-me-your-name-name-group show-me-your-name-first-name-group"; - const secondGroupClasses = "show-me-your-name-name-group show-me-your-name-second-name-group"; - const thirdGroupClasses = "show-me-your-name-name-group show-me-your-name-third-name-group"; - const fourthGroupClasses = "show-me-your-name-name-group show-me-your-name-fourth-name-group"; - const fifthGroupClasses = "show-me-your-name-name-group show-me-your-name-fifth-name-group"; - const firstNameClasses = "show-me-your-name-name show-me-your-name-first-name"; - const secondNameClasses = "show-me-your-name-name show-me-your-name-second-name"; - const thirdNameClasses = "show-me-your-name-name show-me-your-name-third-name"; - const fourthNameClasses = "show-me-your-name-name show-me-your-name-fourth-name"; - const fifthNameClasses = "show-me-your-name-name show-me-your-name-fifth-name"; - const prefixClasses = "show-me-your-name-affix show-me-your-name-prefix"; - const suffixClasses = "show-me-your-name-affix show-me-your-name-suffix"; + const firstGroupClasses = "smyn-name-group smyn-first-name-group"; + const secondGroupClasses = "smyn-name-group smyn-second-name-group"; + const thirdGroupClasses = "smyn-name-group smyn-third-name-group"; + const fourthGroupClasses = "smyn-name-group smyn-fourth-name-group"; + const fifthGroupClasses = "smyn-name-group smyn-fifth-name-group"; + const firstNameClasses = "smyn-name smyn-first-name"; + const secondNameClasses = "smyn-name smyn-second-name"; + const thirdNameClasses = "smyn-name smyn-third-name"; + const fourthNameClasses = "smyn-name smyn-fourth-name"; + const fifthNameClasses = "smyn-name smyn-fifth-name"; + const prefixClasses = "smyn-affix smyn-prefix"; + const suffixClasses = "smyn-affix smyn-suffix"; + + const animationDuration = Math.max(1, 1.5 * (first.name.length / 12)); const topLevelStyle = { // Allows names to wrap in reaction popouts. ...(isReactionsPopout ? { display: "flex", flexWrap: "wrap", lineHeight: "1.1em", fontSize: "0.9em" } : {}), - ...(hasEffect ? effectCSSVars : {}) + ...(hasEffect ? effectCSSVars : {}), + "--smyn-gradient-duration": `${animationDuration}s` } as React.CSSProperties; const nameElement = ( @@ -630,7 +690,7 @@ function renderUsername( ...topLevelStyle, ...(topRoleStyle?.normal.original || {}) }} - className="show-me-your-name-container" + className="smyn-container" > {mentionSymbol && {mentionSymbol}} {( @@ -667,9 +727,7 @@ function renderUsername( ].map(({ name, dataText, groupClass, nameClass, position }) => name && ( {nameSeparator} @@ -677,10 +735,12 @@ function renderUsername( { setValue(""); delete customNicknames[user.id]; @@ -826,7 +886,7 @@ function CustomNicknameModal({ modalProps, user }: { modalProps: ModalProps; use
- +