feat(webmail): trusted senders on the server, Not spam, and a trust pill in the reader
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d7f9659b5e
commit
d2611d39fa
9 files changed
+203
-37
No files matched your search
@@ -11,6 +11,8 @@ import {
|
||||
FolderInput,
|
||||
Forward,
|
||||
Image as ImageIcon,
|
||||
ShieldAlert,
|
||||
ShieldCheck,
|
||||
MailOpen,
|
||||
Pencil,
|
||||
Reply,
|
||||
@@ -72,14 +74,18 @@ interface Props {
|
||||
/** Whether anything was held back, so the privacy bar is not always shown. */
|
||||
hasBlockedContent: boolean;
|
||||
imagesLoaded: boolean;
|
||||
senderRemembered: boolean;
|
||||
/** Whether the account trusts this sender: not spam, and images load. */
|
||||
senderTrusted: boolean;
|
||||
/** The message is being read in the spam folder. */
|
||||
isJunk: boolean;
|
||||
onClose: () => void;
|
||||
onToggleSeen: () => void;
|
||||
onToggleFlagged: () => void;
|
||||
onDelete: () => void;
|
||||
onMove: (target: string) => void;
|
||||
onToggleImages: () => void;
|
||||
onToggleRemember: () => void;
|
||||
onToggleTrusted: () => void;
|
||||
onNotSpam: () => void;
|
||||
}
|
||||
|
||||
/** The sender's name and address, from a header like `Jenny <jenny@x.test>`. */
|
||||
@@ -324,6 +330,22 @@ export function MessageReader(props: Props) {
|
||||
<span className="text-[13px] text-slate-400 [overflow-wrap:anywhere]"><{address}></span>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-0.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onToggleTrusted}
|
||||
aria-pressed={props.senderTrusted}
|
||||
title={props.senderTrusted ? t("mail.trustedSenderHint") : t("mail.trustSenderHint")}
|
||||
className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11.5px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400 ${
|
||||
props.senderTrusted
|
||||
? "bg-indigo-500/20 text-indigo-200 hover:bg-indigo-500/30"
|
||||
: "text-slate-400 shadow-[inset_0_0_0_1px_rgba(233,233,237,0.18)] hover:bg-white/[0.07] hover:text-slate-200"
|
||||
}`}
|
||||
>
|
||||
<ShieldCheck className="h-3 w-3" aria-hidden />
|
||||
{props.senderTrusted ? t("mail.trustedSender") : t("mail.trustSender")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-[12.5px] leading-relaxed text-slate-400 [overflow-wrap:anywhere]">
|
||||
{visibleTo.length > 0 && (
|
||||
<>
|
||||
@@ -361,18 +383,35 @@ export function MessageReader(props: Props) {
|
||||
error. Two separate decisions - look now, or stop asking about
|
||||
this sender - so a reader can see one message without granting
|
||||
anything permanently. */}
|
||||
{(props.hasBlockedContent || props.senderRemembered) && (
|
||||
{props.isJunk && (
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-2 rounded-lg bg-amber-500/10 px-3 py-2.5 shadow-[inset_0_0_0_1px_rgba(245,158,11,0.35)]">
|
||||
<ShieldAlert className="h-4 w-4 flex-none text-amber-400" aria-hidden />
|
||||
<span className="min-w-[180px] flex-1 text-[12.5px] leading-relaxed text-amber-100">
|
||||
{t("mail.junkNotice")}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onNotSpam}
|
||||
className="rounded-md bg-amber-400 px-2.5 py-1 text-[12.5px] font-semibold text-slate-900 transition-colors hover:bg-amber-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-200"
|
||||
>
|
||||
{t("mail.notSpam")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.hasBlockedContent && (
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 rounded-lg bg-dark-card px-3 py-2 shadow-edge">
|
||||
<ImageIcon className="h-4 w-4 flex-none text-indigo-400" aria-hidden />
|
||||
<span className="min-w-[180px] flex-1 text-[12.5px] leading-relaxed text-slate-300">
|
||||
{props.imagesLoaded ? t("mail.imagesEnabled") : t("mail.remoteContentBlocked")}
|
||||
</span>
|
||||
<button type="button" onClick={props.onToggleImages} className={infoLinkClass}>
|
||||
{props.imagesLoaded ? t("mail.hideImages") : t("mail.loadImages")}
|
||||
</button>
|
||||
<button type="button" onClick={props.onToggleRemember} className={infoLinkClass}>
|
||||
{props.senderRemembered ? t("mail.forgetSender") : t("mail.alwaysFromSender")}
|
||||
</button>
|
||||
{/* A trusted sender's images are shown for as long as they are
|
||||
trusted, so hiding them here would do nothing. */}
|
||||
{!props.senderTrusted && (
|
||||
<button type="button" onClick={props.onToggleImages} className={infoLinkClass}>
|
||||
{props.imagesLoaded ? t("mail.hideImages") : t("mail.loadImages")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in new issue
Block a user