diff --git a/packages/web/src/components/chat/Message.tsx b/packages/web/src/components/chat/Message.tsx
index bfd021d8..549782c2 100644
--- a/packages/web/src/components/chat/Message.tsx
+++ b/packages/web/src/components/chat/Message.tsx
@@ -173,10 +173,16 @@ export function Message({ message, isCompact, isFirstInGroup, previousMessageId
e.stopPropagation();
const selectedText = window.getSelection()?.toString() ?? '';
+ // Detect if the right-click target is a content image (not an avatar)
+ const imgEl = (e.target as HTMLElement).closest('img') as HTMLImageElement | null;
+ const isContentImage = imgEl && !imgEl.closest('[data-avatar]');
+ const imageUrl = isContentImage ? imgEl.src : null;
+
const items = buildMessageMenuItems({
message,
selectedText,
previousMessageId,
+ imageUrl,
isAuthor,
isDm: isDmMessage,
canAddReactions,
diff --git a/packages/web/src/components/chat/messageMenuItems.tsx b/packages/web/src/components/chat/messageMenuItems.tsx
index 48842f22..d25ab4a9 100644
--- a/packages/web/src/components/chat/messageMenuItems.tsx
+++ b/packages/web/src/components/chat/messageMenuItems.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import type { ContextMenuItem } from '../../stores/contextMenuStore';
import type { MessageWithUser } from '@backspace/shared';
+import { saveImage, copyImageToClipboard } from '../../utils/imageActions';
const QUICK_EMOJIS = ['👍', '❤️', '😂', '😮'];
@@ -19,6 +20,7 @@ interface MessageMenuParams {
onReaction: (emoji: string) => void;
onOpenEmojiPicker: () => void;
onMarkUnread: (messageId: string) => void;
+ imageUrl?: string | null;
}
export function buildMessageMenuItems(params: MessageMenuParams): ContextMenuItem[] {
@@ -37,10 +39,56 @@ export function buildMessageMenuItems(params: MessageMenuParams): ContextMenuIte
onReaction,
onOpenEmojiPicker,
onMarkUnread,
+ imageUrl,
} = params;
const items: ContextMenuItem[] = [];
+ // ── Image Actions (when right-clicking an image) ──────────────────────
+ if (imageUrl) {
+ items.push({
+ key: 'save-image',
+ type: 'action',
+ label: 'Save Image',
+ icon: (
+
+ ),
+ onClick: () => {
+ const filename = imageUrl.split('/').pop()?.split('?')[0] ?? 'image';
+ saveImage(imageUrl, filename);
+ },
+ });
+ items.push({
+ key: 'copy-image',
+ type: 'action',
+ label: 'Copy Image',
+ icon: (
+
+ ),
+ onClick: () => {
+ copyImageToClipboard(imageUrl);
+ },
+ });
+ items.push({
+ key: 'open-original',
+ type: 'action',
+ label: 'Open Original',
+ icon: (
+
+ ),
+ onClick: () => {
+ window.open(imageUrl, '_blank', 'noopener');
+ },
+ });
+ items.push({ key: 'image-sep', type: 'separator' });
+ }
+
// ── Quick Reaction Row ──────────────────────────────────────────────────
if (canAddReactions) {
items.push({