From 0897e2ce4c39d5327b180d051b5329824540e70f Mon Sep 17 00:00:00 2001 From: root Date: Sat, 16 May 2026 01:49:25 -0400 Subject: [PATCH] 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 } diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx index 03c9c6927..6ec8af156 100644 --- a/src/app/features/room/message/Message.tsx +++ b/src/app/features/room/message/Message.tsx @@ -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 ( + + {icon} + + ); +} + 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 && ( + + )} ); diff --git a/src/lotus-terminal.css.ts b/src/lotus-terminal.css.ts index 3255516cd..0a8860f4b 100644 --- a/src/lotus-terminal.css.ts +++ b/src/lotus-terminal.css.ts @@ -626,3 +626,37 @@ globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} button[data boxShadow: '0 0 7px rgba(196,78,0,0.18)', }); +// ── GIF picker (terminal mode) ─────────────────────────────────────────────── +globalStyle(`body.${lotusTerminalBodyClass} [data-gif-terminal] input,` + + `body.${lotusTerminalBodyClass} [data-gif-terminal] form`, { + background: '#030c14 !important' as any, + color: '#e8edf5 !important' as any, + fontFamily: "'JetBrains Mono','Cascadia Code','Fira Code',monospace !important" as any, + border: '1px solid rgba(255,107,0,0.35) !important' as any, + borderRadius: '4px !important' as any, + fontSize: '12px !important' as any, + boxShadow: 'none !important' as any, +}); +globalStyle(`body.${lotusTerminalBodyClass} [data-gif-terminal] input:focus`, { + borderColor: 'rgba(255,107,0,0.70) !important' as any, + boxShadow: '0 0 0 2px rgba(255,107,0,0.12) !important' as any, + outline: 'none !important' as any, +}); +globalStyle(`body.${lotusTerminalBodyClass} [data-gif-terminal] input::placeholder`, { + color: 'rgba(255,107,0,0.40) !important' as any, +}); +globalStyle(`body.${lotusTerminalBodyClass} [data-gif-terminal] svg,` + + `body.${lotusTerminalBodyClass} [data-gif-terminal] button[type="reset"]`, { + display: 'none !important' as any, +}); +globalStyle(`body.${lotusTerminalBodyClass} [data-gif-terminal] ::-webkit-scrollbar`, { + width: '4px', +}); +globalStyle(`body.${lotusTerminalBodyClass} [data-gif-terminal] ::-webkit-scrollbar-track`, { + background: '#030508', +}); +globalStyle(`body.${lotusTerminalBodyClass} [data-gif-terminal] ::-webkit-scrollbar-thumb`, { + background: 'rgba(255,107,0,0.40)', + borderRadius: '2px', +}); +