import { useSettings } from "@/store/settings"; import { Switch } from "@/ui/misc"; import { browserTimeZone, listTimeZones } from "@/lib/dates"; import { toast } from "@/ui/toast"; import { useState } from "react"; import { t, tNode } from "@/lib/i18n"; import { MAX_LEVELS, type SortField, type SortPreset } from "@/lib/listSort"; import { canUnregisterMailtoHandler, isInstalledApp, mailtoHandlerRequested, mailtoHandlerSupport, registerMailtoHandler, unregisterMailtoHandler, } from "@/lib/mailhandler"; import { browserLocale, formatClock, formatDate, formatFullDateTime, getServerLocale, localeLabel, localeOptions, withPrefs, type DateFormat, } from "@/lib/datetime"; import { isEnforced } from "@/lib/settingsPolicy"; import { downloadFile } from "@/lib/download"; /** Illustrative instant used for the format previews: 22 Nov 2025, 18:23. */ const SAMPLE = new Date(2025, 10, 22, 18, 23); const DATE_FORMATS: Array<{ value: DateFormat; label: string }> = [ { value: "auto", label: "Automatic" }, { value: "dmy-dot", label: "Day.Month.Year" }, { value: "dmy-slash", label: "Day/Month/Year" }, { value: "mdy-slash", label: "Month/Day/Year" }, { value: "ymd-dash", label: "Year-Month-Day (ISO 8601)" }, ]; /** * What each direction means in the reader's terms. "Descending" is meaningless * for a field like Unread, where the question is which state belongs at the top. */ const DIRECTION_LABELS: Record = { unread: ["Unread first", "Read first"], starred: ["Starred first", "Unstarred first"], date: ["Newest first", "Oldest first"], sent: ["Newest first", "Oldest first"], from: ["Z to A", "A to Z"], to: ["Z to A", "A to Z"], subject: ["Z to A", "A to Z"], size: ["Largest first", "Smallest first"], }; export function GeneralSettings() { const s = useSettings((st) => st.settings); const update = useSettings((st) => st.update); const reset = useSettings((st) => st.reset); const exportJson = useSettings((st) => st.exportJson); const importJson = useSettings((st) => st.importJson); const serverLocale = getServerLocale(); const autoLocale = serverLocale ?? browserLocale(); return (

{t("General")}

{t("Reading, sending, and how dates and times are shown. What reaches a sender lives in Privacy & safety.")}

{t("Reading")}

update({ conversationMode: v })} label={t("Conversation view")} hint={t("Group messages from the same thread together.")} /> update({ showPreview: v })} label={t("Show message snippets")} hint={t("Preview the first line of each message in the list.")} /> update({ showAvatars: v })} label={t("Show sender avatars")} />
{s.listSortPreset === "custom" && (
{Array.from({ length: MAX_LEVELS }, (_, i) => { const level = s.listSortLevels[i]; return (
{level && ( )}
); })}

{t("Ordered by the server over the whole folder, not just the messages loaded so far. Ties always fall back to newest first, so the order never shuffles between two looks at the same folder.")}

)}

{t("Composing")}

update({ includeQuote: v })} label={t("Quote original message in replies")} /> update({ signatureAboveQuote: v })} label={t("Place signature above quoted text")} /> update({ spellcheck: v })} label={t("Spell check while typing")} />

{t("Locale")}

{`${serverLocale ? t("Your mail server reports {name} ({tag}).", { name: localeLabel(serverLocale), tag: serverLocale }) : t("Your mail server does not report a locale, so the browser's is used.")} ${t("Dates, times and month names follow this choice.")}`}

{t("Preview: {example}", { example: formatFullDateTime(SAMPLE) })}

{t("Default mail app")}

{t("Backup")}

); } /** * Offer ihasmail as the browser's handler for `mailto:` links. The browser * owns the decision, and nothing can read the answer back, so this states what * it can and points at the browser's own settings for the rest. */ function MailHandlerSettings() { const support = mailtoHandlerSupport(); const [requested, setRequested] = useState(mailtoHandlerRequested); const ask = () => { try { registerMailtoHandler(); setRequested(true); toast.success(t("Your browser will ask whether to open mail links in ihasmail")); } catch (err) { toast.error(t("Your browser refused the request: {error}", { error: (err as Error).message })); } }; const remove = () => { unregisterMailtoHandler(); setRequested(false); toast.show(t("Removed. Mail links will open in whatever your browser falls back to.")); }; if (support === "unsupported") { return

{tNode("This browser cannot register apps for {scheme} links. Safari, in particular, has no such API — you can still make ihasmail the default from your operating system if you install it as an app.", { scheme: mailto: })}

; } if (support === "insecure") { return

{tNode("Registering for {scheme} links requires a secure (HTTPS) connection.", { scheme: mailto: })}

; } return ( <>

{tNode("Open {scheme} links — in web pages, documents and other apps — in ihasmail instead of a desktop mail client. Your browser will ask you to confirm, and you can change it later in its own settings (Chrome: Settings › Privacy and security › Site settings › Protocol handlers; Firefox: Settings › General › Applications).", { scheme: mailto: })}

{requested && canUnregisterMailtoHandler() && }
{requested &&

{t("Requested in this browser. Whether it took effect is up to the browser — check its settings if mail links still open elsewhere.")}

} {!isInstalledApp() && (

{t("For a system-wide default, install ihasmail as an app first (in Chrome: the install icon in the address bar). Your operating system can then offer ihasmail directly wherever it asks which mail app to use.")}

)} ); }