From 7a938c85f64b2a91e5c586b2bac35e621532492b Mon Sep 17 00:00:00 2001 From: Hampus Date: Mon, 24 Aug 2026 12:41:36 +0200 Subject: [PATCH] fix(app): stop rewriting message emoji urls to toggle animation (#1897) --- .../channel/components/MessageAttachments.tsx | 535 +++++++++--------- .../channel/components/ReplyPreview.tsx | 8 +- .../channel/components/UserMessage.tsx | 170 ++---- 3 files changed, 324 insertions(+), 389 deletions(-) diff --git a/fluxer_app/src/features/channel/components/MessageAttachments.tsx b/fluxer_app/src/features/channel/components/MessageAttachments.tsx index 6365c0cac..1df684bce 100644 --- a/fluxer_app/src/features/channel/components/MessageAttachments.tsx +++ b/fluxer_app/src/features/channel/components/MessageAttachments.tsx @@ -281,304 +281,295 @@ const ForwardedFromSource = observer(({message}: {message: Message}) => { interface ForwardedMessageContentProps { message: Message; snapshot: MessageSnapshot; - shouldAnimate: boolean; - interactionHandlers: Record; onDelete?: (bypassConfirm?: boolean) => void; } -export const ForwardedMessageContent = observer( - ({message, snapshot, shouldAnimate, interactionHandlers, onDelete}: ForwardedMessageContentProps) => { - const {i18n} = useLingui(); - const snapshotIndex = 0; - const snapshotEditedTimestamp = snapshot.edited_timestamp ? new Date(snapshot.edited_timestamp) : null; - const copyText = useMemo( - () => - buildMessageSnapshotCopyText(snapshot, { - channelId: message.channelId, - messageId: message.id, - i18n, - }), - [i18n.locale, message.channelId, message.id, snapshot], - ); - return ( +export const ForwardedMessageContent = observer(({message, snapshot, onDelete}: ForwardedMessageContentProps) => { + const {i18n} = useLingui(); + const snapshotIndex = 0; + const snapshotEditedTimestamp = snapshot.edited_timestamp ? new Date(snapshot.edited_timestamp) : null; + const copyText = useMemo( + () => + buildMessageSnapshotCopyText(snapshot, { + channelId: message.channelId, + messageId: message.id, + i18n, + }), + [i18n.locale, message.channelId, message.id, snapshot], + ); + return ( +
+
-
-
- - - Forwarded - -
- {snapshot.content && ( -
- - {snapshotEditedTimestamp && ( - - - {' '} - (edited) - - - )} -
- )} - {snapshot.attachments && snapshot.attachments.length > 0 && ( -
- {(() => { - const {enrichedAttachments, mediaAttachments, shouldUseMosaic} = getAttachmentRenderingState( - snapshot.attachments, - ); - return ( - <> - {shouldUseMosaic && ( - - )} - {enrichedAttachments.map((attachment: MessageAttachment) => ( - - ))} - - ); - })()} -
- )} - {snapshot.embeds && snapshot.embeds.length > 0 && UserSettings.getRenderEmbeds() && ( -
- {snapshot.embeds.map((embed: MessageEmbed, index: number) => { - const embedKey = `${embed.id}-${index}`; - return ( - - ); - })} -
- )} - {snapshot.stickers && snapshot.stickers.length > 0 && ( -
- {snapshot.stickers.map((sticker: MessageStickerItem) => ( - - ))} -
- )} - + + Forwarded +
+ {snapshot.content && ( +
+ + {snapshotEditedTimestamp && ( + + + {' '} + (edited) + + + )} +
+ )} + {snapshot.attachments && snapshot.attachments.length > 0 && ( +
+ {(() => { + const {enrichedAttachments, mediaAttachments, shouldUseMosaic} = getAttachmentRenderingState( + snapshot.attachments, + ); + return ( + <> + {shouldUseMosaic && ( + + )} + {enrichedAttachments.map((attachment: MessageAttachment) => ( + + ))} + + ); + })()} +
+ )} + {snapshot.embeds && snapshot.embeds.length > 0 && UserSettings.getRenderEmbeds() && ( +
+ {snapshot.embeds.map((embed: MessageEmbed, index: number) => { + const embedKey = `${embed.id}-${index}`; + return ( + + ); + })} +
+ )} + {snapshot.stickers && snapshot.stickers.length > 0 && ( +
+ {snapshot.stickers.map((sticker: MessageStickerItem) => ( + + ))} +
+ )} +
- ); - }, -); +
+ ); +}); interface StickerItemProps { sticker: MessageStickerItem; message: Message; - shouldAnimate: boolean; - interactionHandlers: Record; sourceChannel?: Channel | null; handleDelete?: (bypassConfirm?: boolean) => void; } -const StickerItem = observer( - ({sticker, message, shouldAnimate, interactionHandlers, sourceChannel, handleDelete}: StickerItemProps) => { - const stickerUrl = AvatarUtils.getStickerURL({ +const StickerItem = observer(({sticker, message, sourceChannel, handleDelete}: StickerItemProps) => { + const {shouldAnimate, interactionHandlers} = useStickerAnimation({isAnimated: sticker.animated}); + const stickerUrl = AvatarUtils.getStickerURL({ + id: sticker.id, + animated: shouldAnimate, + isAnimatable: sticker.animated, + size: 320, + }); + const stickerRecord = Sticker.getStickerById(sticker.id); + const guild = stickerRecord?.guildId ? Guilds.getGuild(stickerRecord.guildId) : null; + const isMature = !!stickerRecord?.nsfw || !!sticker.nsfw; + const {shouldBlur, shouldBlock, canReveal, reveal} = useMatureMedia(isMature, message.channelId); + const tooltipContent = () => ( +
+ + {sticker.name} + + {guild && ( +
+ + + {guild.name} + +
+ )} +
+ ); + const handleContextMenu = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + const stickerForMenu = stickerRecord ?? { id: sticker.id, - animated: shouldAnimate, - size: 320, - }); - const stickerRecord = Sticker.getStickerById(sticker.id); - const guild = stickerRecord?.guildId ? Guilds.getGuild(stickerRecord.guildId) : null; - const isMature = !!stickerRecord?.nsfw || !!sticker.nsfw; - const {shouldBlur, shouldBlock, canReveal, reveal} = useMatureMedia(isMature, message.channelId); - const tooltipContent = () => ( -
- - {sticker.name} - - {guild && ( -
- - - {guild.name} - -
- )} -
- ); - const handleContextMenu = (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - const stickerForMenu = stickerRecord ?? { - id: sticker.id, - guildId: '', - name: sticker.name, - description: '', - tags: [], - url: stickerUrl, - animated: sticker.animated, - nsfw: Boolean(sticker.nsfw), - user: undefined, - }; - ContextMenuCommands.openFromEvent(e, ({onClose}) => ( - - } - data-flx="channel.message-attachments.handle-context-menu.message-context-menu" - /> - )); + guildId: '', + name: sticker.name, + description: '', + tags: [], + url: stickerUrl, + animated: sticker.animated, + nsfw: Boolean(sticker.nsfw), + user: undefined, }; - const handleRevealClick = useCallback( - (e: React.MouseEvent) => { - if (shouldBlur && canReveal) { - e.preventDefault(); - e.stopPropagation(); - reveal(); + ContextMenuCommands.openFromEvent(e, ({onClose}) => ( + } - }, - [shouldBlur, canReveal, reveal], - ); - if (shouldBlock) { - return null; - } - const stickerImage = ( - {stickerRecord?.description - ); - return ( - - - - - - ); - }, -); + )); + }; + const handleRevealClick = useCallback( + (e: React.MouseEvent) => { + if (shouldBlur && canReveal) { + e.preventDefault(); + e.stopPropagation(); + reveal(); + } + }, + [shouldBlur, canReveal, reveal], + ); + if (shouldBlock) { + return null; + } + const stickerImage = ( + {stickerRecord?.description + ); + return ( + + + + + + ); +}); export const MessageAttachments = observer(() => { const {channel, message, handleDelete, previewContext, onPopoutToggle, suppressMessageActions} = useMessageViewContext(); const isPreview = Boolean(previewContext); const reactionsIsPreview = isPreview || Boolean(suppressMessageActions); const reactions = useMessageReactionsSnapshot(message.id); - const {shouldAnimate, interactionHandlers} = useStickerAnimation(); const spoileredUrlEmbeds = useMemo(() => { const embeddableCodeLinkContent = extractEmbeddableCodeLinkContent(message.content); return { @@ -593,8 +584,6 @@ export const MessageAttachments = observer(() => { @@ -648,8 +637,6 @@ export const MessageAttachments = observer(() => { key={sticker.id} sticker={sticker} message={message} - shouldAnimate={shouldAnimate} - interactionHandlers={interactionHandlers} sourceChannel={channel} handleDelete={handleDelete} data-flx="channel.message-attachments.sticker-item" diff --git a/fluxer_app/src/features/channel/components/ReplyPreview.tsx b/fluxer_app/src/features/channel/components/ReplyPreview.tsx index dc75dbee2..437e7d90e 100644 --- a/fluxer_app/src/features/channel/components/ReplyPreview.tsx +++ b/fluxer_app/src/features/channel/components/ReplyPreview.tsx @@ -47,13 +47,11 @@ export const ReplyPreview = observer( message, channelId, guildId, - animateEmoji, messageDisplayCompact, }: { message: Message; channelId: string; guildId?: string; - animateEmoji: boolean; messageDisplayCompact: boolean; }) => { const {i18n} = useLingui(); @@ -63,14 +61,15 @@ export const ReplyPreview = observer( ); const {isMessageRevealed} = useCollapsedMessageVisibility(); const resolvedGuildId = guildId ?? message.guildId ?? message.messageReference?.guild_id; + const referenceChannelId = message.messageReference?.channel_id ?? message.channelId; const jumpToRepliedMessage = useCallback(() => { if (message.messageReference?.message_id) { - goToMessage(message.channelId, message.messageReference.message_id, { + goToMessage(referenceChannelId, message.messageReference.message_id, { returnToMessageId: message.id, returnChannelId: message.channelId, }); } - }, [message.channelId, message.id, message.messageReference]); + }, [referenceChannelId, message.channelId, message.id, message.messageReference]); if (!message.messageReference) return null; if (resolution.state !== MessageReferenceState.LOADED) { const isDeleted = resolution.state === MessageReferenceState.DELETED; @@ -253,7 +252,6 @@ export const ReplyPreview = observer( context: MarkdownContext.RESTRICTED_INLINE_REPLY, messageId: referencedMessage.id, channelId, - disableAnimatedEmoji: !animateEmoji, mentionChannels: referencedMessage.mentionChannels, }} data-flx="channel.reply-preview.safe-markdown" diff --git a/fluxer_app/src/features/channel/components/UserMessage.tsx b/fluxer_app/src/features/channel/components/UserMessage.tsx index c2b43bedb..d7324998d 100644 --- a/fluxer_app/src/features/channel/components/UserMessage.tsx +++ b/fluxer_app/src/features/channel/components/UserMessage.tsx @@ -2,11 +2,6 @@ import Accessibility from '@app/features/accessibility/state/Accessibility'; import {SILENT_MENTION} from '@app/features/app/config/I18nDisplayConstants'; -import { - getAnimatedMediaPlaybackAllowed, - subscribeAnimatedMediaPlaybackChange, - useAnimatedMediaPlaybackAllowed, -} from '@app/features/app/hooks/useAnimatedMediaPlayback'; import {UserTag} from '@app/features/channel/components/ChannelUserTag'; import {CompactAuthorPrefix, CompactMessageLayout} from '@app/features/channel/components/CompactMessageLayout'; import {EditingMessageInput} from '@app/features/channel/components/EditingMessageInput'; @@ -14,7 +9,6 @@ import {isMediaOnlyEmbed} from '@app/features/channel/components/embeds/EmbedRen import {MessageAttachments} from '@app/features/channel/components/MessageAttachments'; import {MessageAuthorInfo} from '@app/features/channel/components/MessageAuthorInfo'; import {MessageAvatar} from '@app/features/channel/components/MessageAvatar'; -import {shouldAnimateMessageEmojiByDefault} from '@app/features/channel/components/MessageEmojiAnimationUtils'; import {MessageTimeoutIndicator} from '@app/features/channel/components/MessageTimeoutIndicator'; import {MessageUsername} from '@app/features/channel/components/MessageUsername'; import {useMessageViewContext} from '@app/features/channel/components/MessageViewContext'; @@ -55,9 +49,8 @@ import {msg} from '@lingui/core/macro'; import {Trans, useLingui} from '@lingui/react/macro'; import {ArrowsClockwiseIcon, BellSlashIcon, EyeIcon, WarningCircleIcon} from '@phosphor-icons/react'; import {clsx} from 'clsx'; -import {autorun} from 'mobx'; import {observer} from 'mobx-react-lite'; -import {type MouseEvent, useCallback, useEffect, useMemo, useState} from 'react'; +import {type MouseEvent, useCallback, useMemo} from 'react'; const JUMP_TO_MESSAGE_FROM_SENT_DESCRIPTOR = msg({ message: 'Jump to message from {displayName}, sent {formattedDate}', @@ -86,22 +79,6 @@ const MessageStateToClassName: Record = { [MessageStates.FAILED]: styles.messageFailed, }; const CUSTOM_EMOJI_MARKDOWN_PATTERN = //g; -const getDefaultMessageEmojiAnimationAllowed = (): boolean => - shouldAnimateMessageEmojiByDefault({ - animateEmojiSetting: UserSettings.getAnimateEmoji(), - animatedMediaPlaybackAllowed: getAnimatedMediaPlaybackAllowed(), - }); -const setMessageEmojiImagesAnimated = (messageId: string, shouldAnimate: boolean): void => { - const emojiImgs = document.querySelectorAll( - `img[data-message-id="${messageId}"][data-animated="true"]`, - ) as NodeListOf; - for (const img of emojiImgs) { - const url = new URL(img.src, window.location.origin); - url.searchParams.set('animated', shouldAnimate.toString()); - img.src = url.toString(); - } -}; - function messageContentCopyBlockProps(content: string): { 'data-message-copy-block'?: 'true'; 'data-message-copy-text'?: string; @@ -122,8 +99,6 @@ export const UserMessage = observer(() => { previewOverrides, onHeadingActivate, } = useMessageViewContext(); - const [animateEmoji, setAnimateEmoji] = useState(getDefaultMessageEmojiAnimationAllowed); - const animatedMediaPlaybackAllowed = useAnimatedMediaPlaybackAllowed(); const isEditing = MessageEdit.isEditing(message.channelId, message.id); const userAuthor = Users.getUser(message.author.id); const author = message.webhookId != null ? message.author : (userAuthor ?? message.author); @@ -155,6 +130,15 @@ export const UserMessage = observer(() => { }), [message.content], ); + const markdownOptions = useMemo( + () => ({ + context: MarkdownContext.STANDARD_WITH_JUMBO, + messageId: message.id, + channelId: message.channelId, + mentionChannels: message.mentionChannels, + }), + [message.id, message.channelId, message.mentionChannels], + ); const shouldHideContent = UserSettings.getRenderEmbeds() && message.embeds.length > 0 && @@ -312,12 +296,7 @@ export const UserMessage = observer(() => { > {(message.editedTimestamp || message.isEditing) && @@ -346,6 +325,7 @@ export const UserMessage = observer(() => { }, [ shouldShowEditingInput, shouldHideContent, + markdownOptions, message, message.content, message.id, @@ -357,25 +337,6 @@ export const UserMessage = observer(() => { onSubmit, i18n, ]); - useEffect(() => { - if (animateEmoji) return; - setMessageEmojiImagesAnimated(message.id, animatedMediaPlaybackAllowed && isHovering); - }, [animateEmoji, animatedMediaPlaybackAllowed, isHovering, message.id]); - useEffect(() => { - const updateDefaultEmojiAnimation = () => { - const shouldAnimate = getDefaultMessageEmojiAnimationAllowed(); - setAnimateEmoji(shouldAnimate); - setMessageEmojiImagesAnimated(message.id, shouldAnimate); - }; - const disposer = autorun(() => { - updateDefaultEmojiAnimation(); - }); - const unsubscribePlayback = subscribeAnimatedMediaPlaybackChange(updateDefaultEmojiAnimation); - return () => { - disposer(); - unsubscribePlayback(); - }; - }, [message.id]); const renderFailedFooter = useCallback(() => { if (!shouldShowFailedFooter) { return null; @@ -469,12 +430,7 @@ export const UserMessage = observer(() => { >
@@ -523,7 +479,6 @@ export const UserMessage = observer(() => { message={message} channelId={channel.id} guildId={channel.guildId} - animateEmoji={animateEmoji} messageDisplayCompact={messageDisplayCompact} data-flx="channel.user-message.reply-preview" /> @@ -559,37 +514,30 @@ export const UserMessage = observer(() => { > {showMetadata && compactAuthorPrefix} {!shouldHideContent && ( - <> - - {(message.editedTimestamp || message.isEditing) && - (message.isEditing ? ( - - {' '} - {i18n._(EDITED_DESCRIPTOR)} - - ) : ( - - - {' '} - {i18n._(EDITED_DESCRIPTOR)} - - - ))} - + )} + {(message.editedTimestamp || message.isEditing) && + (message.isEditing ? ( + + {' '} + {i18n._(EDITED_DESCRIPTOR)} + + ) : ( + + + {' '} + {i18n._(EDITED_DESCRIPTOR)} + + + ))}
) } @@ -608,7 +556,6 @@ export const UserMessage = observer(() => { message={message} channelId={channel.id} guildId={channel.guildId} - animateEmoji={animateEmoji} messageDisplayCompact={messageDisplayCompact} data-flx="channel.user-message.reply-preview--2" /> @@ -770,28 +717,32 @@ export const UserMessage = observer(() => { /> )} - {(message.editedTimestamp || message.isEditing) && - (message.isEditing ? ( - - {' '} - {i18n._(EDITED_DESCRIPTOR)} - - ) : ( - - - {' '} - {i18n._(EDITED_DESCRIPTOR)} - - - ))} )} - {((!message.content && !isEditing) || (shouldHideContent && !isEditing)) && - shouldGroup && + {shouldHideContent && + !isEditing && + message.content.length > 0 && + (message.editedTimestamp || message.isEditing) && + (message.isEditing ? ( + + {' '} + {i18n._(EDITED_DESCRIPTOR)} + + ) : ( + + + {' '} + {i18n._(EDITED_DESCRIPTOR)} + + + ))} + + {message.content.length === 0 && + !isEditing && (message.editedTimestamp || message.isEditing) && (message.isEditing ? ( @@ -810,7 +761,6 @@ export const UserMessage = observer(() => { ))} - {renderFailedFooter()}