/* * Copyright 2024 New Vector Ltd. * Copyright 2024 The Matrix.org Foundation C.I.C. * * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial * Please see LICENSE files in the repository root for full details. */ import React, { type HTMLProps, type JSX, useContext, useState } from "react"; import { type IContent, M_POLL_START, type MatrixEvent, MatrixEventEvent, MsgType } from "matrix-js-sdk/src/matrix"; import classNames from "classnames"; import { _t } from "../../../languageHandler"; import { MessagePreviewStore } from "../../../stores/message-preview"; import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { useTypedEventEmitter } from "../../../hooks/useEventEmitter.ts"; /** * The props for the {@link EventPreview} component. */ interface Props extends HTMLProps { /** * The event to display the preview for */ mxEvent: MatrixEvent; } /** * A component that displays a preview for the given event. * Wraps both `useEventPreview` & `EventPreviewTile`. */ export function EventPreview({ mxEvent, className, ...props }: Props): JSX.Element | null { const preview = useEventPreview(mxEvent); if (!preview) return null; return ; } /** * The props for the {@link EventPreviewTile} component. */ interface EventPreviewTileProps extends HTMLProps { /** * The preview to display */ preview: Preview; } /** * A component that displays a preview given the output from `useEventPreview`. */ export function EventPreviewTile({ preview: [preview, prefix], className, ...props }: EventPreviewTileProps): JSX.Element | null { const classes = classNames("mx_EventPreview", className); if (!prefix) return ( {preview} ); return ( {_t( "event_preview|preview", { prefix, preview, }, { bold: (sub) => {sub}, }, )} ); } type Preview = [preview: string, prefix: string | null]; /** * Hooks to generate a preview for the event. * @param mxEvent */ export function useEventPreview(mxEvent: MatrixEvent | undefined): Preview | null { const cli = useContext(MatrixClientContext); // track the content as a means to regenerate the preview upon edits & decryption const [content, setContent] = useState(mxEvent?.getContent()); useTypedEventEmitter(mxEvent ?? undefined, MatrixEventEvent.Replaced, () => { setContent(mxEvent!.getContent()); }); const awaitDecryption = mxEvent?.shouldAttemptDecryption() || mxEvent?.isBeingDecrypted(); useTypedEventEmitter(awaitDecryption ? (mxEvent ?? undefined) : undefined, MatrixEventEvent.Decrypted, () => { setContent(mxEvent!.getContent()); }); return useAsyncMemo( async () => { if (!mxEvent || mxEvent.isRedacted() || mxEvent.isDecryptionFailure()) return null; await cli.decryptEventIfNeeded(mxEvent); return [ MessagePreviewStore.instance.generatePreviewForEvent(mxEvent), getPreviewPrefix(mxEvent.getType(), content?.msgtype as MsgType), ]; }, [mxEvent, content], null, ); } /** * Get the prefix for the preview based on the type and the message type. * @param type * @param msgType */ function getPreviewPrefix(type: string, msgType: MsgType): string | null { switch (type) { case M_POLL_START.name: return _t("event_preview|prefix|poll"); default: } switch (msgType) { case MsgType.Audio: return _t("event_preview|prefix|audio"); case MsgType.Image: return _t("event_preview|prefix|image"); case MsgType.Video: return _t("event_preview|prefix|video"); case MsgType.File: return _t("event_preview|prefix|file"); default: return null; } }