feat(messaging-ui): attachments in inbox mail — reply + compose upload
InboxAdapter gains uploadAttachment + attachment params on mailReply/ composeInternal/composeExternal. MailReader reply and ComposeModal grow an attach affordance (paperclip + pending chips); mail history renders sent attachments. MockInboxAdapter implements upload. 67 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { InboxAdapter } from '../inbox/adapter';
|
||||
import type { InboxItem, InboxState, MailMessage, MailPerson } from '../inbox/types';
|
||||
import type { InboxItem, InboxState, MailAttachment, MailMessage, MailPerson } from '../inbox/types';
|
||||
|
||||
const PEOPLE: MailPerson[] = [
|
||||
{ id: 'pp_sofia', name: 'Sofia Ramirez', kind: 'staff' },
|
||||
@@ -79,26 +79,31 @@ export class MockInboxAdapter implements InboxAdapter {
|
||||
return [...(this.threads.get(threadId)?.messages ?? [])];
|
||||
}
|
||||
|
||||
async mailReply(threadId: string, content: string): Promise<void> {
|
||||
async mailReply(threadId: string, content: string, attachment?: MailAttachment): Promise<void> {
|
||||
const t = this.threads.get(threadId);
|
||||
if (t) t.messages = [...t.messages, { id: `r_${this.seq++}`, actorId: 'me', kind: 'MESSAGE', at: this.now(), html: null, text: content, attachment: null }];
|
||||
if (t) t.messages = [...t.messages, { id: `r_${this.seq++}`, actorId: 'me', kind: 'MESSAGE', at: this.now(), html: null, text: content || null, attachment: attachment ?? null }];
|
||||
}
|
||||
|
||||
async uploadAttachment(file: File): Promise<MailAttachment> {
|
||||
return { contentRef: `mock/${this.seq++}`, mimeType: file.type || 'application/octet-stream', sizeBytes: file.size, filename: file.name };
|
||||
}
|
||||
|
||||
async directory(): Promise<MailPerson[]> {
|
||||
return [...PEOPLE];
|
||||
}
|
||||
|
||||
async composeInternal(recipientUserId: string, subject: string, text: string): Promise<void> {
|
||||
async composeInternal(recipientUserId: string, subject: string, text: string, attachments?: MailAttachment[]): Promise<void> {
|
||||
const threadId = `mt_${this.seq++}`;
|
||||
this.threads.set(threadId, { subject, messages: [{ id: `m_${this.seq++}`, actorId: 'me', kind: 'EMAIL', at: this.now(), html: `<p>${text}</p>`, text, attachment: null }] });
|
||||
this.threads.set(threadId, { subject, messages: [{ id: `m_${this.seq++}`, actorId: 'me', kind: 'EMAIL', at: this.now(), html: `<p>${text}</p>`, text, attachment: attachments?.[0] ?? null }] });
|
||||
this.mailFold.unshift(threadId);
|
||||
void recipientUserId;
|
||||
}
|
||||
|
||||
async composeExternal(target: string, subject: string, text: string): Promise<void> {
|
||||
async composeExternal(target: string, subject: string, text: string, attachments?: MailAttachment[]): Promise<void> {
|
||||
// A mock external send has no in-app thread — no-op beyond acknowledging.
|
||||
void target;
|
||||
void subject;
|
||||
void text;
|
||||
void attachments;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user