feat: delivery status indicator, GIF picker CSS to TDS file (M-6, M-7)

- Message.tsx: show delivery status (sending/sent/failed) on own messages when
  no read receipts yet; hidden once server confirms (status null); TDS-styled
- GifPicker.tsx: move terminal CSS from runtime <style> tag into lotus-terminal.css.ts
  eliminating flash of unstyled content (M-6)
- lotus-terminal.css.ts: add [data-gif-terminal] selector rules for GIF picker
This commit is contained in:
root
2026-05-16 01:49:25 -04:00
parent 4249150100
commit b14575fa0a
3 changed files with 80 additions and 37 deletions
+46 -1
View File
@@ -32,7 +32,7 @@ import React, {
} from 'react';
import FocusTrap from 'focus-trap-react';
import { useHover, useFocusWithin } from 'react-aria';
import { MatrixEvent, Room } from 'matrix-js-sdk';
import { MatrixEvent, Room, EventStatus } from 'matrix-js-sdk';
import { Relations } from 'matrix-js-sdk/lib/models/relations';
import classNames from 'classnames';
import { RoomPinnedEventsEventContent } from 'matrix-js-sdk/lib/types';
@@ -60,6 +60,8 @@ import {
} from '../../../utils/matrix';
import { MessageLayout, MessageSpacing } from '../../../state/settings';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
import { useRecentEmoji } from '../../../hooks/useRecentEmoji';
import * as css from './styles.css';
import { EventReaders } from '../../../components/event-readers';
@@ -82,6 +84,44 @@ import { PowerIcon } from '../../../components/power';
import colorMXID from '../../../../util/colorMXID';
import { getPowerTagIconSrc } from '../../../hooks/useMemberPowerTag';
// Delivery status indicator for own messages
function DeliveryStatus({ status, lotusTerminal }: { status: string | null; lotusTerminal: boolean }) {
if (status === null) return null; // confirmed by server — read receipts take over
let icon: string;
let label: string;
let colorStyle: string;
if (status === EventStatus.NOT_SENT) {
icon = '✕'; label = 'Failed to send'; colorStyle = lotusTerminal ? '#FF3B3B' : color.Critical.Main;
} else if (status === EventStatus.SENDING || status === EventStatus.ENCRYPTING) {
icon = '⟳'; label = 'Sending...'; colorStyle = lotusTerminal ? 'rgba(0,212,255,0.60)' : color.Secondary.Main;
} else {
icon = '✓'; label = 'Sent'; colorStyle = lotusTerminal ? 'rgba(0,212,255,0.70)' : color.Secondary.Main;
}
return (
<Box
as="span"
aria-label={label}
title={label}
style={{
display: 'inline-flex',
alignItems: 'center',
marginTop: '2px',
fontSize: '10px',
lineHeight: 1,
color: colorStyle,
opacity: 0.85,
userSelect: 'none',
...(lotusTerminal && status === EventStatus.NOT_SENT
? { textShadow: '0 0 6px #FF3B3B' }
: {}),
}}
>
{icon}
</Box>
);
}
export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void;
type MessageQuickReactionsProps = {
@@ -727,6 +767,8 @@ export const Message = as<'div', MessageProps>(
const readReceiptUsers = hideReadReceipts
? []
: (readPositions.get(mEvent.getId() ?? '') ?? []);
const isMine = mEvent.getSender() === mx.getUserId();
const [lotusTerminal] = useSetting(settingsAtom, 'lotusTerminal');
const [hover, setHover] = useState(false);
const { hoverProps } = useHover({ onHoverChange: setHover });
@@ -846,6 +888,9 @@ export const Message = as<'div', MessageProps>(
userIds={readReceiptUsers}
/>
)}
{isMine && readReceiptUsers.length === 0 && (
<DeliveryStatus status={mEvent.status} lotusTerminal={!!lotusTerminal} />
)}
</Box>
);