From 95762a1974fedeb110417ab2737f8347e130601d Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Fri, 20 Mar 2026 23:47:16 +0100 Subject: [PATCH] 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. --- packages/web/src/components/chat/Embed.tsx | 81 ------------------- packages/web/src/components/chat/Message.tsx | 60 +++----------- .../web/src/components/chat/MessageList.tsx | 4 +- 3 files changed, 14 insertions(+), 131 deletions(-) delete mode 100644 packages/web/src/components/chat/Embed.tsx diff --git a/packages/web/src/components/chat/Embed.tsx b/packages/web/src/components/chat/Embed.tsx deleted file mode 100644 index 79628231..00000000 --- a/packages/web/src/components/chat/Embed.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { useAuthStore } from '../../stores/authStore'; - -interface EmbedProps { - url: string; -} - -interface Metadata { - title?: string; - description?: string; - image?: string; - siteName?: string; -} - -export function Embed({ url }: EmbedProps) { - const [metadata, setMetadata] = useState(null); - const [isLoading, setIsLoading] = useState(true); - - useEffect(() => { - const controller = new AbortController(); - - const token = useAuthStore.getState().token; - fetch(`/api/utils/metadata?url=${encodeURIComponent(url)}`, { - headers: { - 'Authorization': `Bearer ${token}` - }, - signal: controller.signal, - }) - .then(res => res.json()) - .then(data => { - if (data.title) { - setMetadata(data); - } - setIsLoading(false); - }) - .catch((err) => { - if (err instanceof DOMException && err.name === 'AbortError') return; - setIsLoading(false); - }); - - return () => { controller.abort(); }; - }, [url]); - - if (isLoading || !metadata) return null; - - return ( -
-
- {metadata.siteName && ( -
- {metadata.siteName} -
- )} - {metadata.title && ( - - {metadata.title} - - )} - {metadata.description && ( -
- {metadata.description} -
- )} -
- {metadata.image && ( -
- -
- )} -
- ); -} diff --git a/packages/web/src/components/chat/Message.tsx b/packages/web/src/components/chat/Message.tsx index 730fcbd8..4965bb5d 100644 --- a/packages/web/src/components/chat/Message.tsx +++ b/packages/web/src/components/chat/Message.tsx @@ -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 && } + {!isEditing && message.embeds && message.embeds.length > 0 && ( +
+ {message.embeds.map((embed) => ( + + ))} +
+ )} )} {/* Attachments */} {message.attachments && message.attachments.length > 0 && (
- {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 ( -
- {att.originalName} openImagePreview(attUrl)} - loading="lazy" - /> -
- ); - } - return ( - -
- - - -
-
-

{att.originalName}

-

- {att.size < 1024 ? `${att.size} B` : - att.size < 1048576 ? `${(att.size / 1024).toFixed(1)} KB` : - `${(att.size / 1048576).toFixed(1)} MB`} -

-
-
- ); - })} + {message.attachments.map((att) => ( + + ))}
)} diff --git a/packages/web/src/components/chat/MessageList.tsx b/packages/web/src/components/chat/MessageList.tsx index 78743993..63338ead 100644 --- a/packages/web/src/components/chat/MessageList.tsx +++ b/packages/web/src/components/chat/MessageList.tsx @@ -134,7 +134,7 @@ export function MessageList({ channelId, jumpToMessageId, onJumpComplete }: Mess if (el) { el.scrollIntoView({ block: 'start' }); const dist = container.scrollHeight - container.scrollTop - container.clientHeight; - const near = dist < 5000; + const near = dist < 150; setIsNearBottom(near); isNearBottomRef.current = near; return; @@ -218,7 +218,7 @@ export function MessageList({ channelId, jumpToMessageId, onJumpComplete }: Mess // Check if near bottom const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight; - const nearBottom = distanceFromBottom < 5000; + const nearBottom = distanceFromBottom < 150; setIsNearBottom(nearBottom); isNearBottomRef.current = nearBottom;