import { useMemo, useState } from "react"; import { Plus, Trash2 } from "lucide-react"; import { useMail } from "@/store/mail"; import { HEADER_CHOICES, HEADER_OPS, type SieveAction, type SieveRule, type SieveTest } from "@/lib/sieve"; import { Dialog, promptDialog } from "@/ui/dialog"; import { toast } from "@/ui/toast"; import type { Id } from "@/jmap/types"; export interface RuleDialogProps { rule: SieveRule; onClose: () => void; /** Called with the rule and whether the user asked to apply it to existing messages now. */ onSave: (r: SieveRule, applyNow: boolean) => void; /** When set, offers "Also apply to existing messages in ". */ applyMailbox?: { id: Id; name: string } | null; title?: string; saveLabel?: string; } export function RuleDialog({ rule, onClose, onSave, applyMailbox, title, saveLabel }: RuleDialogProps) { const [r, setR] = useState(rule); const [applyNow, setApplyNow] = useState(Boolean(applyMailbox)); const mailboxes = useMail((s) => s.mailboxes); const mailboxPath = useMail((s) => s.mailboxPath); const folders = useMemo(() => Object.values(mailboxes).map((m) => ({ id: m.id, path: mailboxPath(m.id) })).sort((a, b) => a.path.localeCompare(b.path)), [mailboxes, mailboxPath]); const setTest = (i: number, t: SieveTest) => setR({ ...r, tests: r.tests.map((x, j) => (j === i ? t : x)) }); const setAction = (i: number, a: SieveAction) => setR({ ...r, actions: r.actions.map((x, j) => (j === i ? a : x)) }); return ( {applyMailbox && ( )} }>
setR({ ...r, name: e.target.value })} autoFocus />
When
{r.tests.map((t, i) => { // A header the dropdown doesn't list needs a box to type its name in, // which takes a column of its own — the comparator keeps its. const customHeader = t.type === "header" && !HEADER_CHOICES.some((h) => h.value === t.header && h.value !== "__custom__"); return (
{customHeader && t.type === "header" && ( setTest(i, { ...t, header: e.target.value })} /> )} {t.type === "size" ? ( ) : t.type === "body" ? ( ) : t.type === "true" ? : ( )} {t.type === "size" ? (
setTest(i, { ...t, value: Number(e.target.value) * 1024 })} />KB
) : t.type === "true" ? : t.type === "header" && (t.op === "exists" || t.op === "notexists") ? : ( setTest(i, { ...t, value: e.target.value } as SieveTest)} /> )}
); })}
Then
{r.actions.map((a, i) => (
{a.type === "fileinto" ? (
) : a.type === "redirect" ? (
setAction(i, { ...a, address: e.target.value })} />
) : a.type === "reject" ? ( setAction(i, { ...a, reason: e.target.value })} /> ) : a.type === "addflag" || a.type === "setflag" || a.type === "removeflag" ? ( setAction(i, { ...a, flag: e.target.value })} /> ) : }
))}
); }