CI / Build & Quality Checks (push) Successful in 1m46s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 8s
CI / Trigger Desktop Build (push) Successful in 11s
CI / Playwright smoke (e2e) (push) Successful in 9m44s
A reply to an image, video or sticker used to quote just the filename. The quote (timeline) and the composer's reply-draft preview now carry a 36 px thumbnail from the event's own thumbnail, decrypted for E2EE media via the same hook the gallery uses — never the full-size file. Clicking still jumps to the original. useDecryptedMediaUrl and getThumbMxc moved out of MediaGallery into hooks/ and utils/ so components/message can use them without a cycle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
import React from 'react';
|
|
import { Icon, Icons, config, toRem } from 'folds';
|
|
import { MatrixEvent, MsgType } from 'matrix-js-sdk';
|
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
|
import { useDecryptedMediaUrl } from '../../hooks/useDecryptedMediaUrl';
|
|
import { getThumbMxc } from '../../utils/mediaThumb';
|
|
import { MessageEvent } from '../../../types/matrix/room';
|
|
|
|
const SIZE = 36;
|
|
|
|
/** Whether a reply quote for this event should carry a thumbnail. */
|
|
export const hasReplyMedia = (mEvent: MatrixEvent | null | undefined): boolean => {
|
|
if (!mEvent || mEvent.isRedacted()) return false;
|
|
if (mEvent.getType() === MessageEvent.Sticker) return true;
|
|
const msgtype = mEvent.getContent().msgtype;
|
|
return msgtype === MsgType.Image || msgtype === MsgType.Video;
|
|
};
|
|
|
|
/**
|
|
* [Gitea #151] 36 px thumbnail in a reply quote for an image/video/sticker.
|
|
* Uses the event's own thumbnail (decrypting it for E2EE media), never the
|
|
* full-size file.
|
|
*/
|
|
export function ReplyMediaThumb({ mEvent }: { mEvent: MatrixEvent }) {
|
|
const mx = useMatrixClient();
|
|
const useAuthentication = useMediaAuthentication();
|
|
const content = mEvent.getContent();
|
|
const isVideo = content.msgtype === MsgType.Video;
|
|
const thumbMxc = getThumbMxc(mEvent);
|
|
const info = content.info as Record<string, unknown> | undefined;
|
|
const encInfo = content.file
|
|
? ((info?.thumbnail_file as typeof content.file | undefined) ?? content.file)
|
|
: undefined;
|
|
const mimeType =
|
|
(info?.thumbnail_info as { mimetype?: string } | undefined)?.mimetype ??
|
|
(info?.mimetype as string | undefined);
|
|
const media = useDecryptedMediaUrl(mx, thumbMxc, encInfo, useAuthentication, mimeType);
|
|
|
|
return (
|
|
<span
|
|
aria-hidden
|
|
style={{
|
|
position: 'relative',
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
width: toRem(SIZE),
|
|
height: toRem(SIZE),
|
|
flexShrink: 0,
|
|
borderRadius: config.radii.R300,
|
|
overflow: 'hidden',
|
|
background: 'rgba(127, 127, 127, 0.15)',
|
|
}}
|
|
>
|
|
{media.status === 'ok' ? (
|
|
<img
|
|
src={media.url}
|
|
alt=""
|
|
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
|
|
/>
|
|
) : (
|
|
<Icon size="100" src={isVideo ? Icons.Play : Icons.Photo} />
|
|
)}
|
|
{isVideo && media.status === 'ok' && (
|
|
<Icon
|
|
size="50"
|
|
src={Icons.Play}
|
|
filled
|
|
style={{ position: 'absolute', color: 'white', filter: 'drop-shadow(0 0 2px black)' }}
|
|
/>
|
|
)}
|
|
</span>
|
|
);
|
|
}
|