feat: replace Embed.tsx with AttachmentRenderer + EmbedRenderer in Message

Switches Message.tsx from client-side URL metadata fetching (old Embed.tsx)
to server-driven embed objects via message.embeds[]. Delegates attachment
rendering to AttachmentRenderer. Removes firstUrl extraction and urlRegex.
This commit is contained in:
Jannis Braun
2026-03-20 23:47:16 +01:00
parent 5a670bc6ad
commit 95762a1974
3 changed files with 14 additions and 131 deletions
+12 -48
View File
@@ -10,7 +10,8 @@ import { useAuthStore } from '../../stores/authStore';
import { useChatStore } from '../../stores/chatStore';
import { useSpaceStore } from '../../stores/spaceStore';
import { useUIStore } from '../../stores/uiStore';
import { Embed } from './Embed';
import { AttachmentRenderer } from './AttachmentRenderer';
import { EmbedRenderer } from './EmbedRenderer';
import { Username } from '../ui/Username';
import { EmojiPicker } from './EmojiPicker';
import { hasPermissionBit, PermissionBits } from '../../utils/permissions';
@@ -73,7 +74,6 @@ export function Message({ message, isCompact, isFirstInGroup, previousMessageId
const editMessage = useChatStore((s) => s.editMessage);
const deleteMessage = useChatStore((s) => s.deleteMessage);
const members = useSpaceStore((s) => s.members);
const openImagePreview = useUIStore((s) => s.openImagePreview);
const openUserProfile = useUIStore((s) => s.openUserProfile);
const channelKey = message.channelId || (message as any).dmChannelId;
@@ -131,9 +131,6 @@ export function Message({ message, isCompact, isFirstInGroup, previousMessageId
const isGifOnly = isGifOnlyMessage(message.content);
const urlRegex = /(https?:\/\/[^\s]+)/g;
const firstUrl = isGifOnly ? null : message.content?.match(urlRegex)?.[0];
// Close reaction picker on outside click
useEffect(() => {
if (!showReactionPicker) return;
@@ -362,55 +359,22 @@ export function Message({ message, isCompact, isFirstInGroup, previousMessageId
)}
{/* Embeds */}
{!isEditing && firstUrl && <Embed url={firstUrl} />}
{!isEditing && message.embeds && message.embeds.length > 0 && (
<div className="flex flex-col gap-2 mt-1">
{message.embeds.map((embed) => (
<EmbedRenderer key={embed.id} embed={embed} />
))}
</div>
)}
</>
)}
{/* Attachments */}
{message.attachments && message.attachments.length > 0 && (
<div className="mt-1 grid gap-2">
{message.attachments.map((att) => {
const isImage = att.mimetype.startsWith('image/');
const attUrl = att.filename.startsWith('http') || att.filename.startsWith('/') ? att.filename : `/api/uploads/${att.filename}`;
const thumbUrl = att.thumbnailFilename
? (att.thumbnailFilename.startsWith('http') || att.thumbnailFilename.startsWith('/') ? att.thumbnailFilename : `/api/uploads/${att.thumbnailFilename}`)
: null;
if (isImage) {
return (
<div key={att.id} className="max-w-fit mt-1 rounded-lg overflow-hidden border border-white/[0.06]">
<img
src={thumbUrl ?? attUrl}
alt={att.originalName}
className="max-w-full max-h-[350px] object-contain cursor-pointer hover:brightness-95 transition-all"
onClick={() => openImagePreview(attUrl)}
loading="lazy"
/>
</div>
);
}
return (
<a
key={att.id}
href={attUrl}
download={att.originalName}
className="flex items-center gap-3 p-4 bg-surface-channel/50 rounded-lg border border-border-hard hover:bg-interactive-hover transition-all max-w-[400px] mt-1 group/att"
>
<div className="p-2 bg-surface-base rounded text-txt-tertiary group-hover/att:text-txt-primary transition-colors">
<svg className="w-8 h-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 10v6m0 0l-3-3m3 3l3-3M3 17V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" />
</svg>
</div>
<div className="min-w-0">
<p className="text-txt-link text-[15px] font-medium truncate hover:underline">{att.originalName}</p>
<p className="text-[12px] text-txt-tertiary font-medium">
{att.size < 1024 ? `${att.size} B` :
att.size < 1048576 ? `${(att.size / 1024).toFixed(1)} KB` :
`${(att.size / 1048576).toFixed(1)} MB`}
</p>
</div>
</a>
);
})}
{message.attachments.map((att) => (
<AttachmentRenderer key={att.id} attachment={att} />
))}
</div>
)}