From e7c29f91f2ec8d8f9d1e1f4b693a1e30e887224a Mon Sep 17 00:00:00 2001 From: jjbrunton Date: Sun, 9 Aug 2026 07:55:51 +0100 Subject: [PATCH] Blur conversation images by default --- apps/dashboard/src/components/origin-post.tsx | 19 +++++++--- .../conversations/conversation-detail.tsx | 38 +++++++++++++------ .../features/conversations/conversations.css | 8 ++-- apps/dashboard/src/styles.css | 12 ++++-- tests/dashboard/conversations.test.tsx | 14 +++---- 5 files changed, 60 insertions(+), 31 deletions(-) diff --git a/apps/dashboard/src/components/origin-post.tsx b/apps/dashboard/src/components/origin-post.tsx index a21fb34..f0a799f 100644 --- a/apps/dashboard/src/components/origin-post.tsx +++ b/apps/dashboard/src/components/origin-post.tsx @@ -8,15 +8,24 @@ export interface OriginPost { } export function OriginPostCard({ post }: { post: OriginPost }) { + const [isImageRevealed, setIsImageRevealed] = useState(false); + return ( - - {post.imageUrl ? {`Source : null} - +
+ {post.imageUrl ? ( +
+ {`Source + +
+ ) : null} +
Started from {post.subreddit ? `r/${post.subreddit}` : 'Reddit'} {post.title} Open original post - - + +
); } diff --git a/apps/dashboard/src/features/conversations/conversation-detail.tsx b/apps/dashboard/src/features/conversations/conversation-detail.tsx index 60dbc5d..8bf4973 100644 --- a/apps/dashboard/src/features/conversations/conversation-detail.tsx +++ b/apps/dashboard/src/features/conversations/conversation-detail.tsx @@ -20,6 +20,8 @@ function mediaPath(localPath: string): string { } function MessageHistory({ conversation }: { conversation: ConversationDetailResponse }) { + const [revealedMedia, setRevealedMedia] = useState>(() => new Set()); + if (!conversation.messages || conversation.messages.length === 0) { return
No messages have been loaded for this conversation.
; } @@ -38,21 +40,35 @@ function MessageHistory({ conversation }: { conversation: ConversationDetailResp

{message.text}

{message.media?.length ? (
- {message.media.map((media) => ( - + {message.media.map((media) => { + const mediaKey = `${message.timestamp}-${media.localPath}`; + const sender = message.isSelf ? 'sent by you' : `received from ${redditUsernameLabel(conversation.username)}`; + const isRevealed = revealedMedia.has(mediaKey); + + return ( +
{`Image - - ))} + +
+ ); + })}
) : null} diff --git a/apps/dashboard/src/features/conversations/conversations.css b/apps/dashboard/src/features/conversations/conversations.css index f0e2946..ea3460a 100644 --- a/apps/dashboard/src/features/conversations/conversations.css +++ b/apps/dashboard/src/features/conversations/conversations.css @@ -62,9 +62,11 @@ .conversation-messages time { color: var(--muted); font-size: .7rem; } .conversation-messages p { margin: 0; line-height: 1.45; white-space: pre-wrap; } .conversation-message-images { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(12rem, 100%), 1fr)); gap: .5rem; margin-top: .65rem; } -.conversation-message-images a { display: block; border-radius: .6rem; } -.conversation-message-images a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } -.conversation-message-images img { display: block; width: 100%; max-width: 28rem; max-height: 32rem; object-fit: contain; background: #08100c; border: 1px solid var(--border); border-radius: .6rem; } +.conversation-message-image { position: relative; overflow: hidden; border-radius: .6rem; } +.conversation-message-images img { display: block; width: 100%; max-width: 28rem; max-height: 32rem; object-fit: contain; background: #08100c; border: 1px solid var(--border); border-radius: .6rem; transition: filter 160ms ease, transform 160ms ease; } +.conversation-message-images img.is-blurred { filter: blur(24px) brightness(.55); transform: scale(1.12); } +.conversation-image-reveal { position: absolute; bottom: .5rem; left: .5rem; min-height: 2rem !important; padding: .35rem .6rem !important; color: white !important; background: rgba(0, 0, 0, .76) !important; border-color: rgba(255, 255, 255, .45) !important; font-size: .75rem; font-weight: 800; } +.conversation-image-reveal:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } .conversation-empty-detail { padding: 2rem; color: var(--muted); text-align: center; } .conversation-composer { display: grid; gap: .65rem; padding: 1rem; border-top: 1px solid var(--border); } .conversation-section-heading, .conversation-actions { display: flex; align-items: center; justify-content: space-between; gap: .75rem; } diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index 6748887..2a8f127 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -99,10 +99,14 @@ h2 { margin-bottom: 0; font-size: 1.1rem; } .origin-post-dialog-copy h2 { margin: 0; font-size: 1.25rem; } .origin-post-dialog-copy small { color: var(--muted); } .origin-post-dialog-copy a { justify-self: start; text-decoration: none; } -.origin-post-card { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .8rem; align-items: center; color: inherit; text-decoration: none; } -.origin-post-card > img { width: 5rem; height: 5rem; object-fit: cover; background: #050907; border-radius: .45rem; } -.origin-post-card > span { display: grid; gap: .2rem; min-width: 0; } -.origin-post-card small, .origin-post-card span span { color: var(--muted); font-size: .75rem; } +.origin-post-card { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .8rem; align-items: center; } +.origin-post-image { position: relative; width: 5rem; height: 5rem; overflow: hidden; background: #050907; border-radius: .45rem; } +.origin-post-image img { width: 100%; height: 100%; object-fit: cover; transition: filter 160ms ease, transform 160ms ease; } +.origin-post-image img.is-blurred { filter: blur(18px) brightness(.55); transform: scale(1.12); } +.origin-post-image button { position: absolute; right: .2rem; bottom: .2rem; min-height: auto; padding: .2rem .3rem; border: 1px solid rgba(255, 255, 255, .45); border-radius: .3rem; color: white; background: rgba(0, 0, 0, .76); font-size: .58rem; font-weight: 800; } +.origin-post-image button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; } +.origin-post-copy { display: grid; gap: .2rem; min-width: 0; color: inherit; text-decoration: none; } +.origin-post-card small, .origin-post-card span { color: var(--muted); font-size: .75rem; } .origin-post-card strong { overflow-wrap: anywhere; } .activity-marker { flex: 0 0 auto; width: .55rem; height: .55rem; margin-top: .3rem; border-radius: 50%; background: var(--muted); } .activity-marker.sent, .activity-marker.response { background: var(--accent); } diff --git a/tests/dashboard/conversations.test.tsx b/tests/dashboard/conversations.test.tsx index e204f5e..4247eb4 100644 --- a/tests/dashboard/conversations.test.tsx +++ b/tests/dashboard/conversations.test.tsx @@ -142,6 +142,9 @@ describe('ConversationsPage', () => { 'src', 'https://i.redd.it/source-post.jpg', ); + expect(screen.getByRole('img', { name: 'Source post: Looking for a chat' })).toHaveClass('is-blurred'); + await user.click(screen.getByRole('button', { name: 'Reveal original image' })); + expect(screen.getByRole('img', { name: 'Source post: Looking for a chat' })).not.toHaveClass('is-blurred'); }); it('orders conversations by latest message activity', async () => { @@ -215,14 +218,9 @@ describe('ConversationsPage', () => { 'src', '/media/received%20image.jpg', ); - expect(screen.getByRole('link', { name: 'View original image received from u/alice' })).toHaveAttribute( - 'href', - '/media/received%20image.jpg', - ); - expect(screen.getByRole('link', { name: 'View original image received from u/alice' })).toHaveAttribute( - 'target', - '_blank', - ); + expect(screen.getByRole('img', { name: 'Image received from u/alice' })).toHaveClass('is-blurred'); + await user.click(screen.getByRole('button', { name: 'Reveal conversation image' })); + expect(screen.getByRole('img', { name: 'Image received from u/alice' })).not.toHaveClass('is-blurred'); }); it('turns a suggestion into an editable draft and confirms sending without losing failed input', async () => {