This commit is contained in:
Lucas Gaitzsch
2026-05-08 22:37:16 +02:00
parent c458091698
commit f45b67fe19
10 changed files with 204 additions and 112 deletions
+23 -22
View File
@@ -72,7 +72,7 @@ interface EmailComposerProps {
attachments?: Array<{ blobId: string; name: string; type: string; size: number; disposition?: 'attachment' | 'inline'; cid?: string }>;
inReplyTo?: string[];
references?: string[];
sendAt?: string;
delayedUntil?: string;
}) => void | Promise<void>;
onScheduledSendCreated?: () => void | Promise<void>;
onClose?: () => void;
@@ -876,8 +876,8 @@ export function EmailComposer({
return null;
};
const getEffectiveSendAt = async (explicitSendAt?: string): Promise<string | undefined> => {
if (explicitSendAt) return explicitSendAt;
const resolveDelayedUntil = async (requestedDelayedUntil?: string): Promise<string | undefined> => {
if (requestedDelayedUntil) return requestedDelayedUntil;
if (sendDelaySeconds === 0) return undefined;
if (client?.hasDelayedSend()) {
return new Date(Date.now() + sendDelaySeconds * 1000).toISOString();
@@ -932,7 +932,7 @@ export function EmailComposer({
};
};
const handleSend = async (skipAttachmentCheck = false, sendAt?: string) => {
const handleSend = async (skipAttachmentCheck = false, delayedUntil?: string) => {
const ccAddresses = cc.split(",").map(e => e.trim()).filter(Boolean);
const bccAddresses = bcc.split(",").map(e => e.trim()).filter(Boolean);
@@ -1014,7 +1014,7 @@ export function EmailComposer({
const inlineAttachments = rewritten?.attachments ?? [];
try {
const effectiveSendAt = await getEffectiveSendAt(sendAt);
const effectiveDelayedUntil = await resolveDelayedUntil(delayedUntil);
// Let plugins veto the send (external-mail warning, mistyped-domain
// guards, etc.). Returning false from any handler aborts before either
// the S/MIME or standard JMAP path runs.
@@ -1151,8 +1151,8 @@ export function EmailComposer({
}
// 7. Send via raw email path
const result = await sendRawEmail(client, payload, currentIdentity.id, effectiveSendAt);
if (effectiveSendAt && finalDraftId) {
const result = await sendRawEmail(client, payload, currentIdentity.id, effectiveDelayedUntil);
if (effectiveDelayedUntil && finalDraftId) {
client.deleteEmail(finalDraftId).catch(err => {
debug.warn('email', 'Scheduled S/MIME send created, but plaintext draft cleanup failed:', err);
toast.warning(t('schedule_send_cleanup_warning'));
@@ -1199,7 +1199,7 @@ export function EmailComposer({
attachments: uploadedAttachments.length > 0 ? uploadedAttachments : undefined,
inReplyTo: threadingHeaders?.inReplyTo,
references: threadingHeaders?.references,
sendAt: effectiveSendAt,
delayedUntil: effectiveDelayedUntil,
});
if (mode === 'reply' || mode === 'replyAll') {
@@ -1700,20 +1700,21 @@ export function EmailComposer({
>
<BookmarkPlus className="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => {
setScheduleError('');
setScheduleValue('');
setShowScheduleDialog(true);
}}
disabled={!client?.hasDelayedSend()}
title={client?.hasDelayedSend() ? t('schedule_send') : t('schedule_send_unsupported')}
className="h-9 w-9"
>
<CalendarClock className="w-4 h-4" />
</Button>
{client?.hasDelayedSend() && (
<Button
variant="ghost"
size="icon"
onClick={() => {
setScheduleError('');
setScheduleValue('');
setShowScheduleDialog(true);
}}
title={t('schedule_send')}
className="h-9 w-9"
>
<CalendarClock className="w-4 h-4" />
</Button>
)}
{/* S/MIME toggles */}
{canSmimeSign && (
+6 -6
View File
@@ -43,7 +43,7 @@ interface EmailListProps {
onLoadMoreScheduled?: () => void;
onCancelScheduled?: (email: Email) => void | Promise<void>;
onCancelScheduledForEdit?: (email: Email) => void | Promise<void>;
onRescheduleScheduled?: (email: Email, sendAt: string) => void | Promise<void>;
onRescheduleScheduled?: (email: Email, delayedUntil: string) => void | Promise<void>;
}
export function EmailList({
@@ -235,7 +235,7 @@ export function EmailList({
}
}, [client, hasMoreEmails, isLoadingMore, isLoading, isScheduledView, loadMoreEmails, onLoadMoreScheduled]);
const promptForRescheduleSendAt = useCallback((): string | null => {
const promptForRescheduleDelayedUntil = useCallback((): string | null => {
const value = window.prompt(t('reschedule_prompt'));
if (!value) return null;
const time = new Date(value).getTime();
@@ -518,8 +518,8 @@ export function EmailList({
size="sm"
className="h-7 px-2"
onClick={() => {
const sendAt = promptForRescheduleSendAt();
if (sendAt) onRescheduleScheduled?.(thread.latestEmail, sendAt);
const delayedUntil = promptForRescheduleDelayedUntil();
if (delayedUntil) onRescheduleScheduled?.(thread.latestEmail, delayedUntil);
}}
>
<CalendarClock className="w-3.5 h-3.5 mr-1" />
@@ -581,8 +581,8 @@ export function EmailList({
onCancelScheduled={() => onCancelScheduled?.(contextMenu.data!)}
onCancelScheduledForEdit={() => onCancelScheduledForEdit?.(contextMenu.data!)}
onRescheduleScheduled={() => {
const sendAt = promptForRescheduleSendAt();
if (sendAt) onRescheduleScheduled?.(contextMenu.data!, sendAt);
const delayedUntil = promptForRescheduleDelayedUntil();
if (delayedUntil) onRescheduleScheduled?.(contextMenu.data!, delayedUntil);
}}
onBatchMarkAsRead={(read) => client && batchMarkAsRead(client, read)}
onBatchDelete={() => client && batchDelete(client)}
+6 -6
View File
@@ -121,7 +121,7 @@ interface EmailViewerProps {
onEditDraft?: () => void;
onCancelScheduled?: () => void;
onCancelScheduledForEdit?: () => void;
onRescheduleScheduled?: (sendAt: string) => void;
onRescheduleScheduled?: (delayedUntil: string) => void;
onCompose?: () => void;
currentUserEmail?: string;
currentUserName?: string;
@@ -883,7 +883,7 @@ export function EmailViewer({
const { isTablet, isMobile } = useDeviceDetection();
const { tabletListVisible } = useUIStore();
const { identities, client, isDemoMode, activeAccountId } = useAuthStore();
const promptForRescheduleSendAt = useCallback((): string | null => {
const promptForRescheduleDelayedUntil = useCallback((): string | null => {
const value = window.prompt(t('reschedule_prompt'));
if (!value) return null;
const time = new Date(value).getTime();
@@ -3147,8 +3147,8 @@ export function EmailViewer({
variant="ghost"
size="sm"
onClick={() => {
const sendAt = promptForRescheduleSendAt();
if (sendAt) onRescheduleScheduled?.(sendAt);
const delayedUntil = promptForRescheduleDelayedUntil();
if (delayedUntil) onRescheduleScheduled?.(delayedUntil);
}}
className="hidden sm:flex sm:h-8"
title={t('reschedule_send')}
@@ -4598,8 +4598,8 @@ export function EmailViewer({
size="sm"
variant="outline"
onClick={() => {
const sendAt = promptForRescheduleSendAt();
if (sendAt) onRescheduleScheduled?.(sendAt);
const delayedUntil = promptForRescheduleDelayedUntil();
if (delayedUntil) onRescheduleScheduled?.(delayedUntil);
}}
>
{t('reschedule_send')}