Blur conversation images by default
This commit is contained in:
1 parent
5c01d7d9aa
commit
e7c29f91f2
5 files changed
+60
-31
No files matched your search
@@ -8,15 +8,24 @@ export interface OriginPost {
|
||||
}
|
||||
|
||||
export function OriginPostCard({ post }: { post: OriginPost }) {
|
||||
const [isImageRevealed, setIsImageRevealed] = useState(false);
|
||||
|
||||
return (
|
||||
<a aria-label={post.title} className="origin-post-card" href={post.url} rel="noreferrer" target="_blank">
|
||||
{post.imageUrl ? <img alt={`Source post: ${post.title}`} loading="lazy" src={post.imageUrl} /> : null}
|
||||
<span>
|
||||
<div className="origin-post-card">
|
||||
{post.imageUrl ? (
|
||||
<div className="origin-post-image">
|
||||
<img alt={`Source post: ${post.title}`} className={isImageRevealed ? undefined : 'is-blurred'} loading="lazy" src={post.imageUrl} />
|
||||
<button aria-pressed={isImageRevealed} onClick={() => setIsImageRevealed((current) => !current)} type="button">
|
||||
{isImageRevealed ? 'Blur original image' : 'Reveal original image'}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
<a aria-label={post.title} className="origin-post-copy" href={post.url} rel="noreferrer" target="_blank">
|
||||
<small>Started from {post.subreddit ? `r/${post.subreddit}` : 'Reddit'}</small>
|
||||
<strong>{post.title}</strong>
|
||||
<span>Open original post</span>
|
||||
</span>
|
||||
</a>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ function mediaPath(localPath: string): string {
|
||||
}
|
||||
|
||||
function MessageHistory({ conversation }: { conversation: ConversationDetailResponse }) {
|
||||
const [revealedMedia, setRevealedMedia] = useState<Set<string>>(() => new Set());
|
||||
|
||||
if (!conversation.messages || conversation.messages.length === 0) {
|
||||
return <div className="conversation-empty-detail">No messages have been loaded for this conversation.</div>;
|
||||
}
|
||||
@@ -38,21 +40,35 @@ function MessageHistory({ conversation }: { conversation: ConversationDetailResp
|
||||
<p>{message.text}</p>
|
||||
{message.media?.length ? (
|
||||
<div className="conversation-message-images">
|
||||
{message.media.map((media) => (
|
||||
<a
|
||||
aria-label={`View original image ${message.isSelf ? 'sent by you' : `received from ${redditUsernameLabel(conversation.username)}`}`}
|
||||
href={mediaPath(media.localPath)}
|
||||
key={`${message.timestamp}-${media.localPath}`}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{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 (
|
||||
<div className="conversation-message-image" key={mediaKey}>
|
||||
<img
|
||||
alt={`Image ${message.isSelf ? 'sent by you' : `received from ${redditUsernameLabel(conversation.username)}`}`}
|
||||
alt={`Image ${sender}`}
|
||||
className={isRevealed ? undefined : 'is-blurred'}
|
||||
loading="lazy"
|
||||
src={mediaPath(media.localPath)}
|
||||
/>
|
||||
</a>
|
||||
))}
|
||||
<button
|
||||
aria-pressed={isRevealed}
|
||||
className="conversation-image-reveal"
|
||||
onClick={() => setRevealedMedia((current) => {
|
||||
const next = new Set(current);
|
||||
if (next.has(mediaKey)) next.delete(mediaKey);
|
||||
else next.add(mediaKey);
|
||||
return next;
|
||||
})}
|
||||
type="button"
|
||||
>
|
||||
{isRevealed ? 'Blur conversation image' : 'Reveal conversation image'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</li>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in new issue
Block a user