feat: persist settings and enrich conversation context
This commit is contained in:
1 parent
a36723a325
commit
2c100d72b6
84 files changed
+2248
-644
No files matched your search
@@ -1,5 +1,7 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import type { ConversationDetailResponse } from '../../../../../packages/api-contracts/src/index.js';
|
||||
import { RedditUsernameLink, redditUsernameLabel } from '../../components/reddit-username-link.js';
|
||||
import { OriginPostCard } from '../../components/origin-post.js';
|
||||
import { useConversationDetailQuery, useSendReplyMutation, useSuggestMediaAskMutation, useSuggestReplyMutation } from './conversation-queries.js';
|
||||
import { SendConfirmationDialog } from './send-confirmation-dialog.js';
|
||||
|
||||
@@ -12,6 +14,10 @@ function formatMessageTime(timestamp: number): string {
|
||||
return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(timestamp);
|
||||
}
|
||||
|
||||
function mediaPath(localPath: string): string {
|
||||
return `/media/${localPath.split('/').map(encodeURIComponent).join('/')}`;
|
||||
}
|
||||
|
||||
function MessageHistory({ conversation }: { conversation: ConversationDetailResponse }) {
|
||||
if (!conversation.messages || conversation.messages.length === 0) {
|
||||
return <div className="conversation-empty-detail">No messages have been loaded for this conversation.</div>;
|
||||
@@ -22,10 +28,22 @@ function MessageHistory({ conversation }: { conversation: ConversationDetailResp
|
||||
{conversation.messages.map((message) => (
|
||||
<li className={message.isSelf ? 'is-self' : 'is-recipient'} key={`${message.timestamp}-${message.sender}-${message.text}`}>
|
||||
<div>
|
||||
<strong>{message.isSelf ? 'You' : `u/${conversation.username}`}</strong>
|
||||
<strong>{message.isSelf ? 'You' : <RedditUsernameLink username={conversation.username} />}</strong>
|
||||
<time dateTime={new Date(message.timestamp).toISOString()}>{formatMessageTime(message.timestamp)}</time>
|
||||
</div>
|
||||
<p>{message.text}</p>
|
||||
{message.media?.length ? (
|
||||
<div className="conversation-message-images">
|
||||
{message.media.map((media) => (
|
||||
<img
|
||||
alt={`Image ${message.isSelf ? 'sent by you' : `received from ${redditUsernameLabel(conversation.username)}`}`}
|
||||
key={`${message.timestamp}-${media.localPath}`}
|
||||
loading="lazy"
|
||||
src={mediaPath(media.localPath)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
@@ -79,7 +97,7 @@ function ConversationComposer({ conversation }: { conversation: ConversationDeta
|
||||
{mediaAskMutation.isPending ? 'Generating media ask…' : 'Suggest media ask'}
|
||||
</button>
|
||||
</div>
|
||||
<label className="sr-only" htmlFor="conversation-reply">Reply to u/{conversation.username}</label>
|
||||
<label className="sr-only" htmlFor="conversation-reply">Reply to {redditUsernameLabel(conversation.username)}</label>
|
||||
<textarea
|
||||
id="conversation-reply"
|
||||
onChange={(event) => {
|
||||
@@ -94,7 +112,7 @@ function ConversationComposer({ conversation }: { conversation: ConversationDeta
|
||||
{mediaAskMutation.isError ? <div className="conversation-inline-error" role="alert">{mediaAskMutation.error.message}</div> : null}
|
||||
{sendMutation.isError ? <div className="conversation-inline-error" role="alert">{sendMutation.error.message}</div> : null}
|
||||
{wasSent ? (
|
||||
<div aria-label="Reply sent" className="conversation-inline-success" role="status">Reply sent to u/{conversation.username}</div>
|
||||
<div aria-label="Reply sent" className="conversation-inline-success" role="status">Reply sent to <RedditUsernameLink username={conversation.username} /></div>
|
||||
) : null}
|
||||
<div className="conversation-actions">
|
||||
<span>{draft.length} characters</span>
|
||||
@@ -143,7 +161,7 @@ export function ConversationDetail({ conversationId, onClose }: ConversationDeta
|
||||
<header>
|
||||
<div>
|
||||
<span className="conversation-eyebrow">Conversation</span>
|
||||
<h2>u/{conversation.username}</h2>
|
||||
<h2><RedditUsernameLink username={conversation.username} /></h2>
|
||||
{conversation.unread ? <span className="conversation-badge">Unread</span> : null}
|
||||
</div>
|
||||
<button aria-label="Close conversation" onClick={onClose} type="button">Close</button>
|
||||
@@ -154,16 +172,14 @@ export function ConversationDetail({ conversationId, onClose }: ConversationDeta
|
||||
</div>
|
||||
) : null}
|
||||
{conversation.triggeringPost ? (
|
||||
<a
|
||||
aria-label={conversation.triggeringPost.title ?? `Open triggering post ${conversation.triggeringPost.id}`}
|
||||
className="triggering-post"
|
||||
href={conversation.triggeringPost.url}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<span>Started from {conversation.triggeringPost.subreddit ? `r/${conversation.triggeringPost.subreddit}` : 'Reddit'}</span>
|
||||
<strong>{conversation.triggeringPost.title ?? conversation.triggeringPost.id}</strong>
|
||||
</a>
|
||||
<div className="triggering-post">
|
||||
<OriginPostCard post={{
|
||||
title: conversation.triggeringPost.title ?? conversation.triggeringPost.id,
|
||||
url: conversation.triggeringPost.url,
|
||||
imageUrl: conversation.triggeringPost.imageUrl,
|
||||
subreddit: conversation.triggeringPost.subreddit,
|
||||
}} />
|
||||
</div>
|
||||
) : null}
|
||||
<MessageHistory conversation={conversation} />
|
||||
<ConversationComposer conversation={conversation} />
|
||||
|
||||
Reference in new issue
Block a user