mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-09-06 03:20:49 +02:00
Copy directly-sent e-mails to an IMAP Sent folder, with a live folder picker
A raw SMTP send has no server-side "Sent" copy on its own (unlike IMAP drafts, which are inherently server-side) -- add an opt-in checkbox plus a configurable target folder so directly sent Mahnungen/SEPA-info-mails still show up in the account's Gesendet/Sent folder like a normal mail client would leave them. Applies only to "send" delivery; drafts already live on the server by definition. Both the Entwürfe- and Gesendet-folder fields are now editable comboboxes: a new "Ordnerliste laden" button fetches the real folder list from the IMAP server (needs working credentials first) via LIST, decoding folder names from modified UTF-7 (RFC 3501) so names like "Entwürfe" render correctly instead of as "Entw&APw-rfe". Free text still works -- ensure_imap_folder() creates the folder on first use if it doesn't exist yet, checked once per batch rather than before every single message. mail_delivery.append_to_imap_drafts() became the more general append_message(client, content, folder=, flags=), reused for both the \Draft and \Seen cases. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
6b0da82b45
commit
cc4aaef895
@@ -12,7 +12,7 @@ from ccma.services.intervals import (
|
||||
IntervalValidationError,
|
||||
normalize_anniversary_intervals,
|
||||
)
|
||||
from ccma.services.mail_delivery import test_imap_connection, test_smtp_connection
|
||||
from ccma.services.mail_delivery import list_imap_folders, test_imap_connection, test_smtp_connection
|
||||
from ccma.storage.repository import MemberRepository, RepositoryError, validate_member_number_pattern
|
||||
from ccma.ui.changelog_view import ChangelogView
|
||||
from ccma.ui.icons import IconStore
|
||||
@@ -649,7 +649,7 @@ class OptionsDialog(tk.Toplevel):
|
||||
row=4, column=0, columnspan=4, sticky="w", pady=(8, 0)
|
||||
)
|
||||
|
||||
imap_frame = ttk.LabelFrame(parent, text="IMAP (für die Ablage als Entwurf)", padding=10)
|
||||
imap_frame = ttk.LabelFrame(parent, text="IMAP (für Entwürfe & Gesendet)", padding=10)
|
||||
imap_frame.grid(row=3, column=0, columnspan=2, sticky="ew", pady=(16, 0))
|
||||
imap_frame.columnconfigure(1, weight=1)
|
||||
self.imap_host_var = tk.StringVar(value=email_settings["imap_host"])
|
||||
@@ -660,6 +660,8 @@ class OptionsDialog(tk.Toplevel):
|
||||
self.imap_username_var = tk.StringVar(value=email_settings["imap_username"])
|
||||
self.imap_password_var = tk.StringVar(value=email_settings["imap_password"])
|
||||
self.imap_drafts_folder_var = tk.StringVar(value=email_settings["imap_drafts_folder"])
|
||||
self.imap_sent_enabled_var = tk.BooleanVar(value=email_settings["imap_sent_enabled"])
|
||||
self.imap_sent_folder_var = tk.StringVar(value=email_settings["imap_sent_folder"])
|
||||
ttk.Label(imap_frame, text="Server").grid(row=0, column=0, sticky="w", pady=4, padx=(0, 8))
|
||||
ttk.Entry(imap_frame, textvariable=self.imap_host_var).grid(
|
||||
row=0, column=1, sticky="ew", pady=4, padx=(0, 8)
|
||||
@@ -685,12 +687,33 @@ class OptionsDialog(tk.Toplevel):
|
||||
row=3, column=1, columnspan=3, sticky="ew", pady=4
|
||||
)
|
||||
ttk.Label(imap_frame, text="Entwürfe-Ordner").grid(row=4, column=0, sticky="w", pady=4, padx=(0, 8))
|
||||
ttk.Entry(imap_frame, textvariable=self.imap_drafts_folder_var).grid(
|
||||
row=4, column=1, columnspan=3, sticky="ew", pady=4
|
||||
)
|
||||
ttk.Button(imap_frame, text="Verbindung testen", command=self._test_imap_connection).grid(
|
||||
row=5, column=0, columnspan=4, sticky="w", pady=(8, 0)
|
||||
self.imap_drafts_folder_combo = ttk.Combobox(imap_frame, textvariable=self.imap_drafts_folder_var)
|
||||
self.imap_drafts_folder_combo.grid(row=4, column=1, columnspan=3, sticky="ew", pady=4)
|
||||
ttk.Checkbutton(
|
||||
imap_frame,
|
||||
text="Direkt versandte Mahnungen/Info-Mails zusätzlich in einen Gesendet-Ordner ablegen",
|
||||
variable=self.imap_sent_enabled_var,
|
||||
).grid(row=5, column=0, columnspan=4, sticky="w", pady=(8, 4))
|
||||
ttk.Label(imap_frame, text="Gesendet-Ordner").grid(row=6, column=0, sticky="w", pady=4, padx=(0, 8))
|
||||
self.imap_sent_folder_combo = ttk.Combobox(imap_frame, textvariable=self.imap_sent_folder_var)
|
||||
self.imap_sent_folder_combo.grid(row=6, column=1, columnspan=3, sticky="ew", pady=4)
|
||||
buttons_row = ttk.Frame(imap_frame)
|
||||
buttons_row.grid(row=7, column=0, columnspan=4, sticky="w", pady=(8, 0))
|
||||
ttk.Button(buttons_row, text="Verbindung testen", command=self._test_imap_connection).pack(
|
||||
side="left", padx=(0, 8)
|
||||
)
|
||||
ttk.Button(buttons_row, text="Ordnerliste laden", command=self._load_imap_folders).pack(side="left")
|
||||
ttk.Label(
|
||||
imap_frame,
|
||||
text=(
|
||||
"„Ordnerliste laden“ ruft die Ordner live vom Server ab (Zugangsdaten oben müssen "
|
||||
"dafür bereits ausgefüllt sein) und füllt beide Dropdowns; freie Eingabe bleibt "
|
||||
"möglich -- ein dabei angegebener, noch nicht vorhandener Ordner wird bei der "
|
||||
"nächsten Ablage automatisch angelegt."
|
||||
),
|
||||
style="Muted.TLabel",
|
||||
wraplength=660,
|
||||
).grid(row=8, column=0, columnspan=4, sticky="w", pady=(6, 0))
|
||||
|
||||
ttk.Label(
|
||||
parent,
|
||||
@@ -717,6 +740,8 @@ class OptionsDialog(tk.Toplevel):
|
||||
"imap_username": self.imap_username_var.get(),
|
||||
"imap_password": self.imap_password_var.get(),
|
||||
"imap_drafts_folder": self.imap_drafts_folder_var.get(),
|
||||
"imap_sent_enabled": self.imap_sent_enabled_var.get(),
|
||||
"imap_sent_folder": self.imap_sent_folder_var.get(),
|
||||
}
|
||||
|
||||
def _test_smtp_connection(self) -> None:
|
||||
@@ -747,6 +772,22 @@ class OptionsDialog(tk.Toplevel):
|
||||
return
|
||||
messagebox.showinfo("IMAP-Verbindung", "Verbindung und Anmeldung erfolgreich.", parent=self)
|
||||
|
||||
def _load_imap_folders(self) -> None:
|
||||
settings = self._collect_email_settings()
|
||||
try:
|
||||
settings["imap_port"] = int(settings["imap_port"])
|
||||
except ValueError:
|
||||
messagebox.showerror("Ungültiger Port", "Der IMAP-Port muss eine ganze Zahl sein.", parent=self)
|
||||
return
|
||||
try:
|
||||
folders = list_imap_folders(settings)
|
||||
except RepositoryError as exc:
|
||||
messagebox.showerror("Ordnerliste konnte nicht geladen werden", str(exc), parent=self)
|
||||
return
|
||||
self.imap_drafts_folder_combo.configure(values=folders)
|
||||
self.imap_sent_folder_combo.configure(values=folders)
|
||||
messagebox.showinfo("Ordnerliste geladen", f"{len(folders)} Ordner gefunden.", parent=self)
|
||||
|
||||
def _choose_store(self) -> None:
|
||||
current = Path(self.store_var.get()).expanduser() if self.store_var.get().strip() else Path.home()
|
||||
initial = current if current.is_dir() else current.parent
|
||||
|
||||
Reference in New Issue
Block a user