mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-04 22:40:29 +03:00
A bit more idiomatic than my previous attempt
This commit is contained in:
parent
3ed8260877
commit
2d66641167
1 changed files with 97 additions and 261 deletions
|
|
@ -36,6 +36,7 @@ import { MatrixEvent, Room } 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';
|
||||
import { useLongPress } from 'use-long-press';
|
||||
import {
|
||||
AvatarBase,
|
||||
BubbleLayout,
|
||||
|
|
@ -79,6 +80,9 @@ import { StateEvent } from '../../../../types/matrix/room';
|
|||
import { getTagIconSrc, PowerLevelTag } from '../../../hooks/usePowerLevelTags';
|
||||
import { PowerIcon } from '../../../components/power';
|
||||
import colorMXID from '../../../../util/colorMXID';
|
||||
import { MessageDropdownMenu, MessageOptionsMenu } from '../MessageOptionsMenu';
|
||||
import { BottomSheetMenu } from '../MobileClickMenu';
|
||||
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
|
||||
|
||||
export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void;
|
||||
|
||||
|
|
@ -714,12 +718,15 @@ export const Message = as<'div', MessageProps>(
|
|||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const senderId = mEvent.getSender() ?? '';
|
||||
const [hover, setHover] = useState(false);
|
||||
const { hoverProps } = useHover({ onHoverChange: setHover });
|
||||
const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover });
|
||||
const { hoverProps, isHovered } = useHover({});
|
||||
const { focusWithinProps, isFocusWithin } = useFocusWithin({});
|
||||
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
|
||||
const [emojiBoardAnchor, setEmojiBoardAnchor] = useState<RectCords>();
|
||||
|
||||
const screenSize = useScreenSizeContext();
|
||||
const isMobile = screenSize === ScreenSize.Mobile;
|
||||
const [isDesktopOptionsActive, setDesktopOptionsActive] = useState(false);
|
||||
const showDesktopOptions = !isMobile && (isHovered || isFocusWithin || isDesktopOptionsActive);
|
||||
const [isMobileSheetOpen, setMobileSheetOpen] = useState(false);
|
||||
const senderDisplayName =
|
||||
getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId;
|
||||
const senderAvatarMxc = getMemberAvatarMxc(room, senderId);
|
||||
|
|
@ -733,6 +740,18 @@ export const Message = as<'div', MessageProps>(
|
|||
|
||||
const usernameColor = legacyUsernameColor ? colorMXID(senderId) : tagColor;
|
||||
|
||||
const longPressBinder = useLongPress(
|
||||
() => {
|
||||
if (isMobile) {
|
||||
setMobileSheetOpen(true);
|
||||
}
|
||||
},
|
||||
{
|
||||
threshold: 400,
|
||||
cancelOnMovement: true,
|
||||
}
|
||||
);
|
||||
|
||||
const headerJSX = !collapse && (
|
||||
<Box
|
||||
gap="300"
|
||||
|
|
@ -760,7 +779,7 @@ export const Message = as<'div', MessageProps>(
|
|||
{tagIconSrc && <PowerIcon size="100" iconSrc={tagIconSrc} />}
|
||||
</Box>
|
||||
<Box shrink="No" gap="100">
|
||||
{messageLayout === MessageLayout.Modern && hover && (
|
||||
{messageLayout === MessageLayout.Modern && isHovered && (
|
||||
<>
|
||||
<Text as="span" size="T200" priority="300">
|
||||
{senderId}
|
||||
|
|
@ -833,263 +852,80 @@ export const Message = as<'div', MessageProps>(
|
|||
});
|
||||
};
|
||||
|
||||
const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
|
||||
setMenuAnchor(target.getBoundingClientRect());
|
||||
};
|
||||
|
||||
const closeMenu = () => {
|
||||
setMenuAnchor(undefined);
|
||||
};
|
||||
|
||||
const handleOpenEmojiBoard: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
|
||||
setEmojiBoardAnchor(target.getBoundingClientRect());
|
||||
};
|
||||
const handleAddReactions: MouseEventHandler<HTMLButtonElement> = () => {
|
||||
const rect = menuAnchor;
|
||||
closeMenu();
|
||||
// open it with timeout because closeMenu
|
||||
// FocusTrap will return focus from emojiBoard
|
||||
|
||||
setTimeout(() => {
|
||||
setEmojiBoardAnchor(rect);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
return (
|
||||
<MessageBase
|
||||
className={classNames(css.MessageBase, className)}
|
||||
tabIndex={0}
|
||||
space={messageSpacing}
|
||||
collapse={collapse}
|
||||
highlight={highlight}
|
||||
selected={!!menuAnchor || !!emojiBoardAnchor}
|
||||
{...props}
|
||||
{...hoverProps}
|
||||
{...focusWithinProps}
|
||||
ref={ref}
|
||||
>
|
||||
{!edit && (hover || !!menuAnchor || !!emojiBoardAnchor) && (
|
||||
<div className={css.MessageOptionsBase}>
|
||||
<Menu className={css.MessageOptionsBar} variant="SurfaceVariant">
|
||||
<Box gap="100">
|
||||
{canSendReaction && (
|
||||
<PopOut
|
||||
position="Bottom"
|
||||
align={emojiBoardAnchor?.width === 0 ? 'Start' : 'End'}
|
||||
offset={emojiBoardAnchor?.width === 0 ? 0 : undefined}
|
||||
anchor={emojiBoardAnchor}
|
||||
content={
|
||||
<EmojiBoard
|
||||
imagePackRooms={imagePackRooms ?? []}
|
||||
returnFocusOnDeactivate={false}
|
||||
allowTextCustomEmoji
|
||||
onEmojiSelect={(key) => {
|
||||
onReactionToggle(mEvent.getId()!, key);
|
||||
setEmojiBoardAnchor(undefined);
|
||||
}}
|
||||
onCustomEmojiSelect={(mxc, shortcode) => {
|
||||
onReactionToggle(mEvent.getId()!, mxc, shortcode);
|
||||
setEmojiBoardAnchor(undefined);
|
||||
}}
|
||||
requestClose={() => {
|
||||
setEmojiBoardAnchor(undefined);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
onClick={handleOpenEmojiBoard}
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
aria-pressed={!!emojiBoardAnchor}
|
||||
>
|
||||
<Icon src={Icons.SmilePlus} size="100" />
|
||||
</IconButton>
|
||||
</PopOut>
|
||||
)}
|
||||
<IconButton
|
||||
onClick={onReplyClick}
|
||||
data-event-id={mEvent.getId()}
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
>
|
||||
<Icon src={Icons.ReplyArrow} size="100" />
|
||||
</IconButton>
|
||||
{canEditEvent(mx, mEvent) && onEditId && (
|
||||
<IconButton
|
||||
onClick={() => onEditId(mEvent.getId())}
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
>
|
||||
<Icon src={Icons.Pencil} size="100" />
|
||||
</IconButton>
|
||||
)}
|
||||
<PopOut
|
||||
anchor={menuAnchor}
|
||||
position="Bottom"
|
||||
align={menuAnchor?.width === 0 ? 'Start' : 'End'}
|
||||
offset={menuAnchor?.width === 0 ? 0 : undefined}
|
||||
content={
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
onDeactivate: () => setMenuAnchor(undefined),
|
||||
clickOutsideDeactivates: true,
|
||||
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
|
||||
isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp',
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Menu>
|
||||
{canSendReaction && (
|
||||
<MessageQuickReactions
|
||||
onReaction={(key, shortcode) => {
|
||||
onReactionToggle(mEvent.getId()!, key, shortcode);
|
||||
closeMenu();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Box direction="Column" gap="100" className={css.MessageMenuGroup}>
|
||||
{canSendReaction && (
|
||||
<MenuItem
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.SmilePlus} />}
|
||||
radii="300"
|
||||
onClick={handleAddReactions}
|
||||
>
|
||||
<Text
|
||||
className={css.MessageMenuItemText}
|
||||
as="span"
|
||||
size="T300"
|
||||
truncate
|
||||
>
|
||||
Add Reaction
|
||||
</Text>
|
||||
</MenuItem>
|
||||
)}
|
||||
{relations && (
|
||||
<MessageAllReactionItem
|
||||
room={room}
|
||||
relations={relations}
|
||||
onClose={closeMenu}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.ReplyArrow} />}
|
||||
radii="300"
|
||||
data-event-id={mEvent.getId()}
|
||||
onClick={(evt: any) => {
|
||||
onReplyClick(evt);
|
||||
closeMenu();
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
className={css.MessageMenuItemText}
|
||||
as="span"
|
||||
size="T300"
|
||||
truncate
|
||||
>
|
||||
Reply
|
||||
</Text>
|
||||
</MenuItem>
|
||||
{canEditEvent(mx, mEvent) && onEditId && (
|
||||
<MenuItem
|
||||
size="300"
|
||||
after={<Icon size="100" src={Icons.Pencil} />}
|
||||
radii="300"
|
||||
data-event-id={mEvent.getId()}
|
||||
onClick={() => {
|
||||
onEditId(mEvent.getId());
|
||||
closeMenu();
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
className={css.MessageMenuItemText}
|
||||
as="span"
|
||||
size="T300"
|
||||
truncate
|
||||
>
|
||||
Edit Message
|
||||
</Text>
|
||||
</MenuItem>
|
||||
)}
|
||||
{!hideReadReceipts && (
|
||||
<MessageReadReceiptItem
|
||||
room={room}
|
||||
eventId={mEvent.getId() ?? ''}
|
||||
onClose={closeMenu}
|
||||
/>
|
||||
)}
|
||||
<MessageSourceCodeItem room={room} mEvent={mEvent} onClose={closeMenu} />
|
||||
<MessageCopyLinkItem room={room} mEvent={mEvent} onClose={closeMenu} />
|
||||
{canPinEvent && (
|
||||
<MessagePinItem room={room} mEvent={mEvent} onClose={closeMenu} />
|
||||
)}
|
||||
</Box>
|
||||
{((!mEvent.isRedacted() && canDelete) ||
|
||||
mEvent.getSender() !== mx.getUserId()) && (
|
||||
<>
|
||||
<Line size="300" />
|
||||
<Box direction="Column" gap="100" className={css.MessageMenuGroup}>
|
||||
{!mEvent.isRedacted() && canDelete && (
|
||||
<MessageDeleteItem
|
||||
room={room}
|
||||
mEvent={mEvent}
|
||||
onClose={closeMenu}
|
||||
/>
|
||||
)}
|
||||
{mEvent.getSender() !== mx.getUserId() && (
|
||||
<MessageReportItem
|
||||
room={room}
|
||||
mEvent={mEvent}
|
||||
onClose={closeMenu}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
</FocusTrap>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
variant="SurfaceVariant"
|
||||
size="300"
|
||||
radii="300"
|
||||
onClick={handleOpenMenu}
|
||||
aria-pressed={!!menuAnchor}
|
||||
>
|
||||
<Icon src={Icons.VerticalDots} size="100" />
|
||||
</IconButton>
|
||||
</PopOut>
|
||||
</Box>
|
||||
</Menu>
|
||||
</div>
|
||||
<>
|
||||
<MessageBase
|
||||
className={classNames(css.MessageBase, className)}
|
||||
tabIndex={0}
|
||||
space={messageSpacing}
|
||||
collapse={collapse}
|
||||
highlight={highlight}
|
||||
selected={isDesktopOptionsActive || isMobileSheetOpen}
|
||||
{...props}
|
||||
{...hoverProps}
|
||||
{...focusWithinProps}
|
||||
{...(isMobile ? longPressBinder() : {})}
|
||||
ref={ref}
|
||||
>
|
||||
{!edit && (isHovered || !!menuAnchor || !!emojiBoardAnchor) && (
|
||||
<MessageOptionsMenu
|
||||
mEvent={mEvent}
|
||||
room={room}
|
||||
mx={mx}
|
||||
relations={relations}
|
||||
imagePackRooms={imagePackRooms}
|
||||
canSendReaction={canSendReaction}
|
||||
canEdit={canEditEvent(mx, mEvent)}
|
||||
canDelete={canDelete}
|
||||
canPinEvent={canPinEvent}
|
||||
hideReadReceipts={hideReadReceipts}
|
||||
onReactionToggle={onReactionToggle}
|
||||
onReplyClick={onReplyClick}
|
||||
onEditId={onEditId}
|
||||
onActiveStateChange={setDesktopOptionsActive}
|
||||
/>
|
||||
)}
|
||||
{messageLayout === MessageLayout.Compact && (
|
||||
<CompactLayout before={headerJSX} onContextMenu={handleContextMenu}>
|
||||
{msgContentJSX}
|
||||
</CompactLayout>
|
||||
)}
|
||||
{messageLayout === MessageLayout.Bubble && (
|
||||
<BubbleLayout before={avatarJSX} onContextMenu={handleContextMenu}>
|
||||
{headerJSX}
|
||||
{msgContentJSX}
|
||||
</BubbleLayout>
|
||||
)}
|
||||
{messageLayout !== MessageLayout.Compact && messageLayout !== MessageLayout.Bubble && (
|
||||
<ModernLayout before={avatarJSX} onContextMenu={handleContextMenu}>
|
||||
{headerJSX}
|
||||
{msgContentJSX}
|
||||
</ModernLayout>
|
||||
)}
|
||||
</MessageBase>
|
||||
|
||||
{isMobile && (
|
||||
<BottomSheetMenu onClose={() => setMobileSheetOpen(false)} isOpen={isMobileSheetOpen}>
|
||||
<MessageDropdownMenu
|
||||
closeMenu={() => setMobileSheetOpen(false)}
|
||||
mEvent={mEvent}
|
||||
eventId={mEvent.getId()}
|
||||
room={room}
|
||||
mx={mx}
|
||||
relations={relations}
|
||||
canSendReaction={canSendReaction}
|
||||
canEdit={canEditEvent(mx, mEvent)}
|
||||
canDelete={canDelete || mEvent?.getSender() === mx.getUserId()}
|
||||
canPinEvent={canPinEvent}
|
||||
hideReadReceipts={hideReadReceipts}
|
||||
onReactionToggle={onReactionToggle}
|
||||
onReplyClick={onReplyClick}
|
||||
onEditId={onEditId}
|
||||
handleAddReactions={null}
|
||||
/>
|
||||
</BottomSheetMenu>
|
||||
)}
|
||||
{messageLayout === MessageLayout.Compact && (
|
||||
<CompactLayout before={headerJSX} onContextMenu={handleContextMenu}>
|
||||
{msgContentJSX}
|
||||
</CompactLayout>
|
||||
)}
|
||||
{messageLayout === MessageLayout.Bubble && (
|
||||
<BubbleLayout before={avatarJSX} onContextMenu={handleContextMenu}>
|
||||
{headerJSX}
|
||||
{msgContentJSX}
|
||||
</BubbleLayout>
|
||||
)}
|
||||
{messageLayout !== MessageLayout.Compact && messageLayout !== MessageLayout.Bubble && (
|
||||
<ModernLayout before={avatarJSX} onContextMenu={handleContextMenu}>
|
||||
{headerJSX}
|
||||
{msgContentJSX}
|
||||
</ModernLayout>
|
||||
)}
|
||||
</MessageBase>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
@ -1155,7 +991,7 @@ export const Event = as<'div', EventProps>(
|
|||
highlight={highlight}
|
||||
selected={!!menuAnchor}
|
||||
{...props}
|
||||
{...hoverProps}
|
||||
{...hoverProps} // Impacts hover
|
||||
{...focusWithinProps}
|
||||
ref={ref}
|
||||
>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue