// SPDX-License-Identifier: AGPL-3.0-or-later import {reportSkeletonCompactTimestampWidth} from '@app/features/app/components/skeleton/SkeletonLayoutMemory'; import {UserTag} from '@app/features/channel/components/ChannelUserTag'; import {MessageAvatar} from '@app/features/channel/components/MessageAvatar'; import {MessageTimeoutIndicator} from '@app/features/channel/components/MessageTimeoutIndicator'; import {MessageUsername} from '@app/features/channel/components/MessageUsername'; import {TimestampWithTooltip} from '@app/features/channel/components/TimestampWithTooltip'; import type {Guild} from '@app/features/guild/models/Guild'; import type {GuildMember} from '@app/features/member/models/GuildMember'; import type {Message} from '@app/features/messaging/models/MessagingMessage'; import {compactMessagePrefixProps} from '@app/features/theme/layout/MessageLayoutAttributes'; import {getRemScaleForDocument} from '@app/features/theme/layout/RemFromPx'; import styles from '@app/features/theme/styles/Message.module.css'; import type {User} from '@app/features/user/models/User'; import * as DateUtils from '@app/features/user/utils/DateFormatting'; import type {MessagePreviewContext} from '@fluxer/constants/src/ChannelConstants'; import {clsx} from 'clsx'; import type React from 'react'; import {useCallback, useLayoutEffect, useRef} from 'react'; const COMPACT_TIMESTAMP_WIDTH_CSS_VARIABLE = '--message-compact-measured-timestamp-width'; const COMPACT_MESSAGE_SELECTOR = '[data-flx-compact="true"]'; interface CompactMessageLayoutProps { message: Message; shouldGroup: boolean; mobileLayoutEnabled: boolean; children: (showMetadata: boolean) => React.ReactNode; } interface CompactAuthorPrefixProps { message: Message; author: User; guild?: Guild; member?: GuildMember; showAvatar: boolean; showTimeoutIndicator: boolean; isHovering: boolean; previewContext?: keyof typeof MessagePreviewContext; previewOverrides?: { usernameColor?: string; displayName?: string; }; } function shouldShowCompactMetadata(shouldGroup: boolean, mobileLayoutEnabled: boolean): boolean { return !(shouldGroup && mobileLayoutEnabled); } function useMeasuredCompactTimestampWidth(enabled: boolean): React.RefCallback { const timestampRef = useRef(null); const setTimestampRef = useCallback((node: HTMLSpanElement | null) => { timestampRef.current = node; }, []); useLayoutEffect(() => { if (!enabled) return undefined; const timestampElement = timestampRef.current; if (!timestampElement) return undefined; const messageElement = timestampElement.closest(COMPACT_MESSAGE_SELECTOR); if (!messageElement) return undefined; let animationFrame = 0; let lastWidth = ''; const updateWidth = () => { animationFrame = 0; const width = timestampElement.getBoundingClientRect().width; const nextWidth = width > 0 ? `${width.toFixed(2)}px` : ''; if (nextWidth === lastWidth) return; lastWidth = nextWidth; if (nextWidth) { reportSkeletonCompactTimestampWidth(width / getRemScaleForDocument(timestampElement.ownerDocument)); messageElement.style.setProperty(COMPACT_TIMESTAMP_WIDTH_CSS_VARIABLE, nextWidth); } else { messageElement.style.removeProperty(COMPACT_TIMESTAMP_WIDTH_CSS_VARIABLE); } }; const scheduleUpdate = () => { if (animationFrame) { cancelAnimationFrame(animationFrame); } animationFrame = requestAnimationFrame(updateWidth); }; updateWidth(); const resizeObserver = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(scheduleUpdate); resizeObserver?.observe(timestampElement); return () => { if (animationFrame) { cancelAnimationFrame(animationFrame); } resizeObserver?.disconnect(); messageElement.style.removeProperty(COMPACT_TIMESTAMP_WIDTH_CSS_VARIABLE); }; }, [enabled]); return setTimestampRef; } function CompactTimestamp({ message, shouldGroup, timestampRef, }: { message: Message; shouldGroup: boolean; timestampRef: React.Ref; }): React.ReactElement { const className = shouldGroup ? styles.messageTimestampCompactHover : styles.messageTimestampCompact; return ( [ {DateUtils.getFormattedTime(message.timestamp)} ] ); } export function CompactMessageLayout({ message, shouldGroup, mobileLayoutEnabled, children, }: CompactMessageLayoutProps): React.ReactElement { const showMetadata = shouldShowCompactMetadata(shouldGroup, mobileLayoutEnabled); const timestampRef = useMeasuredCompactTimestampWidth(showMetadata); return (
{showMetadata && ( )}
{children(showMetadata)}
); } export function CompactAuthorPrefix({ message, author, guild, member, showAvatar, showTimeoutIndicator, isHovering, previewContext, previewOverrides, }: CompactAuthorPrefixProps): React.ReactElement { const isPreview = Boolean(previewContext); return ( {' '} {author.bot && ( )} {showAvatar && ( )} {showTimeoutIndicator && ( )} :{' '} ); }