diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx index d6c68287..180cf4b7 100644 --- a/components/email/email-composer.tsx +++ b/components/email/email-composer.tsx @@ -406,6 +406,7 @@ export function EmailComposer({ const [smimePassphraseError, setSmimePassphraseError] = useState(''); const [showAttachmentWarning, setShowAttachmentWarning] = useState(false); const [attachmentWarningKeyword, setAttachmentWarningKeyword] = useState(''); + const [attachmentWarningDelayedUntil, setAttachmentWarningDelayedUntil] = useState(); const [showScheduleDialog, setShowScheduleDialog] = useState(false); const [scheduleValue, setScheduleValue] = useState(''); const [scheduleError, setScheduleError] = useState(''); @@ -1227,8 +1228,8 @@ export function EmailComposer({ const time = new Date(value).getTime(); if (!Number.isFinite(time)) return t('schedule_send_invalid'); if (time <= Date.now()) return t('schedule_send_future'); - if (client) { - const maxDelayedSend = client.getMaxDelayedSend(); + if (composerClient) { + const maxDelayedSend = composerClient.getMaxDelayedSend(); if (maxDelayedSend > 0 && time > Date.now() + maxDelayedSend * 1000) { return t('schedule_send_too_late'); } @@ -1239,7 +1240,7 @@ export function EmailComposer({ const resolveDelayedUntil = async (requestedDelayedUntil?: string): Promise => { if (requestedDelayedUntil) return requestedDelayedUntil; if (sendDelaySeconds === 0) return undefined; - if (client?.hasDelayedSend()) { + if (composerClient?.hasDelayedSend()) { return new Date(Date.now() + sendDelaySeconds * 1000).toISOString(); } const confirmed = window.confirm(t('send_delay_unsupported_confirm')); @@ -1320,6 +1321,7 @@ export function EmailComposer({ const matched = attachmentReminderKeywords.find(kw => searchText.includes(kw.toLowerCase())); if (matched) { setAttachmentWarningKeyword(matched); + setAttachmentWarningDelayedUntil(delayedUntil); setShowAttachmentWarning(true); return; } @@ -1654,7 +1656,7 @@ export function EmailComposer({ }; const handleScheduleSend = () => { - if (!client?.hasDelayedSend()) { + if (!composerClient?.hasDelayedSend()) { setScheduleError(t('schedule_send_unsupported')); return; } @@ -1742,7 +1744,7 @@ export function EmailComposer({ if (isScheduleShortcut) { e.preventDefault(); - if (!e.repeat && client?.hasDelayedSend()) openScheduleDialog(); + if (!e.repeat && composerClient?.hasDelayedSend()) openScheduleDialog(); } }; @@ -2225,7 +2227,7 @@ export function EmailComposer({ > {t('discard')} - {client?.hasDelayedSend() ? ( + {composerClient?.hasDelayedSend() ? (
-
diff --git a/components/email/email-list.tsx b/components/email/email-list.tsx index 2daa36ee..2d1c3e0c 100644 --- a/components/email/email-list.tsx +++ b/components/email/email-list.tsx @@ -471,7 +471,7 @@ export function EmailList({ onToggleExpand={() => handleToggleThreadExpansion(thread.threadId)} onEmailSelect={(email) => onEmailSelect?.(email)} onEmailDoubleClick={onEmailDoubleClick ? (email) => onEmailDoubleClick(email) : undefined} - onContextMenu={isScheduledView ? undefined : openContextMenu} + onContextMenu={openContextMenu} onOpenConversation={onOpenConversation} onToggleStar={onToggleStar ? (email) => onToggleStar(email) : undefined} onMarkAsRead={onMarkAsRead ? (email, read) => onMarkAsRead(email, read) : undefined} @@ -535,8 +535,8 @@ export function EmailList({ onMarkAsSpam={() => onMarkAsSpam?.(contextMenu.data!)} onUndoSpam={() => onUndoSpam?.(contextMenu.data!)} onEditDraft={() => onEditDraft?.(contextMenu.data!)} - onCancelScheduled={isScheduledView ? undefined : () => onCancelScheduled?.(contextMenu.data!)} - onCancelScheduledForEdit={isScheduledView ? undefined : () => onCancelScheduledForEdit?.(contextMenu.data!)} + onCancelScheduled={() => onCancelScheduled?.(contextMenu.data!)} + onCancelScheduledForEdit={() => onCancelScheduledForEdit?.(contextMenu.data!)} onRescheduleScheduled={undefined} onBatchMarkAsRead={(read) => client && batchMarkAsRead(client, read)} onBatchDelete={() => client && batchDelete(client)} diff --git a/components/pro/pro-compose-tab-body.tsx b/components/pro/pro-compose-tab-body.tsx index ee845788..35686e5d 100644 --- a/components/pro/pro-compose-tab-body.tsx +++ b/components/pro/pro-compose-tab-body.tsx @@ -39,7 +39,7 @@ export function ProComposeTabBody({ tabId, data }: ProComposeTabBodyProps) { const handleSend = useCallback(async (sendData: Parameters['onSend']>>[0]) => { if (!client) return; try { - await sendEmail( + const result = await sendEmail( client, sendData.to, sendData.subject, @@ -58,6 +58,11 @@ export function ProComposeTabBody({ tabId, data }: ProComposeTabBodyProps) { sendData.envelopeMailFrom, ); + if (result.scheduled) { + closeTab(tabIdRef.current); + return; + } + // Mark the original message as $answered / $forwarded so the standard // viewer and list reflect the action (same behaviour as inline compose). if (data.sourceEmailId && (data.mode === 'reply' || data.mode === 'replyAll')) { diff --git a/stores/email-store.ts b/stores/email-store.ts index 89bdb27e..afa845ae 100644 --- a/stores/email-store.ts +++ b/stores/email-store.ts @@ -2606,7 +2606,13 @@ export const useEmailStore = create((set, get) => ({ cancelUndoSend: async (client, pending) => { await client.cancelEmailSubmission(pending.submissionId); - if (pending.emailId && !pending.isSmime) { + if (pending.emailId && pending.isSmime) { + await client.deleteEmail(pending.emailId); + set(state => ({ + selectedEmail: state.selectedEmail?.id === pending.emailId ? null : state.selectedEmail, + selectedEmailIds: new Set(Array.from(state.selectedEmailIds).filter(id => id !== pending.emailId)), + })); + } else if (pending.emailId) { const mailboxes = get().mailboxes.length > 0 ? get().mailboxes : await client.getMailboxes(); const draftsMailbox = mailboxes.find(mb => mb.role === 'drafts'); const sentMailbox = mailboxes.find(mb => mb.role === 'sent'); @@ -2616,7 +2622,7 @@ export const useEmailStore = create((set, get) => ({ } await get().refreshScheduledMetadata(client); set({ pendingUndoSend: null }); - return pending.emailId ? client.getEmail(pending.emailId) : null; + return pending.emailId && !pending.isSmime ? client.getEmail(pending.emailId) : null; }, loadMockData: () => {