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
+4
View File
@@ -621,6 +621,10 @@ export default function Home() {
onToggleSpam: async () => { onToggleSpam: async () => {
if (isScheduledView) return; if (isScheduledView) return;
const currentMailbox = mailboxes.find(m => m.id === selectedMailbox); const currentMailbox = mailboxes.find(m => m.id === selectedMailbox);
// Marking your own outgoing mail as spam makes no sense - the toolbar
// and menus hide the action in Sent/Drafts/Scheduled, so the shortcut
// is a no-op there too.
if (['sent', 'drafts', 'scheduled'].includes(currentMailbox?.role || '')) return;
const isInJunk = currentMailbox?.role === 'junk'; const isInJunk = currentMailbox?.role === 'junk';
if (selectedEmailIds.size > 0 && client) { if (selectedEmailIds.size > 0 && client) {
const ids = Array.from(selectedEmailIds); const ids = Array.from(selectedEmailIds);
+22 -15
View File
@@ -153,6 +153,9 @@ export function EmailContextMenu({
const currentColors = getCurrentColors(email.keywords); const currentColors = getCurrentColors(email.keywords);
const showBatchActions = isMultiSelect && selectedCount > 1; const showBatchActions = isMultiSelect && selectedCount > 1;
const isInJunkFolder = currentMailboxRole === 'junk'; 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 isScheduled = email.isScheduled === true;
const canCancelScheduled = isScheduled && email.scheduledUndoStatus === 'pending'; const canCancelScheduled = isScheduled && email.scheduledUndoStatus === 'pending';
@@ -385,22 +388,26 @@ export function EmailContextMenu({
</ContextMenuSubMenu> </ContextMenuSubMenu>
)} )}
<ContextMenuSeparator /> {/* Spam - contextual based on folder; pointless on own outgoing mail */}
{spamApplicable && (
<>
<ContextMenuSeparator />
{/* Spam - contextual based on folder */} <ContextMenuItem
<ContextMenuItem icon={isInJunkFolder ? ShieldCheck : ShieldAlert}
icon={isInJunkFolder ? ShieldCheck : ShieldAlert} label={isInJunkFolder ? t("not_spam") : t("mark_as_spam")}
label={isInJunkFolder ? t("not_spam") : t("mark_as_spam")} onClick={() =>
onClick={() => handleAction(
handleAction( showBatchActions
showBatchActions ? (isInJunkFolder ? onBatchUndoSpam! : onBatchMarkAsSpam!)
? (isInJunkFolder ? onBatchUndoSpam! : onBatchMarkAsSpam!) : (isInJunkFolder ? onUndoSpam! : onMarkAsSpam!)
: (isInJunkFolder ? onUndoSpam! : onMarkAsSpam!) )
) }
} disabled={showBatchActions ? (isInJunkFolder ? !onBatchUndoSpam : !onBatchMarkAsSpam) : (isInJunkFolder ? !onUndoSpam : !onMarkAsSpam)}
disabled={showBatchActions ? (isInJunkFolder ? !onBatchUndoSpam : !onBatchMarkAsSpam) : (isInJunkFolder ? !onUndoSpam : !onMarkAsSpam)} destructive={!isInJunkFolder}
destructive={!isInJunkFolder} />
/> </>
)}
<ContextMenuSeparator /> <ContextMenuSeparator />
+4
View File
@@ -21,6 +21,8 @@ interface EmailHoverActionsProps {
// the spam quick-action flips to "not spam". // the spam quick-action flips to "not spam".
isInJunk?: boolean; isInJunk?: boolean;
onUndoSpam?: () => void; onUndoSpam?: () => void;
// Hidden where marking spam is meaningless for self-authored mail (Drafts, Sent).
spamApplicable?: boolean;
} }
const ACTION_CONFIG: Record<HoverAction, { const ACTION_CONFIG: Record<HoverAction, {
@@ -78,6 +80,7 @@ export function EmailHoverActions({
onMarkAsSpam, onMarkAsSpam,
isInJunk = false, isInJunk = false,
onUndoSpam, onUndoSpam,
spamApplicable = true,
}: EmailHoverActionsProps) { }: EmailHoverActionsProps) {
const hoverActions = useSettingsStore((state) => state.hoverActions); const hoverActions = useSettingsStore((state) => state.hoverActions);
const hoverActionsMode = useSettingsStore((state) => state.hoverActionsMode); const hoverActionsMode = useSettingsStore((state) => state.hoverActionsMode);
@@ -121,6 +124,7 @@ export function EmailHoverActions({
const actionButtons = hoverActions.map((actionId) => { const actionButtons = hoverActions.map((actionId) => {
const config = ACTION_CONFIG[actionId]; const config = ACTION_CONFIG[actionId];
if (!config) return null; if (!config) return null;
if (actionId === "spam" && !spamApplicable) return null;
const Icon = config.icon; const Icon = config.icon;
// In a junk context the spam action becomes "not spam". // 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} onMarkAsSpam={onMarkAsSpam}
onUndoSpam={onUndoSpam} onUndoSpam={onUndoSpam}
isInJunk={currentMailboxRole === 'junk'} isInJunk={currentMailboxRole === 'junk'}
spamApplicable={!['sent', 'drafts', 'scheduled'].includes(currentMailboxRole || '')}
/> />
</div> </div>
); );
+5 -2
View File
@@ -693,6 +693,9 @@ export function EmailViewer({
// Detect if current mailbox is Junk folder // Detect if current mailbox is Junk folder
const isInJunkFolder = currentMailboxRole === 'junk'; 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 // Detect if the email is a draft
const isDraft = email?.keywords?.['$draft'] === true; const isDraft = email?.keywords?.['$draft'] === true;
@@ -2922,7 +2925,7 @@ export function EmailViewer({
</div> </div>
{/* Spam */} {/* Spam */}
{(onMarkAsSpam || onUndoSpam) && ( {spamApplicable && (onMarkAsSpam || onUndoSpam) && (
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
@@ -3156,7 +3159,7 @@ export function EmailViewer({
</div> </div>
)} )}
{/* Overflow: spam */} {/* Overflow: spam */}
{(onMarkAsSpam || onUndoSpam) && ( {spamApplicable && (onMarkAsSpam || onUndoSpam) && (
<button <button
onClick={() => { (isInJunkFolder ? onUndoSpam : onMarkAsSpam)?.(); setMoreMenuOpen(false); setMoreMenuSub(null); }} 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")} 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} onMarkAsSpam={onMarkAsSpam}
onUndoSpam={onUndoSpam} onUndoSpam={onUndoSpam}
isInJunk={currentMailboxRole === 'junk'} isInJunk={currentMailboxRole === 'junk'}
spamApplicable={!['sent', 'drafts', 'scheduled'].includes(currentMailboxRole || '')}
/> />
)} )}
</div> </div>
@@ -875,6 +876,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
onMarkAsSpam={onMarkAsSpam ? () => onMarkAsSpam(latestEmail) : undefined} onMarkAsSpam={onMarkAsSpam ? () => onMarkAsSpam(latestEmail) : undefined}
onUndoSpam={onUndoSpam ? () => onUndoSpam(latestEmail) : undefined} onUndoSpam={onUndoSpam ? () => onUndoSpam(latestEmail) : undefined}
isInJunk={currentMailboxRole === 'junk'} isInJunk={currentMailboxRole === 'junk'}
spamApplicable={!['sent', 'drafts', 'scheduled'].includes(currentMailboxRole || '')}
/> />
)} )}
</div> </div>