diff --git a/.changeset/improved_message_history_ui.md b/.changeset/improved_message_history_ui.md new file mode 100644 index 00000000..da94e930 --- /dev/null +++ b/.changeset/improved_message_history_ui.md @@ -0,0 +1,5 @@ +--- +default: minor +--- + +Added hover menu inside Message Version Pop-out. diff --git a/src/app/components/event-history/EventHistory.css.ts b/src/app/components/event-history/EventHistory.css.ts index e50d71c3..29946b79 100644 --- a/src/app/components/event-history/EventHistory.css.ts +++ b/src/app/components/event-history/EventHistory.css.ts @@ -1,5 +1,5 @@ import { style } from '@vanilla-extract/css'; -import { DefaultReset, color, config } from 'folds'; +import { DefaultReset, color, config, toRem } from 'folds'; export const EventHistory = style([ DefaultReset, @@ -22,6 +22,7 @@ export const Content = style({ export const EventItem = style({ padding: `${config.space.S200} ${config.space.S200}`, height: 'unset', + width: '100%', borderRadius: '5px', border: '2px hidden', backgroundColor: 'inherit', @@ -31,3 +32,25 @@ export const EventItem = style({ }, }, }); +export const MessageOptionsBase = style([ + DefaultReset, + { + position: 'absolute', + top: toRem(-30), + right: 0, + zIndex: 1, + }, +]); +export const MessageOptionsBar = style([ + DefaultReset, + { + padding: config.space.S100, + }, +]); +export const MenuOptions = style({ + position: 'absolute', + right: '0', + top: '0', + display: 'flex', + transform: 'translateY(-75%)', +}); diff --git a/src/app/components/event-history/EventHistory.tsx b/src/app/components/event-history/EventHistory.tsx index ffdefa1e..2bc87a23 100644 --- a/src/app/components/event-history/EventHistory.tsx +++ b/src/app/components/event-history/EventHistory.tsx @@ -6,6 +6,7 @@ import { Icon, IconButton, Icons, + Menu, MenuItem, Scroll, Text, @@ -13,7 +14,7 @@ import { color, config, } from 'folds'; -import { MatrixEvent, Room } from '$types/matrix-sdk'; +import { IContent, MatrixEvent, Room } from '$types/matrix-sdk'; import { getMemberDisplayName } from '$utils/room'; import { getMxIdLocalPart } from '$utils/matrix'; import { useMatrixClient } from '$hooks/useMatrixClient'; @@ -21,17 +22,23 @@ import { useMediaAuthentication } from '$hooks/useMediaAuthentication'; import { useOpenUserRoomProfile } from '$state/hooks/userRoomProfile'; import { useSpaceOptionally } from '$hooks/useSpace'; import { getMouseEventCords } from '$utils/dom'; -import { useAtomValue } from 'jotai'; +import { useAtomValue, useSetAtom } from 'jotai'; import { nicknamesAtom } from '$state/nicknames'; import { UserAvatar } from '$components/user-avatar'; import { RenderBody, Time } from '$components/message'; import { useSetting } from '$state/hooks/settings'; import { settingsAtom } from '$state/settings'; -import { useMemo } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { getReactCustomHtmlParser, LINKIFY_OPTS } from '$plugins/react-custom-html-parser'; import { Opts as LinkifyOpts } from 'linkifyjs'; import { HTMLReactParserOptions } from 'html-react-parser'; import { useSpoilerClickHandler } from '$hooks/useSpoilerClickHandler'; +import { modalAtom, ModalType } from '$state/modal'; +import { roomIdToReplyDraftAtomFamily } from '$state/room/roomInputDrafts'; +import { useRoomPermissions } from '$hooks/useRoomPermissions'; +import { useRoomCreators } from '$hooks/useRoomCreators'; +import { usePowerLevelsContext } from '$hooks/usePowerLevels'; +import { MessageEvent } from '$types/matrix/room'; import * as css from './EventHistory.css'; export type EventHistoryProps = { @@ -71,6 +78,139 @@ export const EventHistory = as<'div', EventHistoryProps>( }), [linkifyOpts, mEvents, mx, spoilerClickHandler, useAuthentication] ); + const powerLevels = usePowerLevelsContext(); + const creators = useRoomCreators(room); + const permissions = useRoomPermissions(creators, powerLevels); + const canRedact = permissions.action('redact', mx.getSafeUserId()); + const canDeleteOwn = permissions.event(MessageEvent.RoomRedaction, mx.getSafeUserId()); + const canDelete = canRedact || (canDeleteOwn && mEvents[0].getSender() === mx.getUserId()); + + const setReplyDraft = useSetAtom(roomIdToReplyDraftAtomFamily(room.roomId)); + const triggerReply = useCallback( + (replyId: string, startThread = false) => { + const replyEvt = room.findEventById(replyId); + if (!replyEvt) return; + const content: IContent = replyEvt.getOriginalContent(); + const body = content?.['m.new_content']?.body ?? content?.body ?? ''; + const formattedBody = + content?.['m.new_content']?.formatted_body ?? content?.formatted_body ?? ''; + const { 'm.relates_to': relation } = startThread + ? { 'm.relates_to': { rel_type: 'm.thread', event_id: replyId } } + : replyEvt.getWireContent(); + const senderId = replyEvt.getSender(); + if (senderId) { + if (typeof body === 'string') { + setReplyDraft({ + userId: senderId, + eventId: replyId, + body, + formattedBody, + relation, + }); + } else { + setReplyDraft({ + userId: senderId, + eventId: replyId, + body: '', + formattedBody: '', + relation, + }); + } + } + }, + [room, setReplyDraft] + ); + + function MenuOptions({ mEvent }: { mEvent: MatrixEvent }) { + const setModal = useSetAtom(modalAtom); + return ( + + } + radii="300" + fill="None" + variant="Secondary" + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + if (mEvent.event.event_id) { + triggerReply(mEvent.event.event_id, false); + requestClose(); + } + }} + /> + } + radii="300" + fill="None" + variant="Secondary" + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + if (mEvent.event.event_id) { + triggerReply(mEvent.event.event_id, true); + requestClose(); + } + }} + /> + {canDelete && ( + } + radii="300" + fill="None" + variant="Critical" + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + setModal({ + type: ModalType.Delete, + room, + mEvent, + }); + }} + /> + )} + + ); + } + + function EventItem({ mEvent, EventContent }: { mEvent: MatrixEvent; EventContent: IContent }) { + const [isHovered, setIsHovered] = useState(false); + return ( + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + onClick={() => setIsHovered(!isHovered)} + > + + + {isHovered && } + + ); + } + return ( ( return ( <>
- - + ); })} diff --git a/src/app/components/message/modals/MessageEditHistory.tsx b/src/app/components/message/modals/MessageEditHistory.tsx index 32c851e0..9581aa41 100644 --- a/src/app/components/message/modals/MessageEditHistory.tsx +++ b/src/app/components/message/modals/MessageEditHistory.tsx @@ -7,7 +7,15 @@ import { modalAtom, ModalType } from '$state/modal'; import * as css from '$features/room/message/styles.css'; import { EventHistory } from '$components/event-history'; -export function MessageEditHistoryItem({ room, mEvent }: { room: Room; mEvent: MatrixEvent }) { +export function MessageEditHistoryItem({ + room, + mEvent, + closeMenu, +}: { + room: Room; + mEvent: MatrixEvent; + closeMenu: () => void; +}) { const setModal = useSetAtom(modalAtom); return ( @@ -18,6 +26,7 @@ export function MessageEditHistoryItem({ room, mEvent }: { room: Room; mEvent: M onClick={(e: MouseEvent) => { e.preventDefault(); e.stopPropagation(); + closeMenu(); setModal({ type: ModalType.EditHistory, room, diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx index 3f835a35..0bf4317e 100644 --- a/src/app/features/room/message/Message.tsx +++ b/src/app/features/room/message/Message.tsx @@ -912,7 +912,13 @@ function MessageInternal( {!hideReadReceipts && ( )} - {isEdited && } + {isEdited && ( + + )} {showDeveloperTools && ( )} @@ -1217,7 +1223,13 @@ export const Event = as<'div', EventProps>( {!hideReadReceipts && ( )} - {isEdited && } + {isEdited && ( + + )} {showDeveloperTools && ( )}