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:
@@ -8,41 +8,6 @@ import { settingsAtom } from '../state/settings';
|
|||||||
|
|
||||||
const PICKER_WIDTH = 312;
|
const PICKER_WIDTH = 312;
|
||||||
|
|
||||||
const TERMINAL_CSS = `
|
|
||||||
[data-gif-terminal] input,
|
|
||||||
[data-gif-terminal] form {
|
|
||||||
background: #030c14 !important;
|
|
||||||
color: #e8edf5 !important;
|
|
||||||
font-family: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', monospace !important;
|
|
||||||
border: 1px solid rgba(255,107,0,0.35) !important;
|
|
||||||
border-radius: 4px !important;
|
|
||||||
font-size: 12px !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
[data-gif-terminal] input:focus {
|
|
||||||
border-color: rgba(255,107,0,0.7) !important;
|
|
||||||
box-shadow: 0 0 0 2px rgba(255,107,0,0.12) !important;
|
|
||||||
outline: none !important;
|
|
||||||
}
|
|
||||||
[data-gif-terminal] input::placeholder {
|
|
||||||
color: rgba(255,107,0,0.4) !important;
|
|
||||||
font-family: 'JetBrains Mono', monospace !important;
|
|
||||||
}
|
|
||||||
[data-gif-terminal] svg,
|
|
||||||
[data-gif-terminal] button[type="reset"] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
[data-gif-terminal] ::-webkit-scrollbar {
|
|
||||||
width: 4px;
|
|
||||||
}
|
|
||||||
[data-gif-terminal] ::-webkit-scrollbar-track {
|
|
||||||
background: #030508;
|
|
||||||
}
|
|
||||||
[data-gif-terminal] ::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba(255,107,0,0.4);
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
type GifPickerInnerProps = {
|
type GifPickerInnerProps = {
|
||||||
onSelect: (url: string, width: number, height: number) => void;
|
onSelect: (url: string, width: number, height: number) => void;
|
||||||
@@ -142,7 +107,6 @@ export function GifPicker({ apiKey, onSelect, requestClose }: GifPickerProps) {
|
|||||||
data-gif-terminal={lotusTerminal ? '' : undefined}
|
data-gif-terminal={lotusTerminal ? '' : undefined}
|
||||||
style={containerStyle}
|
style={containerStyle}
|
||||||
>
|
>
|
||||||
{lotusTerminal && <style>{TERMINAL_CSS}</style>}
|
|
||||||
<SearchContextManager apiKey={apiKey} initialTerm="">
|
<SearchContextManager apiKey={apiKey} initialTerm="">
|
||||||
<GifPickerInner onSelect={onSelect} requestClose={requestClose} lotusTerminal={!!lotusTerminal} />
|
<GifPickerInner onSelect={onSelect} requestClose={requestClose} lotusTerminal={!!lotusTerminal} />
|
||||||
</SearchContextManager>
|
</SearchContextManager>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import React, {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import FocusTrap from 'focus-trap-react';
|
import FocusTrap from 'focus-trap-react';
|
||||||
import { useHover, useFocusWithin } from 'react-aria';
|
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 { Relations } from 'matrix-js-sdk/lib/models/relations';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { RoomPinnedEventsEventContent } from 'matrix-js-sdk/lib/types';
|
import { RoomPinnedEventsEventContent } from 'matrix-js-sdk/lib/types';
|
||||||
@@ -60,6 +60,8 @@ import {
|
|||||||
} from '../../../utils/matrix';
|
} from '../../../utils/matrix';
|
||||||
import { MessageLayout, MessageSpacing } from '../../../state/settings';
|
import { MessageLayout, MessageSpacing } from '../../../state/settings';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
|
import { useSetting } from '../../../state/hooks/settings';
|
||||||
|
import { settingsAtom } from '../../../state/settings';
|
||||||
import { useRecentEmoji } from '../../../hooks/useRecentEmoji';
|
import { useRecentEmoji } from '../../../hooks/useRecentEmoji';
|
||||||
import * as css from './styles.css';
|
import * as css from './styles.css';
|
||||||
import { EventReaders } from '../../../components/event-readers';
|
import { EventReaders } from '../../../components/event-readers';
|
||||||
@@ -82,6 +84,44 @@ import { PowerIcon } from '../../../components/power';
|
|||||||
import colorMXID from '../../../../util/colorMXID';
|
import colorMXID from '../../../../util/colorMXID';
|
||||||
import { getPowerTagIconSrc } from '../../../hooks/useMemberPowerTag';
|
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;
|
export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void;
|
||||||
|
|
||||||
type MessageQuickReactionsProps = {
|
type MessageQuickReactionsProps = {
|
||||||
@@ -727,6 +767,8 @@ export const Message = as<'div', MessageProps>(
|
|||||||
const readReceiptUsers = hideReadReceipts
|
const readReceiptUsers = hideReadReceipts
|
||||||
? []
|
? []
|
||||||
: (readPositions.get(mEvent.getId() ?? '') ?? []);
|
: (readPositions.get(mEvent.getId() ?? '') ?? []);
|
||||||
|
const isMine = mEvent.getSender() === mx.getUserId();
|
||||||
|
const [lotusTerminal] = useSetting(settingsAtom, 'lotusTerminal');
|
||||||
|
|
||||||
const [hover, setHover] = useState(false);
|
const [hover, setHover] = useState(false);
|
||||||
const { hoverProps } = useHover({ onHoverChange: setHover });
|
const { hoverProps } = useHover({ onHoverChange: setHover });
|
||||||
@@ -846,6 +888,9 @@ export const Message = as<'div', MessageProps>(
|
|||||||
userIds={readReceiptUsers}
|
userIds={readReceiptUsers}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{isMine && readReceiptUsers.length === 0 && (
|
||||||
|
<DeliveryStatus status={mEvent.status} lotusTerminal={!!lotusTerminal} />
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -626,3 +626,37 @@ globalStyle(`html[data-theme="light"] body.${lotusTerminalBodyClass} button[data
|
|||||||
boxShadow: '0 0 7px rgba(196,78,0,0.18)',
|
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',
|
||||||
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user