Fix: hide the spam action in Sent, Drafts and Scheduled

Marking your own outgoing mail as spam makes no sense, but the action
was offered in every non-junk folder: context menu, hover quick-actions,
viewer toolbar and its overflow menu, plus the "!" shortcut.

All surfaces now skip the action when the folder role is sent, drafts or
scheduled, and the shortcut is a no-op there. Scheduled messages were
already covered per-email via isScheduled; the role check additionally
covers the server-side Scheduled folder before that annotation loads.

The hover quick-actions bar gets a spamApplicable prop for this, since
it renders its buttons without knowing the folder.
This commit is contained in:
dealerweb
2026-07-06 11:04:37 +02:00
committed by Linus Rath
parent 06ddda688d
commit d7a64fd9d6
6 changed files with 38 additions and 17 deletions
+22 -15
View File
@@ -153,6 +153,9 @@ export function EmailContextMenu({
const currentColors = getCurrentColors(email.keywords);
const showBatchActions = isMultiSelect && selectedCount > 1;
const isInJunkFolder = currentMailboxRole === 'junk';
// Marking your own outgoing mail as spam makes no sense - hide the action
// in Sent, Drafts and Scheduled.
const spamApplicable = !['sent', 'drafts', 'scheduled'].includes(currentMailboxRole || '');
const isScheduled = email.isScheduled === true;
const canCancelScheduled = isScheduled && email.scheduledUndoStatus === 'pending';
@@ -385,22 +388,26 @@ export function EmailContextMenu({
</ContextMenuSubMenu>
)}
<ContextMenuSeparator />
{/* Spam - contextual based on folder; pointless on own outgoing mail */}
{spamApplicable && (
<>
<ContextMenuSeparator />
{/* Spam - contextual based on folder */}
<ContextMenuItem
icon={isInJunkFolder ? ShieldCheck : ShieldAlert}
label={isInJunkFolder ? t("not_spam") : t("mark_as_spam")}
onClick={() =>
handleAction(
showBatchActions
? (isInJunkFolder ? onBatchUndoSpam! : onBatchMarkAsSpam!)
: (isInJunkFolder ? onUndoSpam! : onMarkAsSpam!)
)
}
disabled={showBatchActions ? (isInJunkFolder ? !onBatchUndoSpam : !onBatchMarkAsSpam) : (isInJunkFolder ? !onUndoSpam : !onMarkAsSpam)}
destructive={!isInJunkFolder}
/>
<ContextMenuItem
icon={isInJunkFolder ? ShieldCheck : ShieldAlert}
label={isInJunkFolder ? t("not_spam") : t("mark_as_spam")}
onClick={() =>
handleAction(
showBatchActions
? (isInJunkFolder ? onBatchUndoSpam! : onBatchMarkAsSpam!)
: (isInJunkFolder ? onUndoSpam! : onMarkAsSpam!)
)
}
disabled={showBatchActions ? (isInJunkFolder ? !onBatchUndoSpam : !onBatchMarkAsSpam) : (isInJunkFolder ? !onUndoSpam : !onMarkAsSpam)}
destructive={!isInJunkFolder}
/>
</>
)}
<ContextMenuSeparator />
+4
View File
@@ -21,6 +21,8 @@ interface EmailHoverActionsProps {
// the spam quick-action flips to "not spam".
isInJunk?: boolean;
onUndoSpam?: () => void;
// Hidden where marking spam is meaningless for self-authored mail (Drafts, Sent).
spamApplicable?: boolean;
}
const ACTION_CONFIG: Record<HoverAction, {
@@ -78,6 +80,7 @@ export function EmailHoverActions({
onMarkAsSpam,
isInJunk = false,
onUndoSpam,
spamApplicable = true,
}: EmailHoverActionsProps) {
const hoverActions = useSettingsStore((state) => state.hoverActions);
const hoverActionsMode = useSettingsStore((state) => state.hoverActionsMode);
@@ -121,6 +124,7 @@ export function EmailHoverActions({
const actionButtons = hoverActions.map((actionId) => {
const config = ACTION_CONFIG[actionId];
if (!config) return null;
if (actionId === "spam" && !spamApplicable) return null;
const Icon = config.icon;
// In a junk context the spam action becomes "not spam".
+1
View File
@@ -338,6 +338,7 @@ export function EmailListItem({ email, selected, onClick, onDoubleClick, onConte
onMarkAsSpam={onMarkAsSpam}
onUndoSpam={onUndoSpam}
isInJunk={currentMailboxRole === 'junk'}
spamApplicable={!['sent', 'drafts', 'scheduled'].includes(currentMailboxRole || '')}
/>
</div>
);
+5 -2
View File
@@ -693,6 +693,9 @@ export function EmailViewer({
// Detect if current mailbox is Junk folder
const isInJunkFolder = currentMailboxRole === 'junk';
// Marking your own outgoing mail as spam makes no sense - hide the action
// in Sent, Drafts and Scheduled.
const spamApplicable = !['sent', 'drafts', 'scheduled'].includes(currentMailboxRole || '');
// Detect if the email is a draft
const isDraft = email?.keywords?.['$draft'] === true;
@@ -2922,7 +2925,7 @@ export function EmailViewer({
</div>
{/* Spam */}
{(onMarkAsSpam || onUndoSpam) && (
{spamApplicable && (onMarkAsSpam || onUndoSpam) && (
<Button
variant="ghost"
size="sm"
@@ -3156,7 +3159,7 @@ export function EmailViewer({
</div>
)}
{/* Overflow: spam */}
{(onMarkAsSpam || onUndoSpam) && (
{spamApplicable && (onMarkAsSpam || onUndoSpam) && (
<button
onClick={() => { (isInJunkFolder ? onUndoSpam : onMarkAsSpam)?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }}
className={cn("w-full px-3 py-1.5 text-sm text-left hover:bg-muted text-foreground flex items-center gap-2", hiddenPriorities.has(7) ? "" : "sm:hidden")}
+2
View File
@@ -405,6 +405,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
onMarkAsSpam={onMarkAsSpam}
onUndoSpam={onUndoSpam}
isInJunk={currentMailboxRole === 'junk'}
spamApplicable={!['sent', 'drafts', 'scheduled'].includes(currentMailboxRole || '')}
/>
)}
</div>
@@ -875,6 +876,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
onMarkAsSpam={onMarkAsSpam ? () => onMarkAsSpam(latestEmail) : undefined}
onUndoSpam={onUndoSpam ? () => onUndoSpam(latestEmail) : undefined}
isInJunk={currentMailboxRole === 'junk'}
spamApplicable={!['sent', 'drafts', 'scheduled'].includes(currentMailboxRole || '')}
/>
)}
</div>