fix(app): stop rewriting message emoji urls to toggle animation (#1897)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-24 12:41:36 +02:00
1 parent 3a6bc4bc7b
commit 7a938c85f6
3 files changed
+324 -389

No files matched your search

@@ -281,304 +281,295 @@ const ForwardedFromSource = observer(({message}: {message: Message}) => {
interface ForwardedMessageContentProps { interface ForwardedMessageContentProps {
message: Message; message: Message;
snapshot: MessageSnapshot; snapshot: MessageSnapshot;
shouldAnimate: boolean;
interactionHandlers: Record<string, unknown>;
onDelete?: (bypassConfirm?: boolean) => void; onDelete?: (bypassConfirm?: boolean) => void;
} }
export const ForwardedMessageContent = observer( export const ForwardedMessageContent = observer(({message, snapshot, onDelete}: ForwardedMessageContentProps) => {
({message, snapshot, shouldAnimate, interactionHandlers, onDelete}: ForwardedMessageContentProps) => { const {i18n} = useLingui();
const {i18n} = useLingui(); const snapshotIndex = 0;
const snapshotIndex = 0; const snapshotEditedTimestamp = snapshot.edited_timestamp ? new Date(snapshot.edited_timestamp) : null;
const snapshotEditedTimestamp = snapshot.edited_timestamp ? new Date(snapshot.edited_timestamp) : null; const copyText = useMemo(
const copyText = useMemo( () =>
() => buildMessageSnapshotCopyText(snapshot, {
buildMessageSnapshotCopyText(snapshot, { channelId: message.channelId,
channelId: message.channelId, messageId: message.id,
messageId: message.id, i18n,
i18n, }),
}), [i18n.locale, message.channelId, message.id, snapshot],
[i18n.locale, message.channelId, message.id, snapshot], );
); return (
return ( <div
className={styles.forwardedContainer}
data-message-copy-block={copyText ? 'true' : undefined}
data-message-copy-text={copyText || undefined}
data-flx="channel.message-attachments.forwarded-message-content.forwarded-container"
>
<div <div
className={styles.forwardedContainer} className={styles.forwardedBar}
data-message-copy-block={copyText ? 'true' : undefined} data-flx="channel.message-attachments.forwarded-message-content.forwarded-bar"
data-message-copy-text={copyText || undefined} />
data-flx="channel.message-attachments.forwarded-message-content.forwarded-container" <div
className={styles.forwardedContent}
data-flx="channel.message-attachments.forwarded-message-content.forwarded-content"
> >
<div <div
className={styles.forwardedBar} className={styles.forwardedHeader}
data-flx="channel.message-attachments.forwarded-message-content.forwarded-bar" data-flx="channel.message-attachments.forwarded-message-content.forwarded-header"
/>
<div
className={styles.forwardedContent}
data-flx="channel.message-attachments.forwarded-message-content.forwarded-content"
> >
<div <ArrowBendUpRightIcon
className={styles.forwardedHeader} className={styles.forwardedIcon}
data-flx="channel.message-attachments.forwarded-message-content.forwarded-header" weight="bold"
> data-flx="channel.message-attachments.forwarded-message-content.forwarded-icon"
<ArrowBendUpRightIcon
className={styles.forwardedIcon}
weight="bold"
data-flx="channel.message-attachments.forwarded-message-content.forwarded-icon"
/>
<span
className={styles.forwardedLabel}
data-flx="channel.message-attachments.forwarded-message-content.forwarded-label"
>
<Trans>Forwarded</Trans>
</span>
</div>
{snapshot.content && (
<div
className={clsx(markupStyles.markup)}
data-search-highlight-scope="message"
data-flx="channel.message-attachments.forwarded-message-content.div"
>
<SafeMarkdown
content={snapshot.content}
options={{
context: MarkdownContext.STANDARD_WITH_JUMBO,
messageId: message.id,
channelId: message.channelId,
mentionChannels: snapshot.mention_channels,
}}
data-flx="channel.message-attachments.forwarded-message-content.safe-markdown"
/>
{snapshotEditedTimestamp && (
<TimestampWithTooltip
date={snapshotEditedTimestamp}
className={messageStyles.editedTimestamp}
data-flx="channel.message-attachments.forwarded-message-content.timestamp-with-tooltip"
>
<span
className={messageStyles.editedLabel}
data-flx="channel.message-attachments.forwarded-message-content.span"
>
{' '}
<Trans>(edited)</Trans>
</span>
</TimestampWithTooltip>
)}
</div>
)}
{snapshot.attachments && snapshot.attachments.length > 0 && (
<div
className={styles.attachmentsContainer}
data-flx="channel.message-attachments.forwarded-message-content.attachments-container"
>
{(() => {
const {enrichedAttachments, mediaAttachments, shouldUseMosaic} = getAttachmentRenderingState(
snapshot.attachments,
);
return (
<>
{shouldUseMosaic && (
<AttachmentMosaic
attachments={mediaAttachments}
message={message}
snapshotIndex={snapshotIndex}
onDelete={onDelete}
data-flx="channel.message-attachments.forwarded-message-content.attachment-mosaic"
/>
)}
{enrichedAttachments.map((attachment: MessageAttachment) => (
<Attachment
key={attachment.id}
attachment={attachment}
snapshotIndex={snapshotIndex}
message={message}
renderInMosaic={shouldUseMosaic}
onDelete={onDelete}
data-flx="channel.message-attachments.forwarded-message-content.attachment"
/>
))}
</>
);
})()}
</div>
)}
{snapshot.embeds && snapshot.embeds.length > 0 && UserSettings.getRenderEmbeds() && (
<div
className={styles.attachmentsContainer}
data-flx="channel.message-attachments.forwarded-message-content.attachments-container--2"
>
{snapshot.embeds.map((embed: MessageEmbed, index: number) => {
const embedKey = `${embed.id}-${index}`;
return (
<Embed
embed={embed}
key={embedKey}
message={message}
embedIndex={index}
contextualEmbeds={snapshot.embeds}
onDelete={onDelete}
isPreview={true}
data-flx="channel.message-attachments.forwarded-message-content.embed"
/>
);
})}
</div>
)}
{snapshot.stickers && snapshot.stickers.length > 0 && (
<div
className={styles.stickersContainer}
data-flx="channel.message-attachments.forwarded-message-content.stickers-container"
>
{snapshot.stickers.map((sticker: MessageStickerItem) => (
<StickerItem
key={sticker.id}
sticker={sticker}
message={message}
shouldAnimate={shouldAnimate}
interactionHandlers={interactionHandlers}
handleDelete={onDelete}
data-flx="channel.message-attachments.forwarded-message-content.sticker-item"
/>
))}
</div>
)}
<ForwardedFromSource
message={message}
data-flx="channel.message-attachments.forwarded-message-content.forwarded-from-source"
/> />
<span
className={styles.forwardedLabel}
data-flx="channel.message-attachments.forwarded-message-content.forwarded-label"
>
<Trans>Forwarded</Trans>
</span>
</div> </div>
{snapshot.content && (
<div
className={clsx(markupStyles.markup)}
data-search-highlight-scope="message"
data-flx="channel.message-attachments.forwarded-message-content.div"
>
<SafeMarkdown
content={snapshot.content}
options={{
context: MarkdownContext.STANDARD_WITH_JUMBO,
messageId: message.id,
channelId: message.channelId,
mentionChannels: snapshot.mention_channels,
}}
data-flx="channel.message-attachments.forwarded-message-content.safe-markdown"
/>
{snapshotEditedTimestamp && (
<TimestampWithTooltip
date={snapshotEditedTimestamp}
className={messageStyles.editedTimestamp}
data-flx="channel.message-attachments.forwarded-message-content.timestamp-with-tooltip"
>
<span
className={messageStyles.editedLabel}
data-flx="channel.message-attachments.forwarded-message-content.span"
>
{' '}
<Trans>(edited)</Trans>
</span>
</TimestampWithTooltip>
)}
</div>
)}
{snapshot.attachments && snapshot.attachments.length > 0 && (
<div
className={styles.attachmentsContainer}
data-flx="channel.message-attachments.forwarded-message-content.attachments-container"
>
{(() => {
const {enrichedAttachments, mediaAttachments, shouldUseMosaic} = getAttachmentRenderingState(
snapshot.attachments,
);
return (
<>
{shouldUseMosaic && (
<AttachmentMosaic
attachments={mediaAttachments}
message={message}
snapshotIndex={snapshotIndex}
onDelete={onDelete}
data-flx="channel.message-attachments.forwarded-message-content.attachment-mosaic"
/>
)}
{enrichedAttachments.map((attachment: MessageAttachment) => (
<Attachment
key={attachment.id}
attachment={attachment}
snapshotIndex={snapshotIndex}
message={message}
renderInMosaic={shouldUseMosaic}
onDelete={onDelete}
data-flx="channel.message-attachments.forwarded-message-content.attachment"
/>
))}
</>
);
})()}
</div>
)}
{snapshot.embeds && snapshot.embeds.length > 0 && UserSettings.getRenderEmbeds() && (
<div
className={styles.attachmentsContainer}
data-flx="channel.message-attachments.forwarded-message-content.attachments-container--2"
>
{snapshot.embeds.map((embed: MessageEmbed, index: number) => {
const embedKey = `${embed.id}-${index}`;
return (
<Embed
embed={embed}
key={embedKey}
message={message}
embedIndex={index}
contextualEmbeds={snapshot.embeds}
onDelete={onDelete}
isPreview={true}
data-flx="channel.message-attachments.forwarded-message-content.embed"
/>
);
})}
</div>
)}
{snapshot.stickers && snapshot.stickers.length > 0 && (
<div
className={styles.stickersContainer}
data-flx="channel.message-attachments.forwarded-message-content.stickers-container"
>
{snapshot.stickers.map((sticker: MessageStickerItem) => (
<StickerItem
key={sticker.id}
sticker={sticker}
message={message}
handleDelete={onDelete}
data-flx="channel.message-attachments.forwarded-message-content.sticker-item"
/>
))}
</div>
)}
<ForwardedFromSource
message={message}
data-flx="channel.message-attachments.forwarded-message-content.forwarded-from-source"
/>
</div> </div>
); </div>
}, );
); });
interface StickerItemProps { interface StickerItemProps {
sticker: MessageStickerItem; sticker: MessageStickerItem;
message: Message; message: Message;
shouldAnimate: boolean;
interactionHandlers: Record<string, unknown>;
sourceChannel?: Channel | null; sourceChannel?: Channel | null;
handleDelete?: (bypassConfirm?: boolean) => void; handleDelete?: (bypassConfirm?: boolean) => void;
} }
const StickerItem = observer( const StickerItem = observer(({sticker, message, sourceChannel, handleDelete}: StickerItemProps) => {
({sticker, message, shouldAnimate, interactionHandlers, sourceChannel, handleDelete}: StickerItemProps) => { const {shouldAnimate, interactionHandlers} = useStickerAnimation({isAnimated: sticker.animated});
const stickerUrl = AvatarUtils.getStickerURL({ 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 = () => (
<div className={styles.stickerTooltip} data-flx="channel.message-attachments.tooltip-content.sticker-tooltip">
<span className={styles.stickerName} data-flx="channel.message-attachments.tooltip-content.sticker-name">
{sticker.name}
</span>
{guild && (
<div
className={styles.stickerGuildInfo}
data-flx="channel.message-attachments.tooltip-content.sticker-guild-info"
>
<GuildIcon
id={guild.id}
name={guild.name}
icon={guild.icon}
className={styles.stickerGuildIcon}
sizePx={16}
data-flx="channel.message-attachments.tooltip-content.sticker-guild-icon"
/>
<span
className={styles.stickerGuildName}
data-flx="channel.message-attachments.tooltip-content.sticker-guild-name"
>
{guild.name}
</span>
</div>
)}
</div>
);
const handleContextMenu = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const stickerForMenu = stickerRecord ?? {
id: sticker.id, id: sticker.id,
animated: shouldAnimate, guildId: '',
size: 320, name: sticker.name,
}); description: '',
const stickerRecord = Sticker.getStickerById(sticker.id); tags: [],
const guild = stickerRecord?.guildId ? Guilds.getGuild(stickerRecord.guildId) : null; url: stickerUrl,
const isMature = !!stickerRecord?.nsfw || !!sticker.nsfw; animated: sticker.animated,
const {shouldBlur, shouldBlock, canReveal, reveal} = useMatureMedia(isMature, message.channelId); nsfw: Boolean(sticker.nsfw),
const tooltipContent = () => ( user: undefined,
<div className={styles.stickerTooltip} data-flx="channel.message-attachments.tooltip-content.sticker-tooltip">
<span className={styles.stickerName} data-flx="channel.message-attachments.tooltip-content.sticker-name">
{sticker.name}
</span>
{guild && (
<div
className={styles.stickerGuildInfo}
data-flx="channel.message-attachments.tooltip-content.sticker-guild-info"
>
<GuildIcon
id={guild.id}
name={guild.name}
icon={guild.icon}
className={styles.stickerGuildIcon}
sizePx={16}
data-flx="channel.message-attachments.tooltip-content.sticker-guild-icon"
/>
<span
className={styles.stickerGuildName}
data-flx="channel.message-attachments.tooltip-content.sticker-guild-name"
>
{guild.name}
</span>
</div>
)}
</div>
);
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}) => (
<MessageContextMenu
message={message}
sourceChannel={sourceChannel}
onClose={onClose}
onDelete={handleDelete!}
inlineStickerOrEmojiItems={
<StickerInlineMenuItems
sticker={stickerForMenu}
onClose={onClose}
data-flx="channel.message-attachments.handle-context-menu.sticker-inline-menu-items"
/>
}
data-flx="channel.message-attachments.handle-context-menu.message-context-menu"
/>
));
}; };
const handleRevealClick = useCallback( ContextMenuCommands.openFromEvent(e, ({onClose}) => (
(e: React.MouseEvent) => { <MessageContextMenu
if (shouldBlur && canReveal) { message={message}
e.preventDefault(); sourceChannel={sourceChannel}
e.stopPropagation(); onClose={onClose}
reveal(); onDelete={handleDelete!}
inlineStickerOrEmojiItems={
<StickerInlineMenuItems
sticker={stickerForMenu}
onClose={onClose}
data-flx="channel.message-attachments.handle-context-menu.sticker-inline-menu-items"
/>
} }
}, data-flx="channel.message-attachments.handle-context-menu.message-context-menu"
[shouldBlur, canReveal, reveal],
);
if (shouldBlock) {
return null;
}
const stickerImage = (
<img
src={stickerUrl}
alt={stickerRecord?.description || sticker.name}
className={clsx(styles.stickerImage, shouldBlur && matureStyles.matureStickerBlurred)}
width="160"
height="160"
data-flx="channel.message-attachments.sticker-item.sticker-image"
/> />
); ));
return ( };
<Tooltip key={sticker.id} text={tooltipContent} data-flx="channel.message-attachments.sticker-item.tooltip"> const handleRevealClick = useCallback(
<FocusRing data-flx="channel.message-attachments.sticker-item.focus-ring"> (e: React.MouseEvent) => {
<button if (shouldBlur && canReveal) {
type="button" e.preventDefault();
aria-label={stickerRecord?.description || sticker.name} e.stopPropagation();
className={styles.stickerWrapper} reveal();
data-message-sticker="true" }
onContextMenu={handleContextMenu} },
onClick={handleRevealClick} [shouldBlur, canReveal, reveal],
data-flx="channel.message-attachments.sticker-item.sticker-wrapper.reveal-click" );
{...interactionHandlers} if (shouldBlock) {
> return null;
{stickerImage} }
</button> const stickerImage = (
</FocusRing> <img
</Tooltip> src={stickerUrl}
); alt={stickerRecord?.description || sticker.name}
}, className={clsx(styles.stickerImage, shouldBlur && matureStyles.matureStickerBlurred)}
); width="160"
height="160"
data-flx="channel.message-attachments.sticker-item.sticker-image"
/>
);
return (
<Tooltip key={sticker.id} text={tooltipContent} data-flx="channel.message-attachments.sticker-item.tooltip">
<FocusRing data-flx="channel.message-attachments.sticker-item.focus-ring">
<button
type="button"
aria-label={stickerRecord?.description || sticker.name}
className={styles.stickerWrapper}
data-message-sticker="true"
onContextMenu={handleContextMenu}
onClick={handleRevealClick}
data-flx="channel.message-attachments.sticker-item.sticker-wrapper.reveal-click"
{...interactionHandlers}
>
{stickerImage}
</button>
</FocusRing>
</Tooltip>
);
});
export const MessageAttachments = observer(() => { export const MessageAttachments = observer(() => {
const {channel, message, handleDelete, previewContext, onPopoutToggle, suppressMessageActions} = const {channel, message, handleDelete, previewContext, onPopoutToggle, suppressMessageActions} =
useMessageViewContext(); useMessageViewContext();
const isPreview = Boolean(previewContext); const isPreview = Boolean(previewContext);
const reactionsIsPreview = isPreview || Boolean(suppressMessageActions); const reactionsIsPreview = isPreview || Boolean(suppressMessageActions);
const reactions = useMessageReactionsSnapshot(message.id); const reactions = useMessageReactionsSnapshot(message.id);
const {shouldAnimate, interactionHandlers} = useStickerAnimation();
const spoileredUrlEmbeds = useMemo(() => { const spoileredUrlEmbeds = useMemo(() => {
const embeddableCodeLinkContent = extractEmbeddableCodeLinkContent(message.content); const embeddableCodeLinkContent = extractEmbeddableCodeLinkContent(message.content);
return { return {
@@ -593,8 +584,6 @@ export const MessageAttachments = observer(() => {
<ForwardedMessageContent <ForwardedMessageContent
message={message} message={message}
snapshot={message.messageSnapshots[0]} snapshot={message.messageSnapshots[0]}
shouldAnimate={shouldAnimate}
interactionHandlers={interactionHandlers}
onDelete={handleDelete} onDelete={handleDelete}
data-flx="channel.message-attachments.forwarded-message-content" data-flx="channel.message-attachments.forwarded-message-content"
/> />
@@ -648,8 +637,6 @@ export const MessageAttachments = observer(() => {
key={sticker.id} key={sticker.id}
sticker={sticker} sticker={sticker}
message={message} message={message}
shouldAnimate={shouldAnimate}
interactionHandlers={interactionHandlers}
sourceChannel={channel} sourceChannel={channel}
handleDelete={handleDelete} handleDelete={handleDelete}
data-flx="channel.message-attachments.sticker-item" data-flx="channel.message-attachments.sticker-item"
@@ -47,13 +47,11 @@ export const ReplyPreview = observer(
message, message,
channelId, channelId,
guildId, guildId,
animateEmoji,
messageDisplayCompact, messageDisplayCompact,
}: { }: {
message: Message; message: Message;
channelId: string; channelId: string;
guildId?: string; guildId?: string;
animateEmoji: boolean;
messageDisplayCompact: boolean; messageDisplayCompact: boolean;
}) => { }) => {
const {i18n} = useLingui(); const {i18n} = useLingui();
@@ -63,14 +61,15 @@ export const ReplyPreview = observer(
); );
const {isMessageRevealed} = useCollapsedMessageVisibility(); const {isMessageRevealed} = useCollapsedMessageVisibility();
const resolvedGuildId = guildId ?? message.guildId ?? message.messageReference?.guild_id; const resolvedGuildId = guildId ?? message.guildId ?? message.messageReference?.guild_id;
const referenceChannelId = message.messageReference?.channel_id ?? message.channelId;
const jumpToRepliedMessage = useCallback(() => { const jumpToRepliedMessage = useCallback(() => {
if (message.messageReference?.message_id) { if (message.messageReference?.message_id) {
goToMessage(message.channelId, message.messageReference.message_id, { goToMessage(referenceChannelId, message.messageReference.message_id, {
returnToMessageId: message.id, returnToMessageId: message.id,
returnChannelId: message.channelId, returnChannelId: message.channelId,
}); });
} }
}, [message.channelId, message.id, message.messageReference]); }, [referenceChannelId, message.channelId, message.id, message.messageReference]);
if (!message.messageReference) return null; if (!message.messageReference) return null;
if (resolution.state !== MessageReferenceState.LOADED) { if (resolution.state !== MessageReferenceState.LOADED) {
const isDeleted = resolution.state === MessageReferenceState.DELETED; const isDeleted = resolution.state === MessageReferenceState.DELETED;
@@ -253,7 +252,6 @@ export const ReplyPreview = observer(
context: MarkdownContext.RESTRICTED_INLINE_REPLY, context: MarkdownContext.RESTRICTED_INLINE_REPLY,
messageId: referencedMessage.id, messageId: referencedMessage.id,
channelId, channelId,
disableAnimatedEmoji: !animateEmoji,
mentionChannels: referencedMessage.mentionChannels, mentionChannels: referencedMessage.mentionChannels,
}} }}
data-flx="channel.reply-preview.safe-markdown" data-flx="channel.reply-preview.safe-markdown"
@@ -2,11 +2,6 @@
import Accessibility from '@app/features/accessibility/state/Accessibility'; import Accessibility from '@app/features/accessibility/state/Accessibility';
import {SILENT_MENTION} from '@app/features/app/config/I18nDisplayConstants'; 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 {UserTag} from '@app/features/channel/components/ChannelUserTag';
import {CompactAuthorPrefix, CompactMessageLayout} from '@app/features/channel/components/CompactMessageLayout'; import {CompactAuthorPrefix, CompactMessageLayout} from '@app/features/channel/components/CompactMessageLayout';
import {EditingMessageInput} from '@app/features/channel/components/EditingMessageInput'; 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 {MessageAttachments} from '@app/features/channel/components/MessageAttachments';
import {MessageAuthorInfo} from '@app/features/channel/components/MessageAuthorInfo'; import {MessageAuthorInfo} from '@app/features/channel/components/MessageAuthorInfo';
import {MessageAvatar} from '@app/features/channel/components/MessageAvatar'; 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 {MessageTimeoutIndicator} from '@app/features/channel/components/MessageTimeoutIndicator';
import {MessageUsername} from '@app/features/channel/components/MessageUsername'; import {MessageUsername} from '@app/features/channel/components/MessageUsername';
import {useMessageViewContext} from '@app/features/channel/components/MessageViewContext'; 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 {Trans, useLingui} from '@lingui/react/macro';
import {ArrowsClockwiseIcon, BellSlashIcon, EyeIcon, WarningCircleIcon} from '@phosphor-icons/react'; import {ArrowsClockwiseIcon, BellSlashIcon, EyeIcon, WarningCircleIcon} from '@phosphor-icons/react';
import {clsx} from 'clsx'; import {clsx} from 'clsx';
import {autorun} from 'mobx';
import {observer} from 'mobx-react-lite'; 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({ const JUMP_TO_MESSAGE_FROM_SENT_DESCRIPTOR = msg({
message: 'Jump to message from {displayName}, sent {formattedDate}', message: 'Jump to message from {displayName}, sent {formattedDate}',
@@ -86,22 +79,6 @@ const MessageStateToClassName: Record<string, string> = {
[MessageStates.FAILED]: styles.messageFailed, [MessageStates.FAILED]: styles.messageFailed,
}; };
const CUSTOM_EMOJI_MARKDOWN_PATTERN = /<a?:[a-zA-Z0-9_+-]{2,}:([0-9]+)>/g; const CUSTOM_EMOJI_MARKDOWN_PATTERN = /<a?:[a-zA-Z0-9_+-]{2,}:([0-9]+)>/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<HTMLImageElement>;
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): { function messageContentCopyBlockProps(content: string): {
'data-message-copy-block'?: 'true'; 'data-message-copy-block'?: 'true';
'data-message-copy-text'?: string; 'data-message-copy-text'?: string;
@@ -122,8 +99,6 @@ export const UserMessage = observer(() => {
previewOverrides, previewOverrides,
onHeadingActivate, onHeadingActivate,
} = useMessageViewContext(); } = useMessageViewContext();
const [animateEmoji, setAnimateEmoji] = useState(getDefaultMessageEmojiAnimationAllowed);
const animatedMediaPlaybackAllowed = useAnimatedMediaPlaybackAllowed();
const isEditing = MessageEdit.isEditing(message.channelId, message.id); const isEditing = MessageEdit.isEditing(message.channelId, message.id);
const userAuthor = Users.getUser(message.author.id); const userAuthor = Users.getUser(message.author.id);
const author = message.webhookId != null ? message.author : (userAuthor ?? message.author); const author = message.webhookId != null ? message.author : (userAuthor ?? message.author);
@@ -155,6 +130,15 @@ export const UserMessage = observer(() => {
}), }),
[message.content], [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 = const shouldHideContent =
UserSettings.getRenderEmbeds() && UserSettings.getRenderEmbeds() &&
message.embeds.length > 0 && message.embeds.length > 0 &&
@@ -312,12 +296,7 @@ export const UserMessage = observer(() => {
> >
<SafeMarkdown <SafeMarkdown
content={message.content} content={message.content}
options={{ options={markdownOptions}
context: MarkdownContext.STANDARD_WITH_JUMBO,
messageId: message.id,
channelId: message.channelId,
mentionChannels: message.mentionChannels,
}}
data-flx="channel.user-message.render-message-content.safe-markdown" data-flx="channel.user-message.render-message-content.safe-markdown"
/> />
{(message.editedTimestamp || message.isEditing) && {(message.editedTimestamp || message.isEditing) &&
@@ -346,6 +325,7 @@ export const UserMessage = observer(() => {
}, [ }, [
shouldShowEditingInput, shouldShowEditingInput,
shouldHideContent, shouldHideContent,
markdownOptions,
message, message,
message.content, message.content,
message.id, message.id,
@@ -357,25 +337,6 @@ export const UserMessage = observer(() => {
onSubmit, onSubmit,
i18n, 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(() => { const renderFailedFooter = useCallback(() => {
if (!shouldShowFailedFooter) { if (!shouldShowFailedFooter) {
return null; return null;
@@ -469,12 +430,7 @@ export const UserMessage = observer(() => {
> >
<SafeMarkdown <SafeMarkdown
content={message.content} content={message.content}
options={{ options={markdownOptions}
context: MarkdownContext.STANDARD_WITH_JUMBO,
messageId: message.id,
channelId: message.channelId,
mentionChannels: message.mentionChannels,
}}
data-flx="channel.user-message.safe-markdown" data-flx="channel.user-message.safe-markdown"
/> />
</div> </div>
@@ -523,7 +479,6 @@ export const UserMessage = observer(() => {
message={message} message={message}
channelId={channel.id} channelId={channel.id}
guildId={channel.guildId} guildId={channel.guildId}
animateEmoji={animateEmoji}
messageDisplayCompact={messageDisplayCompact} messageDisplayCompact={messageDisplayCompact}
data-flx="channel.user-message.reply-preview" data-flx="channel.user-message.reply-preview"
/> />
@@ -559,37 +514,30 @@ export const UserMessage = observer(() => {
> >
{showMetadata && compactAuthorPrefix} {showMetadata && compactAuthorPrefix}
{!shouldHideContent && ( {!shouldHideContent && (
<> <SafeMarkdown
<SafeMarkdown content={message.content}
content={message.content} options={markdownOptions}
options={{ data-flx="channel.user-message.safe-markdown--2"
context: MarkdownContext.STANDARD_WITH_JUMBO, />
messageId: message.id,
channelId: message.channelId,
mentionChannels: message.mentionChannels,
}}
data-flx="channel.user-message.safe-markdown--2"
/>
{(message.editedTimestamp || message.isEditing) &&
(message.isEditing ? (
<span className={styles.editedLabel} data-flx="channel.user-message.edited-label">
{' '}
{i18n._(EDITED_DESCRIPTOR)}
</span>
) : (
<TimestampWithTooltip
date={message.editedTimestamp!}
className={styles.editedTimestamp}
data-flx="channel.user-message.edited-timestamp"
>
<span className={styles.editedLabel} data-flx="channel.user-message.edited-label--2">
{' '}
{i18n._(EDITED_DESCRIPTOR)}
</span>
</TimestampWithTooltip>
))}
</>
)} )}
{(message.editedTimestamp || message.isEditing) &&
(message.isEditing ? (
<span className={styles.editedLabel} data-flx="channel.user-message.edited-label">
{' '}
{i18n._(EDITED_DESCRIPTOR)}
</span>
) : (
<TimestampWithTooltip
date={message.editedTimestamp!}
className={styles.editedTimestamp}
data-flx="channel.user-message.edited-timestamp"
>
<span className={styles.editedLabel} data-flx="channel.user-message.edited-label--2">
{' '}
{i18n._(EDITED_DESCRIPTOR)}
</span>
</TimestampWithTooltip>
))}
</div> </div>
) )
} }
@@ -608,7 +556,6 @@ export const UserMessage = observer(() => {
message={message} message={message}
channelId={channel.id} channelId={channel.id}
guildId={channel.guildId} guildId={channel.guildId}
animateEmoji={animateEmoji}
messageDisplayCompact={messageDisplayCompact} messageDisplayCompact={messageDisplayCompact}
data-flx="channel.user-message.reply-preview--2" data-flx="channel.user-message.reply-preview--2"
/> />
@@ -770,28 +717,32 @@ export const UserMessage = observer(() => {
/> />
</Tooltip> </Tooltip>
)} )}
{(message.editedTimestamp || message.isEditing) &&
(message.isEditing ? (
<span className={styles.editedLabel} data-flx="channel.user-message.edited-label--3">
{' '}
{i18n._(EDITED_DESCRIPTOR)}
</span>
) : (
<TimestampWithTooltip
date={message.editedTimestamp!}
className={styles.editedTimestamp}
data-flx="channel.user-message.edited-timestamp--2"
>
<span className={styles.editedLabel} data-flx="channel.user-message.edited-label--4">
{' '}
{i18n._(EDITED_DESCRIPTOR)}
</span>
</TimestampWithTooltip>
))}
</AuthorHeading> </AuthorHeading>
)} )}
{((!message.content && !isEditing) || (shouldHideContent && !isEditing)) && {shouldHideContent &&
shouldGroup && !isEditing &&
message.content.length > 0 &&
(message.editedTimestamp || message.isEditing) &&
(message.isEditing ? (
<span className={styles.editedLabel} data-flx="channel.user-message.edited-label--3">
{' '}
{i18n._(EDITED_DESCRIPTOR)}
</span>
) : (
<TimestampWithTooltip
date={message.editedTimestamp!}
className={styles.editedTimestamp}
data-flx="channel.user-message.edited-timestamp--2"
>
<span className={styles.editedLabel} data-flx="channel.user-message.edited-label--4">
{' '}
{i18n._(EDITED_DESCRIPTOR)}
</span>
</TimestampWithTooltip>
))}
<MessageAttachments data-flx="channel.user-message.message-attachments--3" />
{message.content.length === 0 &&
!isEditing &&
(message.editedTimestamp || message.isEditing) && (message.editedTimestamp || message.isEditing) &&
(message.isEditing ? ( (message.isEditing ? (
<span className={styles.editedLabel} data-flx="channel.user-message.edited-label--5"> <span className={styles.editedLabel} data-flx="channel.user-message.edited-label--5">
@@ -810,7 +761,6 @@ export const UserMessage = observer(() => {
</span> </span>
</TimestampWithTooltip> </TimestampWithTooltip>
))} ))}
<MessageAttachments data-flx="channel.user-message.message-attachments--3" />
{renderFailedFooter()} {renderFailedFooter()}
</div> </div>
</SpoilerSyncProvider> </SpoilerSyncProvider>