feat: suppress URL text for image-only messages, add sourceUrl to context menu
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import type { MessageWithUser } from '@backspace/shared';
|
import type { MessageWithUser, Embed } from '@backspace/shared';
|
||||||
import { MarkdownRenderer } from './MarkdownRenderer';
|
import { MarkdownRenderer } from './MarkdownRenderer';
|
||||||
import { MentionBadge } from './MentionBadge';
|
import { MentionBadge } from './MentionBadge';
|
||||||
import { Avatar } from '../ui/Avatar';
|
import { Avatar } from '../ui/Avatar';
|
||||||
@@ -61,6 +61,23 @@ function isGifOnlyMessage(content: string | null): boolean {
|
|||||||
return GIF_URL_REGEX.test(trimmed);
|
return GIF_URL_REGEX.test(trimmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the original posted URL when a message is a single URL that resolved
|
||||||
|
* to an image embed, or null if the message should render normally.
|
||||||
|
*/
|
||||||
|
function getImageEmbedSourceUrl(content: string | null, embeds: Embed[]): string | null {
|
||||||
|
if (!content) return null;
|
||||||
|
const trimmed = content.trim();
|
||||||
|
// Must be a single URL with no surrounding text
|
||||||
|
if (!trimmed.startsWith('http://') && !trimmed.startsWith('https://')) return null;
|
||||||
|
if (/\s/.test(trimmed)) return null;
|
||||||
|
// Must have at least one image embed whose URL matches the posted content
|
||||||
|
const hasMatchingImageEmbed = embeds.some(
|
||||||
|
(e) => e.embedType === 'image' && e.url === trimmed
|
||||||
|
);
|
||||||
|
return hasMatchingImageEmbed ? trimmed : null;
|
||||||
|
}
|
||||||
|
|
||||||
export function Message({ message, isCompact, isFirstInGroup, previousMessageId }: MessageProps) {
|
export function Message({ message, isCompact, isFirstInGroup, previousMessageId }: MessageProps) {
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [editContent, setEditContent] = useState(message.content ?? '');
|
const [editContent, setEditContent] = useState(message.content ?? '');
|
||||||
@@ -130,6 +147,11 @@ export function Message({ message, isCompact, isFirstInGroup, previousMessageId
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const isGifOnly = isGifOnlyMessage(message.content);
|
const isGifOnly = isGifOnlyMessage(message.content);
|
||||||
|
const imageEmbedSourceUrl = getImageEmbedSourceUrl(message.content, message.embeds || []);
|
||||||
|
// sourceUrl: the original URL for context menu Copy/Open Link actions
|
||||||
|
const sourceUrl = isGifOnly
|
||||||
|
? (message.content?.trim() ?? null)
|
||||||
|
: imageEmbedSourceUrl;
|
||||||
|
|
||||||
// Close reaction picker on outside click
|
// Close reaction picker on outside click
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -183,6 +205,7 @@ export function Message({ message, isCompact, isFirstInGroup, previousMessageId
|
|||||||
selectedText,
|
selectedText,
|
||||||
previousMessageId,
|
previousMessageId,
|
||||||
imageUrl,
|
imageUrl,
|
||||||
|
sourceUrl,
|
||||||
isAuthor,
|
isAuthor,
|
||||||
isDm: isDmMessage,
|
isDm: isDmMessage,
|
||||||
canAddReactions,
|
canAddReactions,
|
||||||
@@ -354,6 +377,20 @@ export function Message({ message, isCompact, isFirstInGroup, previousMessageId
|
|||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
) : imageEmbedSourceUrl ? (
|
||||||
|
<>
|
||||||
|
{/* URL text suppressed — embeds render the image */}
|
||||||
|
{!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>
|
||||||
|
)}
|
||||||
|
{message.editedAt && (
|
||||||
|
<span className="text-[10px] text-txt-tertiary select-none font-medium">(edited)</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{message.content && (
|
{message.content && (
|
||||||
|
|||||||
Reference in New Issue
Block a user