Widen the media panel, support skinny full-page mode, keep jump-to-latest and return-to-previous reliable, and stop same-room event jumps from remounting the outlet or yanking the sidebar.
185 lines
5.0 KiB
TypeScript
185 lines
5.0 KiB
TypeScript
import React from 'react';
|
|
import { Box, Text, as } from 'folds';
|
|
import classNames from 'classnames';
|
|
import { MatrixClient, MatrixEvent, Room } from 'matrix-js-sdk';
|
|
import * as css from './Reaction.css';
|
|
import { getHexcodeForEmoji, getShortcodeFor } from '../../plugins/emoji';
|
|
import { getMemberDisplayName } from '../../utils/room';
|
|
import { eventWithShortcode, getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
|
|
import { isStationeryTheme, useTheme } from '../../hooks/useTheme';
|
|
|
|
const MAX_STICKER_STACK = 4;
|
|
|
|
export const Reaction = as<
|
|
'button',
|
|
{
|
|
mx: MatrixClient;
|
|
count: number;
|
|
reaction: string;
|
|
useAuthentication?: boolean;
|
|
}
|
|
>(({ className, mx, count, reaction, useAuthentication, ...props }, ref) => {
|
|
const theme = useTheme();
|
|
const stationery = isStationeryTheme(theme);
|
|
const isCustomEmoji = reaction.startsWith('mxc://');
|
|
const customSrc = isCustomEmoji
|
|
? mxcUrlToHttp(mx, reaction, useAuthentication) ?? reaction
|
|
: undefined;
|
|
|
|
const emoji = isCustomEmoji ? (
|
|
<img className={css.ReactionImg} src={customSrc} alt={reaction} />
|
|
) : (
|
|
<Text as="span" size="Inherit" truncate>
|
|
{reaction}
|
|
</Text>
|
|
);
|
|
|
|
// Stationery: fanned sticker stack. Everywhere else: compact emoji + count.
|
|
if (!stationery) {
|
|
return (
|
|
<Box
|
|
as="button"
|
|
className={classNames(css.Reaction, className)}
|
|
alignItems="Center"
|
|
shrink="No"
|
|
gap="200"
|
|
data-reaction=""
|
|
aria-label={`${reaction}, ${count}`}
|
|
{...props}
|
|
ref={ref}
|
|
>
|
|
<Text className={css.ReactionText} as="span" size="T400">
|
|
{emoji}
|
|
</Text>
|
|
<Text as="span" size="T300" data-reaction-count="">
|
|
{count}
|
|
</Text>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
const stackCount = Math.min(Math.max(count, 1), MAX_STICKER_STACK);
|
|
const showCount = count > 2;
|
|
|
|
return (
|
|
<Box
|
|
as="button"
|
|
className={classNames(css.Reaction, className)}
|
|
alignItems="Center"
|
|
shrink="No"
|
|
gap="200"
|
|
data-reaction=""
|
|
data-reaction-stack={stackCount}
|
|
aria-label={`${reaction}, ${count}`}
|
|
{...props}
|
|
ref={ref}
|
|
>
|
|
<span className={css.ReactionStack} data-reaction-stack-layer="">
|
|
{Array.from({ length: stackCount }, (_, i) => {
|
|
const layer = stackCount - 1 - i;
|
|
return (
|
|
<Text
|
|
key={layer}
|
|
className={css.ReactionSticker}
|
|
as="span"
|
|
size="T400"
|
|
data-reaction-sticker=""
|
|
style={{
|
|
['--sticker-i' as string]: layer,
|
|
zIndex: i + 1,
|
|
}}
|
|
aria-hidden={i < stackCount - 1 ? true : undefined}
|
|
>
|
|
{isCustomEmoji ? (
|
|
<img
|
|
className={css.ReactionImg}
|
|
src={customSrc}
|
|
alt={i === stackCount - 1 ? reaction : ''}
|
|
/>
|
|
) : (
|
|
<Text as="span" size="Inherit" truncate>
|
|
{reaction}
|
|
</Text>
|
|
)}
|
|
</Text>
|
|
);
|
|
})}
|
|
</span>
|
|
{showCount && (
|
|
<Text as="span" size="T300" data-reaction-count="">
|
|
{count}
|
|
</Text>
|
|
)}
|
|
</Box>
|
|
);
|
|
});
|
|
|
|
type ReactionTooltipMsgProps = {
|
|
room: Room;
|
|
reaction: string;
|
|
events: MatrixEvent[];
|
|
};
|
|
|
|
export function ReactionTooltipMsg({ room, reaction, events }: ReactionTooltipMsgProps) {
|
|
const shortCodeEvt = events.find(eventWithShortcode);
|
|
const shortcode =
|
|
shortCodeEvt?.getContent().shortcode ??
|
|
getShortcodeFor(getHexcodeForEmoji(reaction)) ??
|
|
reaction;
|
|
const names = events.map(
|
|
(ev: MatrixEvent) =>
|
|
getMemberDisplayName(room, ev.getSender() ?? 'Unknown') ??
|
|
getMxIdLocalPart(ev.getSender() ?? 'Unknown') ??
|
|
'Unknown'
|
|
);
|
|
|
|
return (
|
|
<>
|
|
{names.length === 1 && <b>{names[0]}</b>}
|
|
{names.length === 2 && (
|
|
<>
|
|
<b>{names[0]}</b>
|
|
<Text as="span" size="Inherit" priority="300">
|
|
{' and '}
|
|
</Text>
|
|
<b>{names[1]}</b>
|
|
</>
|
|
)}
|
|
{names.length === 3 && (
|
|
<>
|
|
<b>{names[0]}</b>
|
|
<Text as="span" size="Inherit" priority="300">
|
|
{', '}
|
|
</Text>
|
|
<b>{names[1]}</b>
|
|
<Text as="span" size="Inherit" priority="300">
|
|
{' and '}
|
|
</Text>
|
|
<b>{names[2]}</b>
|
|
</>
|
|
)}
|
|
{names.length > 3 && (
|
|
<>
|
|
<b>{names[0]}</b>
|
|
<Text as="span" size="Inherit" priority="300">
|
|
{', '}
|
|
</Text>
|
|
<b>{names[1]}</b>
|
|
<Text as="span" size="Inherit" priority="300">
|
|
{', '}
|
|
</Text>
|
|
<b>{names[2]}</b>
|
|
<Text as="span" size="Inherit" priority="300">
|
|
{' and '}
|
|
</Text>
|
|
<b>{names.length - 3} others</b>
|
|
</>
|
|
)}
|
|
<Text as="span" size="Inherit" priority="300">
|
|
{' reacted with '}
|
|
</Text>
|
|
:<b>{shortcode}</b>:
|
|
</>
|
|
);
|
|
}
|