fixes from review
This commit is contained in:
@@ -303,6 +303,7 @@ export default function Home() {
|
|||||||
} = useEmailStore();
|
} = useEmailStore();
|
||||||
|
|
||||||
const enableUnifiedMailbox = useSettingsStore((s) => s.enableUnifiedMailbox);
|
const enableUnifiedMailbox = useSettingsStore((s) => s.enableUnifiedMailbox);
|
||||||
|
const delayedSendSupported = client?.hasDelayedSend() ?? true;
|
||||||
const activeEmails = isScheduledView ? scheduledEmails : emails;
|
const activeEmails = isScheduledView ? scheduledEmails : emails;
|
||||||
const activeHasMore = isScheduledView ? scheduledHasMore : hasMoreEmails;
|
const activeHasMore = isScheduledView ? scheduledHasMore : hasMoreEmails;
|
||||||
const activeIsLoading = isScheduledView ? isLoadingScheduled : isLoading;
|
const activeIsLoading = isScheduledView ? isLoadingScheduled : isLoading;
|
||||||
@@ -393,6 +394,11 @@ export default function Home() {
|
|||||||
// Restore mailbox selection. selectMailbox clears the current email,
|
// Restore mailbox selection. selectMailbox clears the current email,
|
||||||
// which is fine because we re-apply the saved email below.
|
// which is fine because we re-apply the saved email below.
|
||||||
if (state.mailboxId === SCHEDULED_MAILBOX_ID) {
|
if (state.mailboxId === SCHEDULED_MAILBOX_ID) {
|
||||||
|
if (!ctx.client?.hasDelayedSend()) {
|
||||||
|
setScheduledView(false);
|
||||||
|
selectEmail(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setScheduledView(true);
|
setScheduledView(true);
|
||||||
selectMailbox(SCHEDULED_MAILBOX_ID);
|
selectMailbox(SCHEDULED_MAILBOX_ID);
|
||||||
selectEmail(null);
|
selectEmail(null);
|
||||||
@@ -655,6 +661,12 @@ export default function Home() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!delayedSendSupported && isScheduledView) {
|
||||||
|
setScheduledView(false);
|
||||||
|
}
|
||||||
|
}, [delayedSendSupported, isScheduledView, setScheduledView]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!pendingUndoSend) return;
|
if (!pendingUndoSend) return;
|
||||||
const pendingSendTime = new Date(pendingUndoSend.sendAt).getTime();
|
const pendingSendTime = new Date(pendingUndoSend.sendAt).getTime();
|
||||||
@@ -1534,6 +1546,10 @@ export default function Home() {
|
|||||||
|
|
||||||
const handleMailboxSelect = async (mailboxId: string) => {
|
const handleMailboxSelect = async (mailboxId: string) => {
|
||||||
if (mailboxId === SCHEDULED_MAILBOX_ID) {
|
if (mailboxId === SCHEDULED_MAILBOX_ID) {
|
||||||
|
if (!delayedSendSupported) {
|
||||||
|
setScheduledView(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isUnifiedView) exitUnifiedView();
|
if (isUnifiedView) exitUnifiedView();
|
||||||
setScheduledView(true);
|
setScheduledView(true);
|
||||||
selectMailbox(mailboxId);
|
selectMailbox(mailboxId);
|
||||||
@@ -2326,6 +2342,7 @@ export default function Home() {
|
|||||||
selectedMailbox={selectedMailbox}
|
selectedMailbox={selectedMailbox}
|
||||||
selectedKeyword={selectedKeyword}
|
selectedKeyword={selectedKeyword}
|
||||||
scheduledTotal={scheduledTotal}
|
scheduledTotal={scheduledTotal}
|
||||||
|
showScheduledMailbox={delayedSendSupported}
|
||||||
onMailboxSelect={handleMailboxSelect}
|
onMailboxSelect={handleMailboxSelect}
|
||||||
onTagSelect={handleTagSelect}
|
onTagSelect={handleTagSelect}
|
||||||
onUnreadFilterClick={handleUnreadFilterClick}
|
onUnreadFilterClick={handleUnreadFilterClick}
|
||||||
@@ -2630,6 +2647,8 @@ export default function Home() {
|
|||||||
emails={activeEmails}
|
emails={activeEmails}
|
||||||
selectedEmailId={selectedEmail?.id}
|
selectedEmailId={selectedEmail?.id}
|
||||||
isLoading={activeIsLoading}
|
isLoading={activeIsLoading}
|
||||||
|
hasMore={activeHasMore}
|
||||||
|
isLoadingMoreItems={isScheduledView ? isLoadingScheduled && activeEmails.length > 0 : undefined}
|
||||||
isScheduledView={isScheduledView}
|
isScheduledView={isScheduledView}
|
||||||
onLoadMoreScheduled={() => client && loadMoreScheduledEmails(client)}
|
onLoadMoreScheduled={() => client && loadMoreScheduledEmails(client)}
|
||||||
onCancelScheduled={async (email) => {
|
onCancelScheduled={async (email) => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||||||
|
|
||||||
const ACCOUNT_ID = 'dev-account-001';
|
const ACCOUNT_ID = 'dev-account-001';
|
||||||
const scheduledSubmissions: Array<{ id: string; emailId: string; identityId: string; sendAt: string; undoStatus: 'pending' | 'final' | 'canceled' }> = [];
|
const scheduledSubmissions: Array<{ id: string; emailId: string; identityId: string; sendAt: string; undoStatus: 'pending' | 'final' | 'canceled' }> = [];
|
||||||
|
const emailCreationIds = new Map<string, string>();
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Mailboxes
|
// Mailboxes
|
||||||
@@ -1534,6 +1535,7 @@ function handleEmailSet(args: MethodArgs, callId: string): MethodResult {
|
|||||||
bodyValues: {},
|
bodyValues: {},
|
||||||
};
|
};
|
||||||
emails.unshift(newEmail);
|
emails.unshift(newEmail);
|
||||||
|
emailCreationIds.set(key, newId);
|
||||||
created[key] = { id: newId };
|
created[key] = { id: newId };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1592,7 +1594,7 @@ function handleEmailSubmissionSet(args: MethodArgs, callId: string): MethodResul
|
|||||||
const delayedUntil = Number.isFinite(holdUntilTime) ? new Date(holdUntilTime).toISOString() : undefined;
|
const delayedUntil = Number.isFinite(holdUntilTime) ? new Date(holdUntilTime).toISOString() : undefined;
|
||||||
created[key] = { id, ...(delayedUntil ? { sendAt: delayedUntil } : {}) };
|
created[key] = { id, ...(delayedUntil ? { sendAt: delayedUntil } : {}) };
|
||||||
if (delayedUntil && value.emailId && value.identityId) {
|
if (delayedUntil && value.emailId && value.identityId) {
|
||||||
const emailId = value.emailId.startsWith('#') ? emails[emails.length - 1]?.id || value.emailId : value.emailId;
|
const emailId = value.emailId.startsWith('#') ? emailCreationIds.get(value.emailId.slice(1)) || value.emailId : value.emailId;
|
||||||
scheduledSubmissions.push({ id, emailId, identityId: value.identityId, sendAt: delayedUntil, undoStatus: 'pending' });
|
scheduledSubmissions.push({ id, emailId, identityId: value.identityId, sendAt: delayedUntil, undoStatus: 'pending' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1441,7 +1441,7 @@ export function EmailComposer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 7. Send via raw email path
|
// 7. Send via raw email path
|
||||||
const result = await sendRawEmail(client, payload, currentIdentity.id, effectiveDelayedUntil);
|
const result = await sendRawEmail(client, payload, currentIdentity.id, effectiveDelayedUntil, [...toAddresses, ...ccAddresses, ...bccAddresses]);
|
||||||
if (effectiveDelayedUntil && finalDraftId) {
|
if (effectiveDelayedUntil && finalDraftId) {
|
||||||
client.deleteEmail(finalDraftId).catch(err => {
|
client.deleteEmail(finalDraftId).catch(err => {
|
||||||
debug.warn('email', 'Scheduled S/MIME send created, but plaintext draft cleanup failed:', err);
|
debug.warn('email', 'Scheduled S/MIME send created, but plaintext draft cleanup failed:', err);
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ interface EmailListProps {
|
|||||||
onEmailDoubleClick?: (email: Email) => void;
|
onEmailDoubleClick?: (email: Email) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
hasMore?: boolean;
|
||||||
|
isLoadingMoreItems?: boolean;
|
||||||
onOpenConversation?: (thread: ThreadGroup) => void;
|
onOpenConversation?: (thread: ThreadGroup) => void;
|
||||||
onReply?: (email: Email) => void;
|
onReply?: (email: Email) => void;
|
||||||
onReplyAll?: (email: Email) => void;
|
onReplyAll?: (email: Email) => void;
|
||||||
@@ -52,6 +54,8 @@ export function EmailList({
|
|||||||
onEmailDoubleClick,
|
onEmailDoubleClick,
|
||||||
className,
|
className,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
hasMore,
|
||||||
|
isLoadingMoreItems,
|
||||||
onOpenConversation,
|
onOpenConversation,
|
||||||
onReply,
|
onReply,
|
||||||
onReplyAll,
|
onReplyAll,
|
||||||
@@ -117,6 +121,8 @@ export function EmailList({
|
|||||||
const mailLayout = useSettingsStore((state) => state.mailLayout);
|
const mailLayout = useSettingsStore((state) => state.mailLayout);
|
||||||
const timeFormat = useSettingsStore((state) => state.timeFormat);
|
const timeFormat = useSettingsStore((state) => state.timeFormat);
|
||||||
const isFocusedMailLayout = mailLayout === 'focus';
|
const isFocusedMailLayout = mailLayout === 'focus';
|
||||||
|
const footerHasMore = hasMore ?? hasMoreEmails;
|
||||||
|
const footerIsLoadingMore = isLoadingMoreItems ?? isLoadingMore;
|
||||||
|
|
||||||
const estimateSize = useCallback(() => {
|
const estimateSize = useCallback(() => {
|
||||||
if (isFocusedMailLayout) {
|
if (isFocusedMailLayout) {
|
||||||
@@ -488,13 +494,13 @@ export function EmailList({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="py-4 flex justify-center">
|
<div className="py-4 flex justify-center">
|
||||||
{isLoadingMore && hasMoreEmails && (
|
{footerIsLoadingMore && footerHasMore && (
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
<Loader2 className="w-4 h-4 animate-spin" />
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
<span>{t('loading_more')}</span>
|
<span>{t('loading_more')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!hasMoreEmails && emails.length > 0 && (
|
{!footerHasMore && emails.length > 0 && (
|
||||||
<div className="text-sm text-muted-foreground border-t border-border pt-6">
|
<div className="text-sm text-muted-foreground border-t border-border pt-6">
|
||||||
{t('no_more_emails')}
|
{t('no_more_emails')}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ interface SidebarProps {
|
|||||||
onImportEmail?: (mailboxId: string) => void;
|
onImportEmail?: (mailboxId: string) => void;
|
||||||
onRefreshMailboxes?: () => void;
|
onRefreshMailboxes?: () => void;
|
||||||
scheduledTotal?: number;
|
scheduledTotal?: number;
|
||||||
|
showScheduledMailbox?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,6 +662,7 @@ export function Sidebar({
|
|||||||
onImportEmail,
|
onImportEmail,
|
||||||
onRefreshMailboxes,
|
onRefreshMailboxes,
|
||||||
scheduledTotal = 0,
|
scheduledTotal = 0,
|
||||||
|
showScheduledMailbox = false,
|
||||||
className,
|
className,
|
||||||
}: SidebarProps) {
|
}: SidebarProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -966,15 +968,17 @@ export function Sidebar({
|
|||||||
onContextMenu={handleMailboxContextMenu}
|
onContextMenu={handleMailboxContextMenu}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<SidebarRow
|
{showScheduledMailbox && (
|
||||||
icon={<CalendarClock className={cn("w-4 h-4 flex-shrink-0", selectedMailbox === '__scheduled__' ? "text-foreground" : "text-muted-foreground")} />}
|
<SidebarRow
|
||||||
label={t('scheduled')}
|
icon={<CalendarClock className={cn("w-4 h-4 flex-shrink-0", selectedMailbox === '__scheduled__' ? "text-foreground" : "text-muted-foreground")} />}
|
||||||
depth={0}
|
label={t('scheduled')}
|
||||||
isSelected={!selectedKeyword && selectedMailbox === '__scheduled__'}
|
depth={0}
|
||||||
total={scheduledTotal}
|
isSelected={!selectedKeyword && selectedMailbox === '__scheduled__'}
|
||||||
onClick={() => onMailboxSelect?.('__scheduled__')}
|
total={scheduledTotal}
|
||||||
isCollapsed={isCollapsed}
|
onClick={() => onMailboxSelect?.('__scheduled__')}
|
||||||
/>
|
isCollapsed={isCollapsed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -925,7 +925,7 @@ export class DemoJMAPClient implements IJMAPClient {
|
|||||||
|
|
||||||
async importRawEmail(): Promise<string> { return generateDemoId('email'); }
|
async importRawEmail(): Promise<string> { return generateDemoId('email'); }
|
||||||
async submitEmail(): Promise<void> { /* no-op */ }
|
async submitEmail(): Promise<void> { /* no-op */ }
|
||||||
async sendRawEmail(_blob?: Blob, identityId = 'demo-identity', _sentMailboxId?: string, _draftMailboxId?: string, delayedUntil?: string): Promise<SendEmailResult> {
|
async sendRawEmail(_blob?: Blob, identityId = 'demo-identity', _sentMailboxId?: string, _draftMailboxId?: string, delayedUntil?: string, _envelopeRecipients?: string[]): Promise<SendEmailResult> {
|
||||||
const emailId = generateDemoId('email');
|
const emailId = generateDemoId('email');
|
||||||
const draftsMailbox = this.data.mailboxes.find(m => m.role === 'drafts');
|
const draftsMailbox = this.data.mailboxes.find(m => m.role === 'drafts');
|
||||||
const sentMailbox = this.data.mailboxes.find(m => m.role === 'sent');
|
const sentMailbox = this.data.mailboxes.find(m => m.role === 'sent');
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ export interface IJMAPClient {
|
|||||||
envelopeMailFrom?: string,
|
envelopeMailFrom?: string,
|
||||||
): Promise<SendEmailResult>;
|
): Promise<SendEmailResult>;
|
||||||
|
|
||||||
sendRawEmail(blob: Blob, identityId: string, sentMailboxId: string, draftMailboxId?: string, delayedUntil?: string): Promise<SendEmailResult>;
|
sendRawEmail(blob: Blob, identityId: string, sentMailboxId: string, draftMailboxId?: string, delayedUntil?: string, envelopeRecipients?: string[]): Promise<SendEmailResult>;
|
||||||
getScheduledEmails(limit?: number, position?: number): Promise<{ emails: ScheduledEmail[]; hasMore: boolean; total: number }>;
|
getScheduledEmails(limit?: number, position?: number): Promise<{ emails: ScheduledEmail[]; hasMore: boolean; total: number }>;
|
||||||
cancelEmailSubmission(submissionId: string): Promise<void>;
|
cancelEmailSubmission(submissionId: string): Promise<void>;
|
||||||
rescheduleEmailSubmission(submissionId: string, emailId: string, identityId: string, delayedUntil: string): Promise<SendEmailResult>;
|
rescheduleEmailSubmission(submissionId: string, emailId: string, identityId: string, delayedUntil: string): Promise<SendEmailResult>;
|
||||||
|
|||||||
+31
-3
@@ -372,8 +372,17 @@ function sanitizeIdentityDisplayName(name: string | undefined | null): string {
|
|||||||
return name.replace(/\s*<[^>]*>\s*$/, '').trim();
|
return name.replace(/\s*<[^>]*>\s*$/, '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDelayedSubmissionEnvelope(fromEmail: string, holdForSeconds?: number): Record<string, unknown> | undefined {
|
function normalizeEnvelopeRecipients(recipients?: Array<string | EmailAddress>): Array<{ email: string }> {
|
||||||
|
return (recipients || [])
|
||||||
|
.map((recipient) => typeof recipient === 'string' ? recipient : recipient.email)
|
||||||
|
.map((email) => email.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((email) => ({ email }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDelayedSubmissionEnvelope(fromEmail: string, holdForSeconds?: number, recipients?: Array<string | EmailAddress>): Record<string, unknown> | undefined {
|
||||||
if (!holdForSeconds) return undefined;
|
if (!holdForSeconds) return undefined;
|
||||||
|
const rcptTo = normalizeEnvelopeRecipients(recipients);
|
||||||
return {
|
return {
|
||||||
mailFrom: {
|
mailFrom: {
|
||||||
email: fromEmail,
|
email: fromEmail,
|
||||||
@@ -381,6 +390,7 @@ function createDelayedSubmissionEnvelope(fromEmail: string, holdForSeconds?: num
|
|||||||
HOLDFOR: String(holdForSeconds),
|
HOLDFOR: String(holdForSeconds),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
rcptTo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3012,6 +3022,18 @@ export class JMAPClient implements IJMAPClient {
|
|||||||
return submission?.sendAt;
|
return submission?.sendAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getEmailSubmissionEnvelope(submissionId: string): Promise<{ rcptTo?: Array<{ email: string }> } | undefined> {
|
||||||
|
const response = await this.request([
|
||||||
|
['EmailSubmission/get', {
|
||||||
|
accountId: this.getSubmissionAccountId(),
|
||||||
|
ids: [submissionId],
|
||||||
|
properties: ['envelope'],
|
||||||
|
}, '0'],
|
||||||
|
]);
|
||||||
|
const submission = response.methodResponses?.[0]?.[1]?.list?.[0] as { envelope?: { rcptTo?: Array<{ email: string }> } } | undefined;
|
||||||
|
return submission?.envelope;
|
||||||
|
}
|
||||||
|
|
||||||
private getSubmissionAccountId(accountId?: string): string {
|
private getSubmissionAccountId(accountId?: string): string {
|
||||||
return accountId || this.session?.primaryAccounts?.['urn:ietf:params:jmap:submission'] || this.accountId;
|
return accountId || this.session?.primaryAccounts?.['urn:ietf:params:jmap:submission'] || this.accountId;
|
||||||
}
|
}
|
||||||
@@ -5500,6 +5522,7 @@ export class JMAPClient implements IJMAPClient {
|
|||||||
sentMailboxId: string,
|
sentMailboxId: string,
|
||||||
draftMailboxId?: string,
|
draftMailboxId?: string,
|
||||||
delayedUntil?: string,
|
delayedUntil?: string,
|
||||||
|
envelopeRecipients?: string[],
|
||||||
): Promise<SendEmailResult> {
|
): Promise<SendEmailResult> {
|
||||||
const holdForSeconds = delayedUntil ? this.validateDelayedUntil(delayedUntil) : undefined;
|
const holdForSeconds = delayedUntil ? this.validateDelayedUntil(delayedUntil) : undefined;
|
||||||
// Upload the raw message
|
// Upload the raw message
|
||||||
@@ -5511,7 +5534,7 @@ export class JMAPClient implements IJMAPClient {
|
|||||||
const importMailboxId = draftMailboxId || sentMailboxId;
|
const importMailboxId = draftMailboxId || sentMailboxId;
|
||||||
const identities = await this.getIdentities();
|
const identities = await this.getIdentities();
|
||||||
const identity = identities.find(item => item.id === identityId);
|
const identity = identities.find(item => item.id === identityId);
|
||||||
const envelope = createDelayedSubmissionEnvelope(identity?.email || this.username, holdForSeconds);
|
const envelope = createDelayedSubmissionEnvelope(identity?.email || this.username, holdForSeconds, envelopeRecipients);
|
||||||
|
|
||||||
const methodCalls: [string, Record<string, unknown>, string][] = [
|
const methodCalls: [string, Record<string, unknown>, string][] = [
|
||||||
['Email/import', {
|
['Email/import', {
|
||||||
@@ -5690,7 +5713,12 @@ export class JMAPClient implements IJMAPClient {
|
|||||||
const sentMailbox = mailboxes.find(mb => mb.role === 'sent');
|
const sentMailbox = mailboxes.find(mb => mb.role === 'sent');
|
||||||
const identities = await this.getIdentities();
|
const identities = await this.getIdentities();
|
||||||
const identity = identities.find(item => item.id === identityId);
|
const identity = identities.find(item => item.id === identityId);
|
||||||
const envelope = createDelayedSubmissionEnvelope(identity?.email || this.username, holdForSeconds);
|
const existingEnvelope = await this.getEmailSubmissionEnvelope(submissionId);
|
||||||
|
const email = existingEnvelope?.rcptTo?.length ? undefined : await this.getEmail(emailId);
|
||||||
|
const envelopeRecipients = existingEnvelope?.rcptTo?.length
|
||||||
|
? existingEnvelope.rcptTo
|
||||||
|
: [...(email?.to || []), ...(email?.cc || []), ...(email?.bcc || [])];
|
||||||
|
const envelope = createDelayedSubmissionEnvelope(identity?.email || this.username, holdForSeconds, envelopeRecipients);
|
||||||
const response = await this.request([
|
const response = await this.request([
|
||||||
['EmailSubmission/set', {
|
['EmailSubmission/set', {
|
||||||
accountId: this.getSubmissionAccountId(),
|
accountId: this.getSubmissionAccountId(),
|
||||||
|
|||||||
+31
-8
@@ -101,7 +101,7 @@ interface EmailStore {
|
|||||||
fetchEmailContent: (client: IJMAPClient, emailId: string) => Promise<Email | null>;
|
fetchEmailContent: (client: IJMAPClient, emailId: string) => Promise<Email | null>;
|
||||||
fetchQuota: (client: IJMAPClient) => Promise<void>;
|
fetchQuota: (client: IJMAPClient) => Promise<void>;
|
||||||
sendEmail: (client: IJMAPClient, to: string[], subject: string, body: string, cc?: string[], bcc?: string[], identityId?: string, fromEmail?: string, draftId?: string, fromName?: string, htmlBody?: string, attachments?: Array<{ blobId: string; name: string; type: string; size: number; disposition?: 'attachment' | 'inline'; cid?: string }>, inReplyTo?: string[], references?: string[], delayedUntil?: string, envelopeMailFrom?: string) => Promise<SendEmailResult>;
|
sendEmail: (client: IJMAPClient, to: string[], subject: string, body: string, cc?: string[], bcc?: string[], identityId?: string, fromEmail?: string, draftId?: string, fromName?: string, htmlBody?: string, attachments?: Array<{ blobId: string; name: string; type: string; size: number; disposition?: 'attachment' | 'inline'; cid?: string }>, inReplyTo?: string[], references?: string[], delayedUntil?: string, envelopeMailFrom?: string) => Promise<SendEmailResult>;
|
||||||
sendRawEmail: (client: IJMAPClient, rawMimeBlob: Blob, identityId: string, delayedUntil?: string) => Promise<SendEmailResult>;
|
sendRawEmail: (client: IJMAPClient, rawMimeBlob: Blob, identityId: string, delayedUntil?: string, envelopeRecipients?: string[]) => Promise<SendEmailResult>;
|
||||||
deleteEmail: (client: IJMAPClient, emailId: string, forceDelete?: boolean) => Promise<void>;
|
deleteEmail: (client: IJMAPClient, emailId: string, forceDelete?: boolean) => Promise<void>;
|
||||||
markAsRead: (client: IJMAPClient, emailId: string, read: boolean) => Promise<void>;
|
markAsRead: (client: IJMAPClient, emailId: string, read: boolean) => Promise<void>;
|
||||||
moveToMailbox: (client: IJMAPClient, emailId: string, mailboxId: string) => Promise<void>;
|
moveToMailbox: (client: IJMAPClient, emailId: string, mailboxId: string) => Promise<void>;
|
||||||
@@ -673,14 +673,14 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
sendRawEmail: async (client, rawMimeBlob, identityId, delayedUntil) => {
|
sendRawEmail: async (client, rawMimeBlob, identityId, delayedUntil, envelopeRecipients) => {
|
||||||
set({ isLoading: true, error: null });
|
set({ isLoading: true, error: null });
|
||||||
try {
|
try {
|
||||||
const mailboxes = await client.getMailboxes();
|
const mailboxes = await client.getMailboxes();
|
||||||
const sentMailbox = mailboxes.find(mb => mb.role === 'sent');
|
const sentMailbox = mailboxes.find(mb => mb.role === 'sent');
|
||||||
if (!sentMailbox) throw new Error('No sent mailbox found');
|
if (!sentMailbox) throw new Error('No sent mailbox found');
|
||||||
const draftsMailbox = mailboxes.find(mb => mb.role === 'drafts');
|
const draftsMailbox = mailboxes.find(mb => mb.role === 'drafts');
|
||||||
const result = await client.sendRawEmail(rawMimeBlob, identityId, sentMailbox.id, draftsMailbox?.id, delayedUntil);
|
const result = await client.sendRawEmail(rawMimeBlob, identityId, sentMailbox.id, draftsMailbox?.id, delayedUntil, envelopeRecipients);
|
||||||
set({
|
set({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
pendingUndoSend: result.scheduled && result.emailSubmissionId && result.sendAt
|
pendingUndoSend: result.scheduled && result.emailSubmissionId && result.sendAt
|
||||||
@@ -2095,10 +2095,15 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setScheduledView: (isScheduledView) => set(state => ({
|
setScheduledView: (isScheduledView) => set(state => {
|
||||||
isScheduledView,
|
const leavingScheduled = !isScheduledView && state.selectedMailbox === VIRTUAL_SCHEDULED_MAILBOX_ID;
|
||||||
selectedMailbox: isScheduledView ? VIRTUAL_SCHEDULED_MAILBOX_ID : state.selectedMailbox,
|
return {
|
||||||
})),
|
isScheduledView,
|
||||||
|
selectedMailbox: isScheduledView ? VIRTUAL_SCHEDULED_MAILBOX_ID : leavingScheduled ? "" : state.selectedMailbox,
|
||||||
|
selectedEmail: leavingScheduled ? null : state.selectedEmail,
|
||||||
|
selectedEmailIds: leavingScheduled ? new Set<string>() : state.selectedEmailIds,
|
||||||
|
};
|
||||||
|
}),
|
||||||
clearPendingUndoSend: () => set({ pendingUndoSend: null }),
|
clearPendingUndoSend: () => set({ pendingUndoSend: null }),
|
||||||
|
|
||||||
fetchScheduledEmails: async (client) => {
|
fetchScheduledEmails: async (client) => {
|
||||||
@@ -2191,6 +2196,10 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
|||||||
await client.cancelEmailSubmission(submissionId);
|
await client.cancelEmailSubmission(submissionId);
|
||||||
if (emailId) {
|
if (emailId) {
|
||||||
await client.deleteEmail(emailId);
|
await client.deleteEmail(emailId);
|
||||||
|
set(state => ({
|
||||||
|
selectedEmail: state.selectedEmail?.id === emailId ? null : state.selectedEmail,
|
||||||
|
selectedEmailIds: new Set(Array.from(state.selectedEmailIds).filter(id => id !== emailId)),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
if (get().pendingUndoSend?.submissionId === submissionId) {
|
if (get().pendingUndoSend?.submissionId === submissionId) {
|
||||||
set({ pendingUndoSend: null });
|
set({ pendingUndoSend: null });
|
||||||
@@ -2214,8 +2223,9 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
rescheduleScheduledEmail: async (client, submissionId, emailId, identityId, delayedUntil) => {
|
rescheduleScheduledEmail: async (client, submissionId, emailId, identityId, delayedUntil) => {
|
||||||
|
let result: SendEmailResult | undefined;
|
||||||
try {
|
try {
|
||||||
const result = await client.rescheduleEmailSubmission(submissionId, emailId, identityId, delayedUntil);
|
result = await client.rescheduleEmailSubmission(submissionId, emailId, identityId, delayedUntil);
|
||||||
const pendingUndoSend = get().pendingUndoSend;
|
const pendingUndoSend = get().pendingUndoSend;
|
||||||
if (pendingUndoSend?.submissionId === submissionId) {
|
if (pendingUndoSend?.submissionId === submissionId) {
|
||||||
set({ pendingUndoSend: { ...pendingUndoSend, submissionId: result.emailSubmissionId || submissionId, sendAt: result.sendAt || delayedUntil } });
|
set({ pendingUndoSend: { ...pendingUndoSend, submissionId: result.emailSubmissionId || submissionId, sendAt: result.sendAt || delayedUntil } });
|
||||||
@@ -2223,6 +2233,19 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
|||||||
return result;
|
return result;
|
||||||
} finally {
|
} finally {
|
||||||
await get().fetchScheduledEmails(client);
|
await get().fetchScheduledEmails(client);
|
||||||
|
if (result && get().selectedEmail?.id === emailId) {
|
||||||
|
const refreshed = get().scheduledEmails.find(email => email.id === emailId);
|
||||||
|
set(state => ({
|
||||||
|
selectedEmail: refreshed || (state.selectedEmail ? {
|
||||||
|
...state.selectedEmail,
|
||||||
|
emailSubmissionId: result?.emailSubmissionId || submissionId,
|
||||||
|
scheduledSendAt: result?.sendAt || delayedUntil,
|
||||||
|
scheduledIdentityId: identityId,
|
||||||
|
scheduledUndoStatus: 'pending' as const,
|
||||||
|
isScheduled: true,
|
||||||
|
} : state.selectedEmail),
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user