feat(messaging-ui): mail attachment download + scrollable reader (0.1.1)

- Attachment chips in the mail reader are now clickable: new InboxAdapter
  downloadAttachment() resolves a short-lived URL, opened in a new tab.
- Mail reader scrolls again: convert the .miu-detail/.miu-mail height:100%
  chain to flex fill so a long thread scrolls within the pane instead of
  being clipped. MockInboxAdapter implements download. Bump to 0.1.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 00:53:08 +05:30
parent c4254749a4
commit 8878ee8c54
6 changed files with 58 additions and 7 deletions
+17 -2
View File
@@ -117,7 +117,7 @@ function Detail({ item, onTransition }: { item: InboxItem; onTransition: (state:
/** Read a mail thread (HTML in a sandboxed iframe) + reply. */
export function MailReader({ threadId, subject }: { threadId: string; subject: string }) {
const { messages, loading, error, reply, canAttach, upload } = useMailThread(threadId);
const { messages, loading, error, reply, canAttach, upload, canDownload, download } = useMailThread(threadId);
const [draft, setDraft] = useState('');
const [sending, setSending] = useState(false);
const [pending, setPending] = useState<MailAttachment | null>(null);
@@ -125,6 +125,15 @@ export function MailReader({ threadId, subject }: { threadId: string; subject: s
const [attachErr, setAttachErr] = useState<string | null>(null);
const fileRef = useRef<HTMLInputElement>(null);
async function openAttachment(att: MailAttachment): Promise<void> {
try {
const url = await download(att);
window.open(url, '_blank', 'noopener,noreferrer');
} catch (err) {
setAttachErr(err instanceof Error ? err.message : String(err));
}
}
async function pick(e: React.ChangeEvent<HTMLInputElement>): Promise<void> {
const file = e.target.files?.[0];
e.target.value = '';
@@ -176,7 +185,13 @@ export function MailReader({ threadId, subject }: { threadId: string; subject: s
<div className="miu-mail-text">{m.text}</div>
) : null}
{m.attachment ? (
<span className="miu-attach-chip">📎 {m.attachment.filename ?? 'attachment'}{m.attachment.sizeBytes ? ` · ${fmtBytes(m.attachment.sizeBytes)}` : ''}</span>
canDownload ? (
<button type="button" className="miu-attach-chip miu-attach-dl" onClick={() => void openAttachment(m.attachment!)} title="Download">
📎 {m.attachment.filename ?? 'attachment'}{m.attachment.sizeBytes ? ` · ${fmtBytes(m.attachment.sizeBytes)}` : ''}
</button>
) : (
<span className="miu-attach-chip">📎 {m.attachment.filename ?? 'attachment'}{m.attachment.sizeBytes ? ` · ${fmtBytes(m.attachment.sizeBytes)}` : ''}</span>
)
) : null}
</article>
))}