/* Copyright 2024 New Vector Ltd. Copyright 2021 The Matrix.org Foundation C.I.C. Copyright 2017 Travis Ralston SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ import React, { JSX, useCallback, useState } from "react"; import { EventTimeline, EventType, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; import { IconButton, Menu, MenuItem, Separator, Tooltip } from "@vector-im/compound-web"; import ViewIcon from "@vector-im/compound-design-tokens/assets/web/icons/visibility-on"; import UnpinIcon from "@vector-im/compound-design-tokens/assets/web/icons/unpin"; import ForwardIcon from "@vector-im/compound-design-tokens/assets/web/icons/forward"; import TriggerIcon from "@vector-im/compound-design-tokens/assets/web/icons/overflow-horizontal"; import DeleteIcon from "@vector-im/compound-design-tokens/assets/web/icons/delete"; import ThreadIcon from "@vector-im/compound-design-tokens/assets/web/icons/threads"; import classNames from "classnames"; import dis from "../../../dispatcher/dispatcher"; import { Action } from "../../../dispatcher/actions"; import MessageEvent from "../messages/MessageEvent"; import MemberAvatar from "../avatars/MemberAvatar"; import { _t } from "../../../languageHandler"; import { getUserNameColorClass } from "../../../utils/FormattingUtils"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; import { useRoomState } from "../../../hooks/useRoomState"; import { isContentActionable } from "../../../utils/EventUtils"; import { getForwardableEvent } from "../../../events"; import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload"; import { createRedactEventDialog } from "../dialogs/ConfirmRedactDialog"; import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; import PinningUtils from "../../../utils/PinningUtils.ts"; import PosthogTrackers from "../../../PosthogTrackers.ts"; const AVATAR_SIZE = "32px"; /** * Properties for {@link PinnedEventTile}. */ interface PinnedEventTileProps { /** * The event to display. */ event: MatrixEvent; /** * The permalink creator to use. */ permalinkCreator: RoomPermalinkCreator; /** * The room the event is in. */ room: Room; } /** * A pinned event tile. */ export function PinnedEventTile({ event, room, permalinkCreator }: PinnedEventTileProps): JSX.Element { const sender = event.getSender(); if (!sender) { throw new Error("Pinned event unexpectedly has no sender"); } const isInThread = Boolean(event.threadRootId); const displayThreadInfo = !event.isThreadRoot && isInThread; return (
{event.sender?.name || sender}
{}} // we need to give this, apparently permalinkCreator={permalinkCreator} replacingEventId={event.replacingEventId()} /> {displayThreadInfo && (
{_t( "right_panel|pinned_messages|reply_thread", {}, { link: (sub) => ( ), }, )}
)}
); } /** * Properties for {@link PinMenu}. */ interface PinMenuProps extends PinnedEventTileProps {} /** * A popover menu with actions on the pinned event */ function PinMenu({ event, room, permalinkCreator }: PinMenuProps): JSX.Element { const [open, setOpen] = useState(false); const matrixClient = useMatrixClientContext(); /** * View the event in the timeline. */ const onViewInTimeline = useCallback(() => { PosthogTrackers.trackInteraction("PinnedMessageListViewTimeline"); dis.dispatch({ action: Action.ViewRoom, event_id: event.getId(), highlighted: true, room_id: event.getRoomId(), metricsTrigger: undefined, // room doesn't change }); }, [event]); /** * Whether the client can unpin the event. * If the room state change, we want to check again the permission */ const canUnpin = useRoomState(room, () => PinningUtils.canUnpin(matrixClient, event)); /** * Unpin the event. * @param event */ const onUnpin = useCallback(async (): Promise => { await PinningUtils.pinOrUnpinEvent(matrixClient, event); PosthogTrackers.trackPinUnpinMessage("Unpin", "MessagePinningList"); }, [event, matrixClient]); const contentActionable = isContentActionable(event); // Get the forwardable event for the given event const forwardableEvent = contentActionable && getForwardableEvent(event, matrixClient); /** * Open the forward dialog. */ const onForward = useCallback(() => { if (forwardableEvent) { dis.dispatch({ action: Action.OpenForwardDialog, event: forwardableEvent, permalinkCreator: permalinkCreator, }); } }, [forwardableEvent, permalinkCreator]); /** * Whether the client can redact the event. */ const canRedact = room .getLiveTimeline() .getState(EventTimeline.FORWARDS) ?.maySendRedactionForEvent(event, matrixClient.getSafeUserId()) && event.getType() !== EventType.RoomServerAcl && event.getType() !== EventType.RoomEncryption; /** * Redact the event. */ const onRedact = useCallback( (): void => createRedactEventDialog({ mxEvent: event, }), [event], ); return ( } > {canUnpin && } {forwardableEvent && } {canRedact && ( <> )} ); }