feat: Verschluesselung waehlbar, dazu zwei Layout-Fehler

Verschluesselung: Bisher gab es nur den Schalter use_ssl – also implizites
TLS auf 993 oder gar nichts. STARTTLS auf 143 fehlte ganz, und das bieten
viele Anbieter an. Jetzt ein Feld mit drei Werten, der Port zieht bei der
Auswahl mit, und "keine" warnt, dass Zugangsdaten im Klartext gehen.

Layout: Die Postfach-Karte steht in einer schmalen Spalte; der waagerechte
Aufbau brach dort auf ein Wort je Zeile um und die Knoepfe lagen ueber dem
Titel. Jetzt gestapelt.

Die Einstellungsseite nutzte ein Raster, das die Karten zeilenweise
ausrichtet – die zweite Reihe begann erst unter der hoechsten Karte der
ersten, weshalb "Backup zurueckladen" allein weit unten stand. Jetzt
fliessen die Karten spaltenweise, und die Spalten sind breiter: vier
schmale Karten nebeneinander waren auf dem Notebook nicht lesbar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-22 20:14:37 +02:00
co-authored by Claude Opus 5
parent 408b88f893
commit 51decb7054
9 changed files with 129 additions and 43 deletions
+44 -27
View File
@@ -6,7 +6,7 @@ import { useConfirm } from "../../confirm";
import Icon from "../../components/Icon";
const LEER = {
name: "", host: "", port: 993, use_ssl: true, username: "", password: "",
name: "", host: "", port: 993, security: "ssl", username: "", password: "",
folder: "INBOX", done_folder: "", mark_seen: true, is_active: true, poll_minutes: 15,
};
@@ -116,32 +116,32 @@ export default function Postfaecher() {
<p className="muted small">{t("mailbox.intro")}</p>
{quellen.map((q) => (
<div key={q.id} className="account-row" style={{ alignItems: "flex-start" }}>
<div className="named-text grow">
<div><strong>{q.name || q.host}</strong></div>
<div className="named-sub">
{q.username} · {q.folder}
{q.last_error
? ` · ${t("mailbox.lastError")}: ${q.last_error}`
: q.last_poll_at ? ` · ${t("mailbox.lastPoll")}` : ` · ${t("mailbox.neverPolled")}`}
</div>
<div className="named-sub">
{(absender[q.id] || []).length === 0
? t("mailbox.noSenders")
: (absender[q.id] || []).map((a) => {
const konto = konten.find((k) => k.id === a.account_id);
return `${a.from_contains || "*"} → ${konto ? konto.name : t("mailbox.noAccount")}`;
}).join(" · ")}
</div>
<SenderForm konten={konten} onAdd={(d) => absenderAnlegen(q, d)} t={t} />
<div key={q.id} className="mail-source">
<div><strong>{q.name || q.host}</strong></div>
<div className="muted small">{q.username} · {q.folder} · {q.security.toUpperCase()}</div>
<div className="muted small">
{q.last_error
? `${t("mailbox.lastError")}: ${q.last_error}`
: q.last_poll_at ? t("mailbox.lastPoll") : t("mailbox.neverPolled")}
</div>
<div className="row-actions">
<div className="muted small" style={{ marginTop: "var(--sp-2)" }}>
{(absender[q.id] || []).length === 0
? t("mailbox.noSenders")
: (absender[q.id] || []).map((a) => {
const konto = konten.find((k) => k.id === a.account_id);
return `${a.from_contains || "*"} → ${konto ? konto.name : t("mailbox.noAccount")}`;
}).join(" · ")}
</div>
<SenderForm konten={konten} onAdd={(d) => absenderAnlegen(q, d)} t={t} />
<div className="btn-pair" style={{ marginTop: "var(--sp-2)" }}>
<button className="btn sm" disabled={busy} onClick={() => testen(q)}>{t("mailbox.test")}</button>
<button className="btn sm primary" disabled={busy} onClick={() => abrufen(q)}>{t("mailbox.fetchNow")}</button>
<button className="btn sm ghost" onClick={() => { setForm({ ...LEER, ...q, password: "" }); setOrdner(null); }}>
<button className="btn sm ghost" title={t("common.edit")}
onClick={() => { setForm({ ...LEER, ...q, password: "" }); setOrdner(null); }}>
<Icon name="edit" size={15} />
</button>
<button className="btn sm danger" onClick={() => loeschen(q)}><Icon name="trash" size={15} /></button>
<button className="btn sm danger" title={t("common.delete")}
onClick={() => loeschen(q)}><Icon name="trash" size={15} /></button>
</div>
</div>
))}
@@ -162,6 +162,22 @@ export default function Postfaecher() {
<input value={form.host} required onChange={(e) => setzen({ host: e.target.value })}
placeholder="imap.example.com" />
</label>
<label className="field">
<span>{t("mailbox.security")}</span>
<select value={form.security}
onChange={(e) => {
// Der uebliche Port haengt an der Verschluesselung – gleich
// mitziehen, damit die haeufigste Fehlerquelle wegfaellt.
const art = e.target.value;
const port = art === "ssl" ? 993 : 143;
setzen({ security: art, port });
}}>
<option value="ssl">{t("mailbox.secSsl")}</option>
<option value="starttls">{t("mailbox.secStarttls")}</option>
<option value="none">{t("mailbox.secNone")}</option>
</select>
</label>
{form.security === "none" && <p className="muted small">{t("mailbox.secNoneWarn")}</p>}
<label className="field">
<span>{t("mailbox.port")}</span>
<input type="number" value={form.port}
@@ -216,16 +232,17 @@ function SenderForm({ konten, onAdd, t }) {
const [von, setVon] = useState("");
const [konto, setKonto] = useState("");
return (
<div className="field-inline" style={{ marginTop: "var(--sp-2)", marginBottom: 0 }}>
<input placeholder={t("mailbox.fromPlaceholder")} value={von}
<div style={{ marginTop: "var(--sp-2)" }}>
<input style={{ width: "100%" }} placeholder={t("mailbox.fromPlaceholder")} value={von}
onChange={(e) => setVon(e.target.value)} />
<select value={konto} onChange={(e) => setKonto(e.target.value)}>
<select style={{ width: "100%", marginTop: 4 }} value={konto}
onChange={(e) => setKonto(e.target.value)}>
<option value="">{t("mailbox.noAccount")}</option>
{konten.map((k) => <option key={k.id} value={k.id}>{k.name}</option>)}
</select>
<button type="button" className="btn sm" disabled={!von.trim()}
<button type="button" className="btn sm" style={{ marginTop: 4 }} disabled={!von.trim()}
onClick={() => { onAdd({ from_contains: von.trim(), account_id: konto ? Number(konto) : null }); setVon(""); }}>
<Icon name="plus" size={14} />
<Icon name="plus" size={14} /> {t("mailbox.addSender")}
</button>
</div>
);