(bugfix): open the bundle save dialog and keep the desktop shortcut on upgrade

This commit is contained in:
Maksym Sadovnychyy 2026-09-27 13:36:28 +02:00
parent fb3289a4c3
commit c84d8d37b7
19 changed files with 298 additions and 89 deletions

View File

@ -6,6 +6,20 @@ The format follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic
## [Unreleased] ## [Unreleased]
## [0.4.1] - 2026-09-27
Create bundle asks for the passphrase once, then opens Save. About lists the company contacts. A Windows upgrade keeps the desktop shortcut that is already there.
### Changed
- **Help → About** lists Info, Privacy, Security, and Support (`info@`, `privacy@`, `security@`, `support@maks-it.com`) instead of one personal address.
- The English, Italian, French, German, and Spanish READMEs describe folder stores, Easy Migration, background sync, spam marks, and the current menus.
### Fixed
- **File → Easy Migration → Create bundle…** asks for the passphrase and the repeat in one window. The save dialog opens after that window closes.
- Windows setup keeps an existing desktop shortcut when the checkbox is off on upgrade. Installing the shortcut removes a second desktop icon, including the "(2)" copy.
## [0.4.0] - 2026-09-27 ## [0.4.0] - 2026-09-27
Background sync runs at boot with nobody signed in. Easy Migration packs this PC into a passphrase-locked bundle. Marked spam is remembered across mailboxes, and Gmail folder reads stay on the open mailbox. Background sync runs at boot with nobody signed in. Easy Migration packs this PC into a passphrase-locked bundle. Marked spam is remembered across mailboxes, and Gmail folder reads stay on the open mailbox.

View File

@ -37,8 +37,11 @@ Desktop- und Webmail behandeln einen PEC-**Umschlag** oder einen ETSI-REM-Nachwe
- **Mandatskennzeichen auf diesem PC** — WEG, Mandantenakte, Steuerbescheid. Kennzeichen sind lokal, keine Kanzlei-Datenbank im Netz. - **Mandatskennzeichen auf diesem PC** — WEG, Mandantenakte, Steuerbescheid. Kennzeichen sind lokal, keine Kanzlei-Datenbank im Netz.
- **Lokales Archiv des ganzen Ordners** — Nachrichten abrufen speichert den **gesamten IMAP-Ordner** in `mail.db` plus `.eml` auf der Platte (kein Last-N-Fenster). FTS über Betreff, Text, PDF-/XML-/HTML-/CSV-/JSON-Anlagen und Kennzeichen. - **Lokales Archiv des ganzen Ordners** — Nachrichten abrufen speichert den **gesamten IMAP-Ordner** in `mail.db` plus `.eml` auf der Platte (kein Last-N-Fenster). FTS über Betreff, Text, PDF-/XML-/HTML-/CSV-/JSON-Anlagen und Kennzeichen.
- **Bedeutungssuche auf dem Gerät (RAG)** — optionales EmbeddingGemma 300M unter **Einstellungen → Indizes…**. Vektoren bleiben in `mail.db`. Mail verlässt den Rechner nicht. Stichwort- und Bedeutungsindex neu aufbauen oder reparieren. - **Bedeutungssuche auf dem Gerät (RAG)** — optionales EmbeddingGemma 300M unter **Einstellungen → Indizes…**. Vektoren bleiben in `mail.db`. Mail verlässt den Rechner nicht. Stichwort- und Bedeutungsindex neu aufbauen oder reparieren.
- **Regeln über Postfächer hinweg** — verschieben, löschen, kennzeichnen, etikettieren; Zielordner kann auf einem anderen Konto liegen, auch auf einer angehängten `.pst`-/`.ost`-Datei. Import von JSON oder einer älteren `.rwz`-Datei; Export ist JSON. - **Regeln über Postfächer hinweg** — verschieben, löschen, kennzeichnen, etikettieren; der Zielordner kann auf einem anderen Konto liegen, auch auf einem **Ordnerspeicher**. Import von JSON oder einer älteren `.rwz`-Datei; Export ist JSON.
- **`.pst` / `.ost` als Postfach öffnen** — Unicode-Persönlicher Store ist schreibbar (Ordner, Flags, Verschieben, Löschen). Ein Offline-Store wird beim ersten Schreiben in ein Unicode-`.pst` kopiert. Zum Lesen der Datei ist kein weiteres Mailprogramm nötig. - **Ordnerspeicher** — Verzeichnis mit `.eml` und eigener `mail.db` anlegen, anhängen, verschieben oder lösen. **PST/OST importieren…** kopiert die Post in einen Speicher; die `.pst` ist kein Postfach. Standardpfad: `%LocalAppData%\Postclient\stores\{Name}` (portabel: `{Installation}/data/stores/{Name}`).
- **Easy Migration** — **Datei → Easy Migration → Bundle erstellen…** fragt die Passphrase zweimal im selben Fenster, dann Speichern. Das `.postbundle` enthält Post, Konten, Geheimnisse und den gemeinsamen Modellordner. **Bundle öffnen…** stellt es in den Postclient-Ordnern dieses PCs wieder her. Die Hintergrundsync bleibt aus.
- **Hintergrundsync** — **Einstellungen → Hintergrundsync für dieses Konto…**: ein Dienst beim Start (Windows-Dienst, systemd-Systemunit unter Linux). Er synchronisiert jedes gespeicherte Postfach, ohne dass jemand angemeldet ist.
- **Spam** — **Als Spam markieren** merkt sich diese Nachricht auf diesem PC und lehrt jedes Postfach, auch nachdem Nachricht oder Postfach weg sind. Ähnliche Post ist nur ein Hinweis. **Kein Spam** löscht diese Erinnerung und legt die Nachricht zurück in den Posteingang. Verschieben nach Junk lehrt den Filter nicht.
- **Länderpakete** — **Einstellungen → Funktionen** schaltet Italien (PEC, FatturaPA, Fascicolo), Europa (eIDAS-REM), Frankreich, Deutschland, Spanien und die Schweiz ein oder aus. Gewöhnliches IMAP bleibt verfügbar. - **Länderpakete** — **Einstellungen → Funktionen** schaltet Italien (PEC, FatturaPA, Fascicolo), Europa (eIDAS-REM), Frankreich, Deutschland, Spanien und die Schweiz ein oder aus. Gewöhnliches IMAP bleibt verfügbar.
## Zielgruppen ## Zielgruppen
@ -57,7 +60,7 @@ Das Programm ersetzt den Gestore nicht, stellt keinen qualifizierten Nachweis au
## Konten und Protokolle ## Konten und Protokolle
Eingang: **IMAP** (Ordnerbaum), **POP3** (nur Posteingang) oder eine angehängte **`.pst` / `.ost`**. Unicode-`.pst` ist schreibbar. Ausgang: **SMTP**. Verschlüsselung: Auto, SSL/TLS, STARTTLS, STARTTLS falls verfügbar, oder keine. MailKit nutzt die SASL-Verfahren, die der Server anbietet. Eingang: **IMAP** (Ordnerbaum), **POP3** (nur Posteingang) oder ein lokaler **Ordnerspeicher**. Eine Unicode-`.pst` lässt sich weiterhin in einen Speicher **importieren**. Ausgang: **SMTP**. Verschlüsselung: Auto, SSL/TLS, STARTTLS, STARTTLS falls verfügbar, oder keine. MailKit nutzt die SASL-Verfahren, die der Server anbietet.
Mehrere Postfächer sind speicherbar. Profile füllen Hosts und Ports: Mehrere Postfächer sind speicherbar. Profile füllen Hosts und Ports:
@ -116,10 +119,10 @@ Systemhinweise erscheinen bei neuer PEC, neuen Ricevute und neuer REM (Windows-T
## Lesen, Ordner, Verfassen ## Lesen, Ordner, Verfassen
- Der Baum folgt dem Server (Posteingang, Ricevute, Entwürfe, Gesendet, Archiv, Junk, Papierkorb, plus eigene Ordner). Verschachtelte IMAP-Pfade erscheinen als Unterordner, auch Gmail-Labels `[Gmail]/…` unter `[Gmail]`. Entwürfe, Gesendet und Papierkorb als `INBOX.Drafts` / `INBOX.Sent` / `INBOX.Trash` stehen neben dem Posteingang, nicht darunter. Gmail trennt mit `/`, daher bleibt ein Label wie `P.IVA` ein Ordner. Systemordner lassen sich nicht löschen. Ordner anlegen, leeren (in den Papierkorb bzw. endgültig im Papierkorb), alles gelesen/ungelesen, eigenen Ordner löschen, Nachrichten auf einen Ordner ziehen. Welche Konten und Unterordner aufgeklappt oder zugeklappt sind, bleibt in `settings.json`. - Der Baum folgt dem Server (Posteingang, Ricevute, Entwürfe, Gesendet, Archiv, Junk, Papierkorb, plus eigene Ordner). Verschachtelte IMAP-Pfade erscheinen als Unterordner, auch Gmail-Labels `[Gmail]/…` unter `[Gmail]`. Entwürfe, Gesendet und Papierkorb als `INBOX.Drafts` / `INBOX.Sent` / `INBOX.Trash` stehen neben dem Posteingang, nicht darunter. Gmail trennt mit `/`, daher bleibt ein Label wie `P.IVA` ein Ordner. Systemordner lassen sich nicht löschen. Ordner anlegen, leeren (in den Papierkorb bzw. endgültig im Papierkorb), alles gelesen/ungelesen, eigenen Ordner löschen, Nachrichten auf einen Ordner ziehen. Welche Konten und Unterordner aufgeklappt oder zugeklappt sind, bleibt in `settings.json`.
- Spalten: ungelesen, Kennzeichnung, Anlagen, Zustellung, Typ (PEC / RIC / REM / SIG), von, Betreff, Datum (lokal `yyyy-MM-dd HH:mm`), Mandatskennzeichen. - Spalten: ungelesen, Kennzeichnung, Anlagen, Zustellung, Typ (PEC / RIC / REM / SIG), von, Betreff, Datum (lokal `yyyy-MM-dd HH:mm`), Größe, Mandatskennzeichen.
- Ansicht: HTML (Web-Engine des Systems), Text, Quelltext oder **FatturaPA**. Layout: Liste über dem Lesen, oder drei Spalten (Ordner, Liste, Lesen). - Ansicht: HTML (Web-Engine des Systems), Text, Quelltext oder **FatturaPA**. Layout: Liste über dem Lesen, oder drei Spalten (Ordner, Liste, Lesen).
- **Unterhaltungen gruppieren** rückt Antworten in der Liste ein (Message-ID / In-Reply-To / References, Tiefe bis 8). PEC-Nachweise zum selben Original stehen beieinander. Es gibt kein eigenes Unterhaltungsfenster. - **Unterhaltungen gruppieren** rückt Antworten in der Liste ein (Message-ID / In-Reply-To / References, Tiefe bis 8). PEC-Nachweise zum selben Original stehen beieinander. Es gibt kein eigenes Unterhaltungsfenster.
- Aktionen: antworten, allen antworten, weiterleiten, in neuem Fenster, gelesen/ungelesen, Kennzeichnung, Priorität, löschen. - Aktionen: antworten, allen antworten, weiterleiten, in neuem Fenster, gelesen/ungelesen, Kennzeichnung, Priorität, als Spam markieren, kein Spam, löschen. **Umschalt+Klick** und **Strg+Klick** wählen mehrere Nachrichten oder Ordner. Löschen (Leiste, **Entf**, Ordner leeren) verschiebt die Auswahl in den Papierkorb. Aus dem Papierkorb ist Löschen endgültig.
- Das Verfassen ist **Klartext**. An, Cc, Bcc sind Adress-Chips. **Anlagen als ZIP senden** packt per Drag-and-Drop übergebene Dateien (optionales Passwort). Der Versand nutzt nur die SMTP-Identität des gewählten Postfachs. - Das Verfassen ist **Klartext**. An, Cc, Bcc sind Adress-Chips. **Anlagen als ZIP senden** packt per Drag-and-Drop übergebene Dateien (optionales Passwort). Der Versand nutzt nur die SMTP-Identität des gewählten Postfachs.
HTML: **WebView2** unter Windows, **WebKitGTK** unter Linux, **WKWebView** unter macOS. Fehlt die Engine, erscheint der Textkörper. HTML: **WebView2** unter Windows, **WebKitGTK** unter Linux, **WKWebView** unter macOS. Fehlt die Engine, erscheint der Textkörper.
@ -136,9 +139,9 @@ Fedora: `gtk3 webkit2gtk4.1 libsoup3`.
„Nachrichten abrufen“ indiziert den **gesamten** IMAP-Ordner in SQLite (`mail.db`) plus `.eml` auf der Platte. POP3 füllt nur den Posteingang. Die Indizierung läuft im Hintergrund; die Statusleiste zeigt den Fortschritt. „Nachrichten abrufen“ indiziert den **gesamten** IMAP-Ordner in SQLite (`mail.db`) plus `.eml` auf der Platte. POP3 füllt nur den Posteingang. Die Indizierung läuft im Hintergrund; die Statusleiste zeigt den Fortschritt.
- **Einstellungen → Indizes…**: Stichwortindex (FTS) und optional EmbeddingGemma 300M (~300 MB) für Bedeutung. Das ONNX-Paket kommt von Hugging Face (`onnx-community/embeddinggemma-300m-ONNX`). Vektoren bleiben in `mail.db` auf diesem PC. Gerät: Auto / CPU / GPU (Windows DirectML). Index neu aufbauen oder reparieren, wenn die Suche falsch wirkt. Die Gewichte unterliegen Googles Gemma-Bedingungen. - **Einstellungen → Indizes…**: Stichwortindex (FTS) und optional EmbeddingGemma 300M (~300 MB) für Bedeutung. Das ONNX-Paket wird einmal in den Maschinenordner geladen (`ProgramData\MaksIT\Postclient\models`, unter Linux `/var/lib/maksit/postclient/models`) und von jedem Konto und dem Sync-Dienst geteilt. Vektoren bleiben in `mail.db` auf diesem PC. Gerät: Auto / CPU / GPU (Windows DirectML). Index neu aufbauen oder reparieren, wenn die Suche falsch wirkt. Die Gewichte unterliegen Googles Gemma-Bedingungen. **Spam-Markierungen…** listet, was der Filter gelernt hat.
Die Suche (aktueller Ordner) umfasst Betreff, Text, Anlagentext (PDF sowie text/XML/HTML/CSV/JSON), Mandatskennzeichen und Bedeutung, sobald das Modell bereit ist. Kennzeichen existieren nur auf diesem PC. Die Suche (aktueller Ordner) umfasst Betreff, Text, Anlagentext (PDF sowie text/XML/HTML/CSV/JSON), Mandatskennzeichen und Bedeutung, sobald das Modell bereit ist. Das Suchfeld hat ein ×, das den Text löscht. Kennzeichen existieren nur auf diesem PC.
Das Archiv liegt **nur auf diesem PC**. Datei → Archivordner öffnen / Archiv exportieren kopiert `mail.db` und die `.eml`. Pfade: Das Archiv liegt **nur auf diesem PC**. Datei → Archivordner öffnen / Archiv exportieren kopiert `mail.db` und die `.eml`. Pfade:
@ -158,10 +161,17 @@ Das IMAP-**QUOTA** des Gestore (wenn der Server es anbietet) steht in der Status
|--|--| |--|--|
| EML importieren | Originale in den aktuellen Ordner | | EML importieren | Originale in den aktuellen Ordner |
| mbox importieren | Lokale mbox-Stores aus einem Desktop-Mailprofil | | mbox importieren | Lokale mbox-Stores aus einem Desktop-Mailprofil |
| `.pst` / `.ost` importieren | Kopiert Mail aus der Datendatei in das **gewählte** IMAP-/POP3-Konto. Verschachtelte Stores in der Datei werden mitimportiert. Schließen Sie jedes Programm, das die Datei geöffnet hält. | | `.pst` / `.ost` importieren | Kopiert Outlook-Post **in einen Ordnerspeicher** (vorgeschlagener Pfad unter `stores/` in den App-Daten). Ein erneuter Import in denselben Speicher überspringt Dubletten nach Message-ID. Schließen Sie jedes Programm, das die Datei geöffnet hält. |
| Datendatei anhängen | Öffnet eine `.pst` / `.ost` als eigenes Postfach. Unicode-`.pst` ist schreibbar. `.ost` wird nicht am Ort beschrieben — die erste Änderung kopiert sie in ein Unicode-`.pst` neben dem Original. Dateimenü oder Kontotyp *Datendatei*. | | Neuer Speicher | Dateimenü: leerer Ordner (`Posteingang`, `Entwürfe`, `Gesendet`, `Gelöscht`) mit `postclient.store.json` und `mail.db`. |
| Neue Datendatei | Dateimenü: leeres Unicode-`.pst` (Posteingang, Entwürfe, Gesendet, Gelöscht) und als Postfach anhängen. | | Speicher anhängen | Dateimenü: Ordner wählen, der schon `postclient.store.json` hat. |
| Regeln | **Einstellungen → Regeln**: **Regeln importieren…** liest den JSON-Export dieser App oder eine ältere `.rwz`-Datei. **Regeln exportieren…** schreibt JSON. Jede Regel gehört zu einem **Konto**; ein Zielordner kann auf einem anderen Postfach liegen (auch einer angehängten Datendatei). Sie laufen beim Abrufen, beim Import und unter **Alle Regeln ausführen**. | | Verschieben / lösen | Kopiert das ganze Verzeichnis oder nimmt es aus der Liste. Der Ordner bleibt auf der Platte, wenn er gelöst wird. |
| Aufbewahrung | **Einstellungen → Aufbewahrung…**: Tage je Ordner (`0` = für immer). Abgelaufene Ordnerpost geht nach dem Abruf in den Papierkorb. Beim Papierkorb behält `0` ihn für immer, eine Tageszahl löscht alten Papierkorb endgültig. |
| Hintergrundsync | **Einstellungen → Hintergrundsync für dieses Konto…**: Dienst beim Start (Windows-Dienst, systemd-Systemunit unter Linux). Synchronisiert jedes gespeicherte Postfach ohne Anmeldung. **Dieses Konto teilen** lässt nur andere Personen auf diesem PC das Postfach öffnen. |
| Geteiltes Konto | **Kontoeinstellungen → Dieses Konto mit anderen Benutzern dieses PCs teilen**: das Postfach zieht in den Maschinenordner (`ProgramData\MaksIT\Postclient`, oder `Public`, wenn ProgramData nicht beschreibbar ist; `/var/lib/maksit/postclient` unter Linux; `/Users/Shared/MaksIT/Postclient` unter macOS). Andere Benutzer sehen es, wenn sie Postclient öffnen. Kennwörter werden nicht kopiert. |
| Easy Migration | **Datei → Easy Migration → Bundle erstellen…** fragt die Passphrase zweimal im selben Fenster, dann Speichern. Das `.postbundle` enthält Post, Konten, Geheimnisse und den gemeinsamen Modellordner. **Bundle öffnen…** stellt es auf diesem PC wieder her und schreibt Speicherpfade um. Die Hintergrundsync bleibt aus. |
| Spam-Markierungen | **Als Spam markieren** merkt sich diese Nachricht auf diesem PC und lehrt jedes Postfach, auch nachdem Nachricht oder Postfach weg sind. Ähnliche Post ist nur ein Hinweis. **Kein Spam** löscht diese Erinnerung und legt die Nachricht zurück in den Posteingang. Verschieben nach Junk lehrt den Filter nicht. **Einstellungen → Indizes… → Spam-Markierungen…** listet, was gelernt wurde. |
| Updates und Protokoll | **Hilfe → Nach Updates suchen…** fragt GitHub nach der neuesten Version und kann das Paket für dieses System laden. **Hilfe → Protokoll** öffnet ein kopierbares Fenster über den Protokollordner. **Hilfe → Über Postclient** listet Info, Privacy, Security und Support auf maks-it.com. |
| Regeln | **Einstellungen → Regeln**: **Regeln importieren…** liest den JSON-Export dieser App oder eine ältere `.rwz`-Datei. **Regeln exportieren…** schreibt JSON. Jede Regel gehört zu einem **Konto**; ein Zielordner kann auf einem anderen Postfach liegen (auch einem Speicher). Sie laufen beim Abrufen, beim Import und unter **Alle Regeln ausführen**. |
| Drucken | Lesbares HTML der (ggf. ausgepackten) Nachricht | | Drucken | Lesbares HTML der (ggf. ausgepackten) Nachricht |
| PDF speichern | Derselbe Inhalt als PDF | | PDF speichern | Derselbe Inhalt als PDF |
| Anlagen-ZIP | Alle Anlagen der geöffneten Nachricht | | Anlagen-ZIP | Alle Anlagen der geöffneten Nachricht |

View File

@ -37,8 +37,11 @@ El correo de escritorio y el webmail tratan de normalmente un **sobre** PEC o un
- **Etiquetas de expediente en este PC** — comunidad, expediente de cliente, aviso fiscal. Las etiquetas son locales, no una base de despacho en red. - **Etiquetas de expediente en este PC** — comunidad, expediente de cliente, aviso fiscal. Las etiquetas son locales, no una base de despacho en red.
- **Archivo local de toda la carpeta** — Obtener mensajes guarda **toda la carpeta IMAP** en `mail.db` más `.eml` en disco (no una ventana de los últimos N). FTS de asunto, cuerpo, texto de adjuntos PDF/XML/HTML/CSV/JSON y etiquetas. - **Archivo local de toda la carpeta** — Obtener mensajes guarda **toda la carpeta IMAP** en `mail.db` más `.eml` en disco (no una ventana de los últimos N). FTS de asunto, cuerpo, texto de adjuntos PDF/XML/HTML/CSV/JSON y etiquetas.
- **Búsqueda por significado en el dispositivo (RAG)** — EmbeddingGemma 300M opcional en **Ajustes → Índices…**. Los vectores quedan en `mail.db`. El correo no sale de la máquina. Reconstruir o reparar índices de palabras y de significado. - **Búsqueda por significado en el dispositivo (RAG)** — EmbeddingGemma 300M opcional en **Ajustes → Índices…**. Los vectores quedan en `mail.db`. El correo no sale de la máquina. Reconstruir o reparar índices de palabras y de significado.
- **Reglas entre buzones** — mover, eliminar, marcar, etiquetar; la carpeta de destino puede estar en otra cuenta, incluido un archivo de datos `.pst` / `.ost` adjunto. Importar JSON o un archivo `.rwz` heredado; la exportación es JSON. - **Reglas entre buzones** — mover, eliminar, marcar, etiquetar; la carpeta de destino puede estar en otra cuenta, incluido un **almacén de carpetas**. Importar JSON o un archivo `.rwz` heredado; la exportación es JSON.
- **Abrir `.pst` / `.ost` como buzón** — el almacén Unicode es escribible (carpetas, marcas, mover, eliminar). Un almacén sin conexión se copia a un `.pst` Unicode en la primera escritura. No hace falta otro programa de correo para leer el archivo. - **Almacenes de carpetas** — crear, adjuntar, mover o desconectar una carpeta con `.eml` y su propio `mail.db`. **Importar PST/OST…** copia el correo a un almacén; el `.pst` no es un buzón. Ruta predeterminada: `%LocalAppData%\Postclient\stores\{nombre}` (portátil: `{instalación}/data/stores/{nombre}`).
- **Easy Migration** — **Archivo → Easy Migration → Crear bundle…** pide la contraseña dos veces en la misma ventana y luego Guardar. El `.postbundle` contiene el correo, las cuentas, los secretos y la carpeta de modelo compartida. **Abrir bundle…** lo restaura en las carpetas de Postclient de este PC. La sincronización en segundo plano sigue apagada.
- **Sincronización en segundo plano** — **Ajustes → Sincronización en segundo plano para esta cuenta…**: un servicio al arrancar (servicio de Windows, unidad systemd de sistema en Linux). Sincroniza cada buzón guardado sin que nadie haya iniciado sesión.
- **Spam** — **Marcar como spam** recuerda ese mensaje en este PC y lo enseña a cada buzón, aunque el mensaje o el buzón ya no estén. El correo parecido es solo una pista. **No es spam** borra ese recuerdo y devuelve el mensaje a la bandeja de entrada. Mover a No deseado no enseña el filtro.
- **Paquetes por país** — **Ajustes → Funciones** activa o desactiva Italia (PEC, FatturaPA, fascículo), Europa (REM eIDAS), Francia, Alemania, España y Suiza. El IMAP ordinario sigue disponible. - **Paquetes por país** — **Ajustes → Funciones** activa o desactiva Italia (PEC, FatturaPA, fascículo), Europa (REM eIDAS), Francia, Alemania, España y Suiza. El IMAP ordinario sigue disponible.
## A quién va dirigido ## A quién va dirigido
@ -57,7 +60,7 @@ El programa no sustituye al gestor, no emite un acuse cualificado y no comparte
## Cuentas y protocolos ## Cuentas y protocolos
Entrada: **IMAP** (árbol de carpetas), **POP3** (solo bandeja de entrada) o un **`.pst` / `.ost`** adjunto. El `.pst` Unicode es escribible. Salida: **SMTP**. Cifrado: automático, SSL/TLS, STARTTLS, STARTTLS si está disponible, o ninguno. MailKit usa los métodos SASL que anuncia el servidor. Entrada: **IMAP** (árbol de carpetas), **POP3** (solo bandeja de entrada) o un **almacén de carpetas** local. Un `.pst` Unicode todavía se puede **importar** a un almacén. Salida: **SMTP**. Cifrado: automático, SSL/TLS, STARTTLS, STARTTLS si está disponible, o ninguno. MailKit usa los métodos SASL que anuncia el servidor.
Se pueden guardar varios buzones. Los perfiles rellenan hosts y puertos: Se pueden guardar varios buzones. Los perfiles rellenan hosts y puertos:
@ -116,10 +119,10 @@ Las notificaciones del sistema avisan de PEC, acuses y REM nuevos (toast de Wind
## Lectura, carpetas, redacción ## Lectura, carpetas, redacción
- El árbol sigue al servidor (Bandeja de entrada, Ricevute, Borradores, Enviados, Archivo, No deseado, Papelera, más carpetas propias). Las rutas IMAP anidadas se muestran como carpetas anidadas, incluidas las etiquetas de Gmail `[Gmail]/…` bajo `[Gmail]`. Borradores, Enviados y Papelera que el servidor guarda como `INBOX.Drafts` / `INBOX.Sent` / `INBOX.Trash` quedan junto a la bandeja, no debajo. Gmail usa `/` como separador, así que una etiqueta como `P.IVA` sigue siendo una sola carpeta. Las de sistema no se pueden borrar. Se puede crear una carpeta, vaciarla (a Papelera, o de forma definitiva en Papelera), marcar todo leído/no leído, eliminar una carpeta propia y arrastrar mensajes a una carpeta. Qué cuentas y carpetas anidadas están abiertas o cerradas se guarda en `settings.json`. - El árbol sigue al servidor (Bandeja de entrada, Ricevute, Borradores, Enviados, Archivo, No deseado, Papelera, más carpetas propias). Las rutas IMAP anidadas se muestran como carpetas anidadas, incluidas las etiquetas de Gmail `[Gmail]/…` bajo `[Gmail]`. Borradores, Enviados y Papelera que el servidor guarda como `INBOX.Drafts` / `INBOX.Sent` / `INBOX.Trash` quedan junto a la bandeja, no debajo. Gmail usa `/` como separador, así que una etiqueta como `P.IVA` sigue siendo una sola carpeta. Las de sistema no se pueden borrar. Se puede crear una carpeta, vaciarla (a Papelera, o de forma definitiva en Papelera), marcar todo leído/no leído, eliminar una carpeta propia y arrastrar mensajes a una carpeta. Qué cuentas y carpetas anidadas están abiertas o cerradas se guarda en `settings.json`.
- Columnas: no leído, marca, adjuntos, entrega, tipo (PEC / RIC / REM / SIG), de, asunto, fecha (local `yyyy-MM-dd HH:mm`), etiqueta de expediente. - Columnas: no leído, marca, adjuntos, entrega, tipo (PEC / RIC / REM / SIG), de, asunto, fecha (local `yyyy-MM-dd HH:mm`), tamaño, etiqueta de expediente.
- Vista: HTML (motor web del sistema), texto, fuente o **FatturaPA**. Disposición: lista encima de la lectura, o tres columnas (carpetas, lista, lectura). - Vista: HTML (motor web del sistema), texto, fuente o **FatturaPA**. Disposición: lista encima de la lectura, o tres columnas (carpetas, lista, lectura).
- **Agrupar conversaciones** sangra las respuestas en la lista (Message-ID / In-Reply-To / References, profundidad máxima 8). Los acuses PEC del mismo original quedan juntos. No hay un panel de conversación aparte. - **Agrupar conversaciones** sangra las respuestas en la lista (Message-ID / In-Reply-To / References, profundidad máxima 8). Los acuses PEC del mismo original quedan juntos. No hay un panel de conversación aparte.
- Acciones: responder, responder a todos, reenviar, abrir en ventana nueva, leído/no leído, marca, prioridad, eliminar. - Acciones: responder, responder a todos, reenviar, abrir en ventana nueva, leído/no leído, marca, prioridad, marcar como spam, no es spam, eliminar. **Mayús+clic** y **Ctrl+clic** seleccionan varios mensajes o carpetas. Eliminar (barra, **Supr**, vaciar carpeta) mueve la selección a Papelera. Desde Papelera, eliminar es definitivo.
- La redacción es **texto sin formato**. Para, Cc, Cco son chips de dirección. **Enviar adjuntos como ZIP** empaqueta los archivos soltados (contraseña opcional). El envío usa solo la identidad SMTP del buzón elegido. - La redacción es **texto sin formato**. Para, Cc, Cco son chips de dirección. **Enviar adjuntos como ZIP** empaqueta los archivos soltados (contraseña opcional). El envío usa solo la identidad SMTP del buzón elegido.
HTML: **WebView2** en Windows, **WebKitGTK** en Linux, **WKWebView** en macOS. Si falta el motor, se muestra el cuerpo de texto. HTML: **WebView2** en Windows, **WebKitGTK** en Linux, **WKWebView** en macOS. Si falta el motor, se muestra el cuerpo de texto.
@ -136,9 +139,9 @@ Fedora: `gtk3 webkit2gtk4.1 libsoup3`.
«Obtener mensajes» indexa **toda** la carpeta IMAP en SQLite (`mail.db`) más los `.eml` en disco. POP3 solo llena la bandeja de entrada. La indexación corre en segundo plano; la barra de estado muestra el avance. «Obtener mensajes» indexa **toda** la carpeta IMAP en SQLite (`mail.db`) más los `.eml` en disco. POP3 solo llena la bandeja de entrada. La indexación corre en segundo plano; la barra de estado muestra el avance.
- **Ajustes → Índices…**: índice de palabras (FTS) y, si se desea, EmbeddingGemma 300M (~300 MB) para el significado. El paquete ONNX se descarga de Hugging Face (`onnx-community/embeddinggemma-300m-ONNX`). Los vectores quedan en `mail.db` en este PC. Dispositivo: Auto / CPU / GPU (DirectML en Windows). Reconstruir o reparar si la búsqueda falla. Los pesos siguen los términos Google Gemma. - **Ajustes → Índices…**: índice de palabras (FTS) y, si se desea, EmbeddingGemma 300M (~300 MB) para el significado. El paquete ONNX se descarga una vez en la carpeta de la máquina (`ProgramData\MaksIT\Postclient\models`, o `/var/lib/maksit/postclient/models` en Linux) y lo comparten cada cuenta y el servicio de sincronización. Los vectores quedan en `mail.db` en este PC. Dispositivo: Auto / CPU / GPU (DirectML en Windows). Reconstruir o reparar si la búsqueda falla. Los pesos siguen los términos Google Gemma. **Marcas de spam…** lista lo que el filtro ha aprendido.
La búsqueda (carpeta actual) cubre asunto, cuerpo, texto de adjuntos (PDF y text/XML/HTML/CSV/JSON), etiquetas de expediente y el significado cuando el modelo está listo. Las etiquetas existen solo en este PC. La búsqueda (carpeta actual) cubre asunto, cuerpo, texto de adjuntos (PDF y text/XML/HTML/CSV/JSON), etiquetas de expediente y el significado cuando el modelo está listo. El cuadro de búsqueda tiene una × que borra el texto. Las etiquetas existen solo en este PC.
El archivo está **solo en este PC**. Archivo → Abrir carpeta de archivo / Exportar archivo copia `mail.db` y los `.eml`. Rutas: El archivo está **solo en este PC**. Archivo → Abrir carpeta de archivo / Exportar archivo copia `mail.db` y los `.eml`. Rutas:
@ -158,10 +161,17 @@ La **CUOTA** IMAP del gestor (si el servidor la publica) aparece en la barra de
|--|--| |--|--|
| Importar EML | Originales en la carpeta actual | | Importar EML | Originales en la carpeta actual |
| Importar mbox | Almacenes mbox locales de un perfil de correo de escritorio | | Importar mbox | Almacenes mbox locales de un perfil de correo de escritorio |
| Importar `.pst` / `.ost` | Copia el correo del archivo de datos a la cuenta IMAP/POP3 **seleccionada**. Los almacenes anidados en el archivo también se importan. Cierre cualquier programa que tenga el archivo abierto. | | Importar `.pst` / `.ost` | Copia el correo de Outlook **a un almacén de carpetas** (ruta propuesta bajo `stores/` en los datos de la app). Un segundo importado al mismo almacén omite duplicados por Message-ID. Cierre cualquier programa que tenga el archivo abierto. |
| Adjuntar archivo de datos | Abre un `.pst` / `.ost` como buzón. El `.pst` Unicode es escribible. El `.ost` no se escribe in situ: el primer cambio lo copia a un `.pst` Unicode junto al original. Menú Archivo o tipo de cuenta *archivo de datos*. | | Almacén nuevo | Menú Archivo: carpeta vacía (`Bandeja de entrada`, `Borradores`, `Enviados`, `Eliminados`) con `postclient.store.json` y `mail.db`. |
| Nuevo archivo de datos | Menú Archivo: crea un `.pst` Unicode vacío (Bandeja de entrada, Borradores, Enviados, Eliminados) y lo adjunta como buzón. | | Adjuntar almacén | Menú Archivo: elegir una carpeta que ya tenga `postclient.store.json`. |
| Reglas | **Ajustes → Reglas**: **Importar reglas…** lee la exportación JSON de esta app o un archivo `.rwz` heredado. **Exportar reglas…** escribe JSON. Cada regla está ligada a una **cuenta**; la carpeta de destino puede estar en otro buzón (incluido un archivo de datos adjunto). Se ejecutan al obtener mensajes, al importar y desde **Ejecutar todas las reglas**. | | Mover / desconectar | Copia todo el directorio, o lo quita de la lista. La carpeta sigue en disco al desconectar. |
| Retención | **Ajustes → Retención…**: días por carpeta (`0` = para siempre). Al vencer, el correo de la carpeta va a Papelera después de obtener mensajes. En Papelera, `0` la conserva para siempre y un número de días borra de forma permanente la Papelera antigua. |
| Sincronización en segundo plano | **Ajustes → Sincronización en segundo plano para esta cuenta…**: servicio al arrancar (servicio de Windows, unidad systemd de sistema en Linux). Sincroniza cada buzón guardado sin sesión. **Compartir esta cuenta** solo deja que otras personas de este PC abran ese buzón. |
| Cuenta compartida | **Ajustes de la cuenta → Compartir esta cuenta con los demás usuarios de este PC**: ese buzón pasa a la carpeta de la máquina (`ProgramData\MaksIT\Postclient`, o `Public` si ProgramData no se puede escribir; `/var/lib/maksit/postclient` en Linux; `/Users/Shared/MaksIT/Postclient` en macOS). Los demás usuarios lo ven al abrir Postclient. Las contraseñas no se copian. |
| Easy Migration | **Archivo → Easy Migration → Crear bundle…** pide la contraseña dos veces en la misma ventana y luego Guardar. El `.postbundle` contiene el correo, las cuentas, los secretos y la carpeta de modelo compartida. **Abrir bundle…** lo restaura en este PC y reescribe las rutas de los almacenes. La sincronización en segundo plano sigue apagada. |
| Marcas de spam | **Marcar como spam** recuerda ese mensaje en este PC y lo enseña a cada buzón, aunque el mensaje o el buzón ya no estén. El correo parecido es solo una pista. **No es spam** borra ese recuerdo y devuelve el mensaje a la bandeja de entrada. Mover a No deseado no enseña el filtro. **Ajustes → Índices… → Marcas de spam…** lista lo aprendido. |
| Actualizaciones y registro | **Ayuda → Buscar actualizaciones…** pregunta a GitHub la última versión y puede descargar el paquete de este sistema. **Ayuda → Registro** abre una ventana copiable sobre la carpeta de registros. **Ayuda → Acerca de Postclient** lista Info, Privacy, Security y Support en maks-it.com. |
| Reglas | **Ajustes → Reglas**: **Importar reglas…** lee la exportación JSON de esta app o un archivo `.rwz` heredado. **Exportar reglas…** escribe JSON. Cada regla está ligada a una **cuenta**; la carpeta de destino puede estar en otro buzón (incluido un almacén). Se ejecutan al obtener mensajes, al importar y desde **Ejecutar todas las reglas**. |
| Imprimir | HTML legible del mensaje (opcionalmente desempaquetado) | | Imprimir | HTML legible del mensaje (opcionalmente desempaquetado) |
| Guardar PDF | El mismo contenido en PDF | | Guardar PDF | El mismo contenido en PDF |
| ZIP de adjuntos | Todos los adjuntos del mensaje abierto | | ZIP de adjuntos | Todos los adjuntos del mensaje abierto |

View File

@ -37,8 +37,11 @@ La messagerie de bureau et le webmail traitent en général une **enveloppe** PE
- **Étiquettes de dossier sur cet ordinateur** — copropriété, dossier client, avis fiscal. Les étiquettes sont locales, pas une base de cabinet en réseau. - **Étiquettes de dossier sur cet ordinateur** — copropriété, dossier client, avis fiscal. Les étiquettes sont locales, pas une base de cabinet en réseau.
- **Archive locale du dossier entier** — Get Messages stocke **tout le dossier IMAP** dans `mail.db` plus les `.eml` sur disque (pas une fenêtre des N derniers). FTS sur objet, corps, texte des pièces PDF/XML/HTML/CSV/JSON et étiquettes. - **Archive locale du dossier entier** — Get Messages stocke **tout le dossier IMAP** dans `mail.db` plus les `.eml` sur disque (pas une fenêtre des N derniers). FTS sur objet, corps, texte des pièces PDF/XML/HTML/CSV/JSON et étiquettes.
- **Recherche de sens sur l’appareil (RAG)** — EmbeddingGemma 300M optionnel dans **Paramètres → Indices…**. Les vecteurs restent dans `mail.db`. Le courrier ne quitte pas la machine. Reconstruire ou réparer les indices mot-clé et sens. - **Recherche de sens sur l’appareil (RAG)** — EmbeddingGemma 300M optionnel dans **Paramètres → Indices…**. Les vecteurs restent dans `mail.db`. Le courrier ne quitte pas la machine. Reconstruire ou réparer les indices mot-clé et sens.
- **Règles entre boîtes** — déplacer, supprimer, marquer, étiqueter ; le dossier cible peut vivre sur un autre compte, y compris un fichier `.pst` / `.ost` attaché. Import JSON ou fichier `.rwz` historique ; l’export est JSON. - **Règles entre boîtes** — déplacer, supprimer, marquer, étiqueter ; le dossier cible peut vivre sur un autre compte, y compris un **magasin de dossiers**. Import JSON ou fichier `.rwz` historique ; l’export est JSON.
- **Ouvrir un `.pst` / `.ost` comme boîte** — le magasin Unicode est inscriptible (dossiers, drapeaux, déplacement, suppression). Un magasin hors ligne est copié vers un `.pst` Unicode à la première écriture. Aucun autre programme de messagerie n’est requis pour lire le fichier. - **Magasins de dossiers** — créer, attacher, déplacer ou détacher un dossier qui contient les `.eml` et son propre `mail.db`. **Importer PST/OST…** copie le courrier dans un magasin ; le `.pst` n’est pas une boîte. Chemin par défaut : `%LocalAppData%\Postclient\stores\{nom}` (portable : `{installation}/data/stores/{nom}`).
- **Easy Migration** — **Fichier → Easy Migration → Créer un bundle…** demande la passphrase deux fois dans la même fenêtre, puis Enregistrer. Le `.postbundle` contient le courrier, les comptes, les secrets et le dossier de modèle partagé. **Ouvrir un bundle…** le restaure dans les dossiers Postclient de ce PC. La synchro en arrière-plan reste arrêtée.
- **Synchro en arrière-plan** — **Paramètres → Synchro en arrière-plan pour ce compte…** : un service au démarrage (service Windows, unité systemd système sous Linux). Il synchronise chaque boîte enregistrée sans personne connectée.
- **Spam** — **Marquer comme spam** retient ce message sur ce PC et l’enseigne à chaque boîte, même après la disparition du message ou de la boîte. Le courrier semblable n’est qu’un indice. **Pas du spam** efface cette mémoire et renvoie le message dans la boîte de réception. Déplacer vers Indésirable n’enseigne pas le filtre.
- **Packs pays** — **Paramètres → Fonctionnalités** active ou désactive l’Italie (PEC, FatturaPA, fascicule), l’Europe (REM eIDAS), la France, l’Allemagne, l’Espagne et la Suisse. L’IMAP ordinaire reste disponible. - **Packs pays** — **Paramètres → Fonctionnalités** active ou désactive l’Italie (PEC, FatturaPA, fascicule), l’Europe (REM eIDAS), la France, l’Allemagne, l’Espagne et la Suisse. L’IMAP ordinaire reste disponible.
## Public ## Public
@ -57,7 +60,7 @@ Le programme ne remplace pas le gestionnaire, n’émet pas d’accusé qualifi
## Comptes et protocoles ## Comptes et protocoles
Entrée : **IMAP** (arborescence), **POP3** (boîte de réception seulement) ou un **`.pst` / `.ost`** attaché. Le `.pst` Unicode est inscriptible. Sortie : **SMTP**. Chiffrement : auto, SSL/TLS, STARTTLS, STARTTLS si disponible, ou aucun. MailKit utilise les méthodes SASL annoncées par le serveur. Entrée : **IMAP** (arborescence), **POP3** (boîte de réception seulement) ou un **magasin de dossiers** local. Un `.pst` Unicode peut encore être **importé** dans un magasin. Sortie : **SMTP**. Chiffrement : auto, SSL/TLS, STARTTLS, STARTTLS si disponible, ou aucun. MailKit utilise les méthodes SASL annoncées par le serveur.
Plusieurs boîtes peuvent être enregistrées. Les profils remplissent hôtes et ports : Plusieurs boîtes peuvent être enregistrées. Les profils remplissent hôtes et ports :
@ -116,10 +119,10 @@ Les notifications système signalent les nouvelles PEC, avis et REM (toast Windo
## Lecture, dossiers, rédaction ## Lecture, dossiers, rédaction
- L’arbre suit le serveur (Boîte de réception, Ricevute, Brouillons, Envoyés, Archive, Indésirable, Corbeille, plus dossiers personnalisés). Les chemins IMAP imbriqués apparaissent comme des dossiers imbriqués, y compris les libellés Gmail `[Gmail]/…` sous `[Gmail]`. Brouillons, Envoyés et Corbeille nommés `INBOX.Drafts` / `INBOX.Sent` / `INBOX.Trash` restent à côté de la boîte de réception, pas en dessous. Gmail utilise `/` comme séparateur : un libellé tel que `P.IVA` reste un seul dossier. Les dossiers système ne se suppriment pas. On peut créer un dossier, le vider (vers la Corbeille, ou définitivement dans la Corbeille), tout marquer lu/non lu, supprimer un dossier personnalisé, glisser des messages sur un dossier. L’état ouvert ou fermé des comptes et des dossiers imbriqués est conservé dans `settings.json`. - L’arbre suit le serveur (Boîte de réception, Ricevute, Brouillons, Envoyés, Archive, Indésirable, Corbeille, plus dossiers personnalisés). Les chemins IMAP imbriqués apparaissent comme des dossiers imbriqués, y compris les libellés Gmail `[Gmail]/…` sous `[Gmail]`. Brouillons, Envoyés et Corbeille nommés `INBOX.Drafts` / `INBOX.Sent` / `INBOX.Trash` restent à côté de la boîte de réception, pas en dessous. Gmail utilise `/` comme séparateur : un libellé tel que `P.IVA` reste un seul dossier. Les dossiers système ne se suppriment pas. On peut créer un dossier, le vider (vers la Corbeille, ou définitivement dans la Corbeille), tout marquer lu/non lu, supprimer un dossier personnalisé, glisser des messages sur un dossier. L’état ouvert ou fermé des comptes et des dossiers imbriqués est conservé dans `settings.json`.
- Colonnes : non lu, drapeau, pièces jointes, livraison, type (PEC / RIC / REM / SIG), de, objet, date (locale `yyyy-MM-dd HH:mm`), étiquette de dossier. - Colonnes : non lu, drapeau, pièces jointes, livraison, type (PEC / RIC / REM / SIG), de, objet, date (locale `yyyy-MM-dd HH:mm`), taille, étiquette de dossier.
- Vue : HTML (moteur web du système), texte, source, ou **FatturaPA**. Disposition : liste au-dessus de la lecture, ou trois colonnes (dossiers, liste, lecture). - Vue : HTML (moteur web du système), texte, source, ou **FatturaPA**. Disposition : liste au-dessus de la lecture, ou trois colonnes (dossiers, liste, lecture).
- **Grouper les conversations** indente les réponses dans la liste (Message-ID / In-Reply-To / References, profondeur max. 8). Les avis PEC du même original restent ensemble. Il n’y a pas de volet conversation séparé. - **Grouper les conversations** indente les réponses dans la liste (Message-ID / In-Reply-To / References, profondeur max. 8). Les avis PEC du même original restent ensemble. Il n’y a pas de volet conversation séparé.
- Actions : répondre, répondre à tous, transférer, ouvrir dans une nouvelle fenêtre, lu/non lu, drapeau, priorité, supprimer. - Actions : répondre, répondre à tous, transférer, ouvrir dans une nouvelle fenêtre, lu/non lu, drapeau, priorité, marquer comme spam, pas du spam, supprimer. **Maj+clic** et **Ctrl+clic** sélectionnent plusieurs messages ou dossiers. Supprimer (barre, **Suppr**, vider le dossier) envoie la sélection à la Corbeille. Depuis la Corbeille, la suppression est définitive.
- La rédaction est en **texte brut**. À, Cc, Cci sont des pastilles d’adresse. **Envoyer les pièces en ZIP** archive les fichiers déposés (mot de passe optionnel). L’envoi n’utilise que l’identité SMTP de la boîte choisie. - La rédaction est en **texte brut**. À, Cc, Cci sont des pastilles d’adresse. **Envoyer les pièces en ZIP** archive les fichiers déposés (mot de passe optionnel). L’envoi n’utilise que l’identité SMTP de la boîte choisie.
HTML : **WebView2** sous Windows, **WebKitGTK** sous Linux, **WKWebView** sous macOS. Sans moteur, le corps texte s’affiche. HTML : **WebView2** sous Windows, **WebKitGTK** sous Linux, **WKWebView** sous macOS. Sans moteur, le corps texte s’affiche.
@ -136,9 +139,9 @@ Fedora : `gtk3 webkit2gtk4.1 libsoup3`.
« Get Messages » indexe **tout** le dossier IMAP dans SQLite (`mail.db`) plus les `.eml` sur disque. POP3 ne remplit que la boîte de réception. L’indexation tourne en arrière-plan ; la barre d’état indique l’avancement. « Get Messages » indexe **tout** le dossier IMAP dans SQLite (`mail.db`) plus les `.eml` sur disque. POP3 ne remplit que la boîte de réception. L’indexation tourne en arrière-plan ; la barre d’état indique l’avancement.
- **Paramètres → Indices…** : index mot-clé (FTS) et, en option, EmbeddingGemma 300M (~300 Mo) pour le sens. Le paquet ONNX se télécharge depuis Hugging Face (`onnx-community/embeddinggemma-300m-ONNX`). Les vecteurs restent dans `mail.db` sur cet ordinateur. Périphérique : Auto / CPU / GPU (DirectML sous Windows). Reconstruire ou réparer si la recherche est fausse. Les poids suivent les conditions Google Gemma. - **Paramètres → Indices…** : index mot-clé (FTS) et, en option, EmbeddingGemma 300M (~300 Mo) pour le sens. Le paquet ONNX se télécharge une fois dans le dossier de la machine (`ProgramData\MaksIT\Postclient\models`, ou `/var/lib/maksit/postclient/models` sous Linux) et est partagé par chaque compte et par le service de synchro. Les vecteurs restent dans `mail.db` sur cet ordinateur. Périphérique : Auto / CPU / GPU (DirectML sous Windows). Reconstruire ou réparer si la recherche est fausse. Les poids suivent les conditions Google Gemma. **Marques de spam…** liste ce que le filtre a appris.
La recherche (dossier courant) couvre objet, corps, texte des pièces (PDF et text/XML/HTML/CSV/JSON), étiquettes et le sens lorsque le modèle est prêt. Les étiquettes n’existent que sur ce PC. La recherche (dossier courant) couvre objet, corps, texte des pièces (PDF et text/XML/HTML/CSV/JSON), étiquettes et le sens lorsque le modèle est prêt. La zone de recherche a une × qui efface le texte. Les étiquettes n’existent que sur ce PC.
L’archive est **uniquement sur cet ordinateur**. Fichier → Ouvrir le dossier d’archive / Exporter l’archive copie `mail.db` et les `.eml`. Emplacements : L’archive est **uniquement sur cet ordinateur**. Fichier → Ouvrir le dossier d’archive / Exporter l’archive copie `mail.db` et les `.eml`. Emplacements :
@ -158,10 +161,17 @@ Le **QUOTA** IMAP du gestionnaire (si le serveur le publie) apparaît dans la ba
|--|--| |--|--|
| Importer EML | Originaux dans le dossier courant | | Importer EML | Originaux dans le dossier courant |
| Importer mbox | Magasins mbox locaux issus d’un profil de messagerie de bureau | | Importer mbox | Magasins mbox locaux issus d’un profil de messagerie de bureau |
| Importer `.pst` / `.ost` | Copie le courrier du fichier de données dans le compte IMAP/POP3 **sélectionné**. Les magasins imbriqués dans le fichier sont importés aussi. Fermez tout programme qui a le fichier ouvert. | | Importer `.pst` / `.ost` | Copie le courrier Outlook **dans un magasin de dossiers** (chemin proposé sous `stores/` dans les données de l’appli). Un second import dans le même magasin ignore les doublons par Message-ID. Fermez tout programme qui a le fichier ouvert. |
| Attacher un fichier de données | Ouvre un `.pst` / `.ost` comme boîte. Le `.pst` Unicode est inscriptible. L’`.ost` n’est pas écrit sur place — le premier changement le copie vers un `.pst` Unicode à côté de l’original. Menu Fichier ou type de compte *fichier de données*. | | Nouveau magasin | Menu Fichier : dossier vide (`Boîte de réception`, `Brouillons`, `Envoyés`, `Supprimés`) avec `postclient.store.json` et `mail.db`. |
| Nouveau fichier de données | Menu Fichier : crée un `.pst` Unicode vide (Boîte de réception, Brouillons, Envoyés, Supprimés) et l’attache comme boîte. | | Attacher un magasin | Menu Fichier : choisir un dossier qui a déjà `postclient.store.json`. |
| Règles | **Paramètres → Règles** : **Importer des règles…** lit l’export JSON de cette appli ou un fichier `.rwz` historique. **Exporter des règles…** écrit du JSON. Chaque règle est liée à un **compte** ; le dossier cible peut vivre sur une autre boîte (y compris un fichier de données attaché). Elles s’exécutent à la récupération, à l’import et via **Exécuter toutes les règles**. | | Déplacer / détacher | Copie tout le dossier, ou le retire de la liste. Le dossier reste sur le disque une fois détaché. |
| Rétention | **Paramètres → Rétention…** : jours par dossier (`0` = toujours). À l’échéance, le courrier du dossier va à la Corbeille après la récupération. Sur la Corbeille, `0` la garde toujours et un nombre de jours supprime définitivement l’ancienne Corbeille. |
| Synchro en arrière-plan | **Paramètres → Synchro en arrière-plan pour ce compte…** : service au démarrage (service Windows, unité systemd système sous Linux). Synchronise chaque boîte enregistrée sans connexion. **Partager ce compte** permet seulement aux autres personnes de ce PC d’ouvrir cette boîte. |
| Compte partagé | **Paramètres du compte → Partager ce compte avec les autres utilisateurs de ce PC** : cette boîte passe dans le dossier de la machine (`ProgramData\MaksIT\Postclient`, ou `Public` si ProgramData n’est pas inscriptible ; `/var/lib/maksit/postclient` sous Linux ; `/Users/Shared/MaksIT/Postclient` sous macOS). Les autres utilisateurs la voient en ouvrant Postclient. Les mots de passe ne sont pas copiés. |
| Easy Migration | **Fichier → Easy Migration → Créer un bundle…** demande la passphrase deux fois dans la même fenêtre, puis Enregistrer. Le `.postbundle` contient le courrier, les comptes, les secrets et le dossier de modèle partagé. **Ouvrir un bundle…** le restaure sur ce PC et réécrit les chemins des magasins. La synchro en arrière-plan reste arrêtée. |
| Marques de spam | **Marquer comme spam** retient ce message sur ce PC et l’enseigne à chaque boîte, même après la disparition du message ou de la boîte. Le courrier semblable n’est qu’un indice. **Pas du spam** efface cette mémoire et renvoie le message dans la boîte de réception. Déplacer vers Indésirable n’enseigne pas le filtre. **Paramètres → Indices… → Marques de spam…** liste ce qui a été appris. |
| Mises à jour et journal | **Aide → Rechercher des mises à jour…** interroge GitHub pour la dernière version et peut télécharger le paquet de ce système. **Aide → Journal** ouvre une fenêtre copiable sur le dossier des journaux. **Aide → À propos de Postclient** liste Info, Privacy, Security et Support sur maks-it.com. |
| Règles | **Paramètres → Règles** : **Importer des règles…** lit l’export JSON de cette appli ou un fichier `.rwz` historique. **Exporter des règles…** écrit du JSON. Chaque règle est liée à un **compte** ; le dossier cible peut vivre sur une autre boîte (y compris un magasin). Elles s’exécutent à la récupération, à l’import et via **Exécuter toutes les règles**. |
| Imprimer | HTML lisible du message (éventuellement désenveloppé) | | Imprimer | HTML lisible du message (éventuellement désenveloppé) |
| Enregistrer PDF | Le même contenu en PDF | | Enregistrer PDF | Le même contenu en PDF |
| ZIP des pièces | Toutes les pièces du message ouvert | | ZIP des pièces | Toutes les pièces du message ouvert |

View File

@ -37,8 +37,11 @@ La posta da scrivania e la webmail trattano di solito una **busta** PEC o una pa
- **Etichette di pratica su questo PC** — condominio, fascicolo cliente, avviso IMU. Le etichette sono locali, non un database di studio in rete. - **Etichette di pratica su questo PC** — condominio, fascicolo cliente, avviso IMU. Le etichette sono locali, non un database di studio in rete.
- **Archivio locale di tutta la cartella** — Scarica messaggi memorizza **tutta** la cartella IMAP in `mail.db` più gli `.eml` su disco (non una finestra degli ultimi N). FTS su oggetto, corpo, testo degli allegati PDF/XML/HTML/CSV/JSON e etichette. - **Archivio locale di tutta la cartella** — Scarica messaggi memorizza **tutta** la cartella IMAP in `mail.db` più gli `.eml` su disco (non una finestra degli ultimi N). FTS su oggetto, corpo, testo degli allegati PDF/XML/HTML/CSV/JSON e etichette.
- **Ricerca per significato sul dispositivo (RAG)** — EmbeddingGemma 300M opzionale in **Impostazioni → Indici…**. I vettori restano in `mail.db`. La posta non lascia la macchina. Si possono ricostruire o riparare gli indici per parole e per significato. - **Ricerca per significato sul dispositivo (RAG)** — EmbeddingGemma 300M opzionale in **Impostazioni → Indici…**. I vettori restano in `mail.db`. La posta non lascia la macchina. Si possono ricostruire o riparare gli indici per parole e per significato.
- **Regole tra caselle** — sposta, elimina, flag, etichetta; la cartella di destinazione può stare su un altro account, compreso un file dati `.pst` / `.ost` collegato. Importa JSON o un file `.rwz` legacy; l’export è JSON. - **Regole tra caselle** — sposta, elimina, flag, etichetta; la cartella di destinazione può stare su un altro account, compreso un **archivio cartella**. Importa JSON o un file `.rwz` legacy; l’export è JSON.
- **Apre `.pst` / `.ost` come casella** — lo store Unicode è scrivibile (cartelle, flag, sposta, elimina). Un file offline viene copiato in un `.pst` Unicode alla prima scrittura. Non serve un altro programma di posta per leggere il file. - **Archivi cartella** — crea, collega, sposta o sgancia una directory con `.eml` e il proprio `mail.db` (parole e significato). **Importa PST…** copia la posta in un archivio; il `.pst` non diventa una casella. Percorso predefinito: `%LocalAppData%\Postclient\stores\{nome}` (portatile: `{installazione}/data/stores/{nome}`).
- **Easy Migration** — **File → Easy Migration → Crea bundle…** chiede la passphrase due volte nella stessa finestra, poi Salva. Il `.postbundle` contiene posta, account, segreti e la cartella modelli condivisa. **Apri bundle…** lo ripristina nelle cartelle Postclient di questo PC. La sincronizzazione in background resta spenta.
- **Sincronizzazione in background** — **Impostazioni → Sincronizzazione in background per questo account…**: un servizio all’avvio (servizio Windows, unità systemd di sistema su Linux). Sincronizza ogni casella salvata senza che nessuno abbia effettuato l’accesso.
- **Spam** — **Segna come spam** ricorda il messaggio su questo PC e insegna a ogni casella, anche dopo che il messaggio o la casella non ci sono più. La posta simile è solo un indizio. **Non spam** cancella quella memoria e riporta il messaggio in Posta in arrivo. Spostare in Indesiderata non insegna il filtro.
- **Pacchetti per Paese** — **Impostazioni → Funzionalità** accende o spegne Italia (PEC, FatturaPA, fascicolo), Europa (REM eIDAS), Francia, Germania, Spagna e Svizzera. L’IMAP ordinario resta disponibile. - **Pacchetti per Paese** — **Impostazioni → Funzionalità** accende o spegne Italia (PEC, FatturaPA, fascicolo), Europa (REM eIDAS), Francia, Germania, Spagna e Svizzera. L’IMAP ordinario resta disponibile.
## A chi serve ## A chi serve
@ -57,7 +60,7 @@ Il programma non sostituisce il gestore, non emette una ricevuta qualificata e n
## Account e protocolli ## Account e protocolli
In ingresso: **IMAP** (albero cartelle), **POP3** (solo Posta in arrivo), o un **`.pst` / `.ost`** collegato. I `.pst` Unicode sono scrivibili. In uscita: **SMTP**. Cifratura: automatica, SSL/TLS, STARTTLS, STARTTLS se disponibile, oppure nessuna. MailKit usa i metodi SASL che il server offre. In ingresso: **IMAP** (albero cartelle), **POP3** (solo Posta in arrivo), o un **archivio cartella** locale. Un `.pst` Unicode si può ancora **importare** in un archivio. In uscita: **SMTP**. Cifratura: automatica, SSL/TLS, STARTTLS, STARTTLS se disponibile, oppure nessuna. MailKit usa i metodi SASL che il server offre.
Si possono usare più caselle insieme. I profili compilano host e porte: Si possono usare più caselle insieme. I profili compilano host e porte:
@ -121,10 +124,10 @@ Le notifiche del sistema segnalano nuove PEC, ricevute e REM (toast Windows, not
## Lettura, cartelle, composizione ## Lettura, cartelle, composizione
- L’albero segue il server (Posta in arrivo, Ricevute, Bozze, Inviata, Archivio, Indesiderata, Cestino, più cartelle personalizzate). I percorsi IMAP nidificati sono cartelle nidificate, comprese le etichette Gmail `[Gmail]/…` sotto `[Gmail]`. Bozze, Inviata e Cestino che il server chiama `INBOX.Drafts` / `INBOX.Sent` / `INBOX.Trash` stanno accanto a Posta in arrivo, non sotto. Gmail usa `/` come separatore: un’etichetta come `P.IVA` resta una sola cartella. Le cartelle di sistema non si eliminano. Si può creare una cartella, svuotarla (nel Cestino, o in modo definitivo nel Cestino), segnare tutto letto/non letto, eliminare una cartella personalizzata, trascinare i messaggi su una cartella. Quali account e cartelle nidificate sono aperti o chiusi resta in `settings.json`. - L’albero segue il server (Posta in arrivo, Ricevute, Bozze, Inviata, Archivio, Indesiderata, Cestino, più cartelle personalizzate). I percorsi IMAP nidificati sono cartelle nidificate, comprese le etichette Gmail `[Gmail]/…` sotto `[Gmail]`. Bozze, Inviata e Cestino che il server chiama `INBOX.Drafts` / `INBOX.Sent` / `INBOX.Trash` stanno accanto a Posta in arrivo, non sotto. Gmail usa `/` come separatore: un’etichetta come `P.IVA` resta una sola cartella. Le cartelle di sistema non si eliminano. Si può creare una cartella, svuotarla (nel Cestino, o in modo definitivo nel Cestino), segnare tutto letto/non letto, eliminare una cartella personalizzata, trascinare i messaggi su una cartella. Quali account e cartelle nidificate sono aperti o chiusi resta in `settings.json`.
- Colonne elenco: non letto, stella, allegati, consegna, tipo (PEC / RIC / REM / SIG), da, oggetto, data (locale `yyyy-MM-dd HH:mm`), etichetta di pratica. - Colonne elenco: non letto, stella, allegati, consegna, tipo (PEC / RIC / REM / SIG), da, oggetto, data (locale `yyyy-MM-dd HH:mm`), dimensione, etichetta di pratica.
- Vista: HTML (motore web del sistema), testo, sorgente, o **FatturaPA**. Disposizione: elenco sopra la lettura, oppure tre colonne (cartelle, elenco, lettura). - Vista: HTML (motore web del sistema), testo, sorgente, o **FatturaPA**. Disposizione: elenco sopra la lettura, oppure tre colonne (cartelle, elenco, lettura).
- **Raggruppa conversazioni** indenta le risposte nell’elenco (Message-ID / In-Reply-To / References, profondità massima 8). Le ricevute PEC dello stesso originale stanno insieme. Non c’è un riquadro conversazioni separato. - **Raggruppa conversazioni** indenta le risposte nell’elenco (Message-ID / In-Reply-To / References, profondità massima 8). Le ricevute PEC dello stesso originale stanno insieme. Non c’è un riquadro conversazioni separato.
- Azioni: rispondi, rispondi a tutti, inoltra, apri in nuova finestra, letto/non letto, stella, priorità, elimina. - Azioni: rispondi, rispondi a tutti, inoltra, apri in nuova finestra, letto/non letto, stella, priorità, segna come spam, non spam, elimina. **Maiusc+clic** e **Ctrl+clic** selezionano più messaggi o cartelle. Elimina (barra, **Canc**, svuota cartella) sposta la selezione nel Cestino. Dal Cestino l’eliminazione è definitiva.
- La composizione è **testo semplice**. A, Cc, Ccn sono chip di indirizzo. Gli allegati sono elencati nel compositore. **Invia allegati come ZIP** impacca i file trascinati (password opzionale). L’invio usa solo l’identità SMTP della casella scelta. - La composizione è **testo semplice**. A, Cc, Ccn sono chip di indirizzo. Gli allegati sono elencati nel compositore. **Invia allegati come ZIP** impacca i file trascinati (password opzionale). L’invio usa solo l’identità SMTP della casella scelta.
HTML: **WebView2** su Windows (profilo sotto la cartella dati), **WebKitGTK** su Linux, **WKWebView** su macOS. Se manca il motore, si vede il corpo di testo. HTML: **WebView2** su Windows (profilo sotto la cartella dati), **WebKitGTK** su Linux, **WKWebView** su macOS. Se manca il motore, si vede il corpo di testo.
@ -141,9 +144,9 @@ Fedora: `gtk3 webkit2gtk4.1 libsoup3`.
Scarica messaggi indica **tutta** la cartella IMAP in SQLite (`mail.db`) più gli `.eml` su disco. POP3 riempie solo la Posta in arrivo. L’indicizzazione gira in background; la barra di stato mostra l’avanzamento. Scarica messaggi indica **tutta** la cartella IMAP in SQLite (`mail.db`) più gli `.eml` su disco. POP3 riempie solo la Posta in arrivo. L’indicizzazione gira in background; la barra di stato mostra l’avanzamento.
- **Impostazioni → Indici…**: indice per parole (FTS) e, se vuoi, EmbeddingGemma 300M (~300 MB) per il significato. Il pacchetto ONNX si scarica da Hugging Face (`onnx-community/embeddinggemma-300m-ONNX`). I vettori restano in `mail.db` su questo PC. Dispositivo: Automatico / CPU / GPU (DirectML su Windows). Ricrea o ripara gli indici se la ricerca è storta. I pesi usano i termini Google Gemma. - **Impostazioni → Indici…**: indice per parole (FTS) e, se vuoi, EmbeddingGemma 300M (~300 MB) per il significato. Il pacchetto ONNX si scarica una volta nella cartella della macchina (`ProgramData\MaksIT\Postclient\models`, o `/var/lib/maksit/postclient/models` su Linux) ed è condiviso da ogni account e dal servizio di sincronizzazione. I vettori restano in `mail.db` su questo PC. Dispositivo: Automatico / CPU / GPU (DirectML su Windows). Ricrea o ripara gli indici se la ricerca è storta. I pesi usano i termini Google Gemma. **Segni spam…** elenca ciò che il filtro ha imparato.
La ricerca (cartella corrente) copre oggetto, corpo, testo degli allegati (PDF e text/XML/HTML/CSV/JSON), etichette di pratica e il significato quando il modello è pronto. Le etichette esistono solo su questo PC. La ricerca (cartella corrente) copre oggetto, corpo, testo degli allegati (PDF e text/XML/HTML/CSV/JSON), etichette di pratica e il significato quando il modello è pronto. La casella di ricerca ha una × che cancella il testo. Le etichette esistono solo su questo PC.
L’archivio è **solo su questo PC**. File → Apri cartella archivio / Esporta archivio copia `mail.db` e gli `.eml`. Percorsi: L’archivio è **solo su questo PC**. File → Apri cartella archivio / Esporta archivio copia `mail.db` e gli `.eml`. Percorsi:
@ -155,7 +158,7 @@ L’archivio è **solo su questo PC**. File → Apri cartella archivio / Esporta
Override: `POSTCLIENT_CONFIG` / `POSTCLIENT_DATA_DIR`. Lo zip portatile Windows con `installType: portable` in `settings.json` tiene la config accanto all’eseguibile. Override: `POSTCLIENT_CONFIG` / `POSTCLIENT_DATA_DIR`. Lo zip portatile Windows con `installType: portable` in `settings.json` tiene la config accanto all’eseguibile.
La **QUOTA** IMAP del gestore (se il server la espone) è in barra di stato. È lo spazio della casella presso l’operatore, non uno storage cloud di questa applicazione. La **QUOTA** IMAP del gestore (se il server la supporta) è una barra sottile sul nodo di quell’account nell’albero. POP3 mostra lo spazio usato da STAT quando il server non ha un limite. Gli archivi locali non hanno quota. È lo spazio della casella presso l’operatore, non uno storage cloud di questa applicazione.
## Importazione, esportazione, stampa ## Importazione, esportazione, stampa
@ -166,8 +169,14 @@ La **QUOTA** IMAP del gestore (se il server la espone) è in barra di stato. È
| Importa `.pst` / `.ost` | Copia la posta Outlook **in un archivio cartella** (percorso proposto sotto `stores/` nei dati app). Un secondo import nello stesso archivio salta i duplicati per Message-ID. | | Importa `.pst` / `.ost` | Copia la posta Outlook **in un archivio cartella** (percorso proposto sotto `stores/` nei dati app). Un secondo import nello stesso archivio salta i duplicati per Message-ID. |
| Nuovo archivio | Menu File: cartella vuota con `postclient.store.json` e `mail.db`. | | Nuovo archivio | Menu File: cartella vuota con `postclient.store.json` e `mail.db`. |
| Collega archivio | Menu File: cartella che ha già `postclient.store.json`. | | Collega archivio | Menu File: cartella che ha già `postclient.store.json`. |
| Conservazione | **Impostazioni → Conservazione…**: giorni per cartella (`0` = per sempre). I messaggi più vecchi si eliminano in modo permanente. | | Sposta / Sgancia archivio | Copia l’intera directory, oppure la toglie dall’elenco. La cartella resta sul disco quando si sgancia. |
| Regole | **Impostazioni → Regole**: **Importa regole…** legge l’export JSON di questa app o un file `.rwz` legacy. **Esporta regole…** scrive JSON. Ogni regola è legata a una **casella**; la cartella di destinazione può stare su un’altra casella (anche un file dati collegato). Partono su Scarica messaggi, in import e da **Esegui tutte le regole**. | | Conservazione | **Impostazioni → Conservazione…**: giorni per cartella (`0` = per sempre). Alla scadenza la posta della cartella va nel Cestino dopo Scarica messaggi. Sul Cestino, `0` lo tiene per sempre e un numero di giorni elimina in modo permanente il Cestino più vecchio. |
| Sincronizzazione in background | **Impostazioni → Sincronizzazione in background per questo account…**: servizio all’avvio (servizio Windows, unità systemd di sistema su Linux). Sincronizza ogni casella salvata senza accesso. **Condividi questo account** fa solo sì che le altre persone su questo PC aprano quella casella. |
| Account condiviso | **Impostazioni account → Condividi questo account con gli altri utenti di questo PC**: quella casella passa nella cartella della macchina (`ProgramData\MaksIT\Postclient`, o `Public` se ProgramData non è scrivibile; `/var/lib/maksit/postclient` su Linux; `/Users/Shared/MaksIT/Postclient` su macOS). Gli altri utenti la vedono aprendo Postclient. Le password non vengono copiate. |
| Easy Migration | **File → Easy Migration → Crea bundle…** chiede la passphrase due volte nella stessa finestra, poi Salva. Il `.postbundle` contiene posta, account, segreti e la cartella modelli condivisa. **Apri bundle…** lo ripristina su questo PC e riscrive i percorsi degli archivi. La sincronizzazione in background resta spenta. |
| Segni spam | **Segna come spam** ricorda il messaggio su questo PC e insegna a ogni casella, anche dopo che il messaggio o la casella non ci sono più. La posta simile è solo un indizio. **Non spam** cancella quella memoria e riporta il messaggio in Posta in arrivo. Spostare in Indesiderata non insegna il filtro. **Impostazioni → Indici… → Segni spam…** elenca ciò che è stato imparato. |
| Aggiornamenti e registro | **Aiuto → Controlla aggiornamenti…** chiede a GitHub l’ultima release e può scaricare il pacchetto per questo sistema. **Aiuto → Registro** apre una finestra copiabile sulla cartella dei log. **Aiuto → Informazioni su Postclient** elenca Info, Privacy, Security e Support su maks-it.com. |
| Regole | **Impostazioni → Regole**: **Importa regole…** legge l’export JSON di questa app o un file `.rwz` legacy. **Esporta regole…** scrive JSON. Ogni regola è legata a una **casella**; la cartella di destinazione può stare su un’altra casella (anche un archivio). Partono su Scarica messaggi, in import e da **Esegui tutte le regole**. |
| Stampa | HTML leggibile del messaggio (eventualmente sbustato) | | Stampa | HTML leggibile del messaggio (eventualmente sbustato) |
| Salva PDF | Lo stesso contenuto in PDF | | Salva PDF | Lo stesso contenuto in PDF |
| Salva allegati ZIP | Tutti gli allegati del messaggio aperto | | Salva allegati ZIP | Tutti gli allegati del messaggio aperto |

View File

@ -121,10 +121,10 @@ Desktop notices fire for new PEC, ricevute, and REM (Windows toast, macOS notifi
## Reading, folders, compose ## Reading, folders, compose
- Folder tree follows the server (Inbox, Ricevute/Receipts, Drafts, Sent, Archive, Junk, Trash, plus custom folders). Nested IMAP paths show as nested folders, including Gmail’s `[Gmail]/…` labels under `[Gmail]`. Standard folders that the server stores as `INBOX.Drafts` / `INBOX.Sent` / `INBOX.Trash` sit next to Inbox, not under it. Gmail uses `/` as the separator, so a label like `P.IVA` stays one folder. System folders cannot be deleted. You can create a folder, empty one (to Trash, or permanently in Trash), mark all read/unread, delete a custom folder, and drag messages onto a folder. Which accounts and nested folders are expanded or collapsed is kept in `settings.json`. - Folder tree follows the server (Inbox, Ricevute/Receipts, Drafts, Sent, Archive, Junk, Trash, plus custom folders). Nested IMAP paths show as nested folders, including Gmail’s `[Gmail]/…` labels under `[Gmail]`. Standard folders that the server stores as `INBOX.Drafts` / `INBOX.Sent` / `INBOX.Trash` sit next to Inbox, not under it. Gmail uses `/` as the separator, so a label like `P.IVA` stays one folder. System folders cannot be deleted. You can create a folder, empty one (to Trash, or permanently in Trash), mark all read/unread, delete a custom folder, and drag messages onto a folder. Which accounts and nested folders are expanded or collapsed is kept in `settings.json`.
- List columns: unread, flag, attachments, delivery, type (PEC / RIC / REM / SIG), from, subject, date (local `yyyy-MM-dd HH:mm`), practice label. - List columns: unread, flag, attachments, delivery, type (PEC / RIC / REM / SIG), from, subject, date (local `yyyy-MM-dd HH:mm`), size, practice label.
- View: HTML (OS web engine), plain text, raw source, or **FatturaPA**. Layout: list above reading, or three columns (folders, list, reading). - View: HTML (OS web engine), plain text, raw source, or **FatturaPA**. Layout: list above reading, or three columns (folders, list, reading).
- **Group conversations** indents replies in the folder list using Message-ID / In-Reply-To / References (depth up to 8). PEC ricevute that share the same original sit together. There is no separate conversation pane. - **Group conversations** indents replies in the folder list using Message-ID / In-Reply-To / References (depth up to 8). PEC ricevute that share the same original sit together. There is no separate conversation pane.
- Message actions: reply, reply all, forward, open in a new window, read/unread, flag, priority, delete. - Message actions: reply, reply all, forward, open in a new window, read/unread, flag, priority, mark as spam, not spam, delete. **Shift+click** and **Ctrl+click** select several messages or folders. Delete (toolbar, **Del**, empty folder) moves the selection to Trash. Deleting from Trash is permanent.
- Compose is **plain text**. To, Cc, Bcc are address chips. Attachments are listed on the composer. **Send attachments as ZIP** packs dropped files (optional password). Send uses the selected mailbox’s SMTP identity only. - Compose is **plain text**. To, Cc, Bcc are address chips. Attachments are listed on the composer. **Send attachments as ZIP** packs dropped files (optional password). Send uses the selected mailbox’s SMTP identity only.
HTML uses the OS engine: **WebView2** on Windows (profile under the data folder), **WebKitGTK** on Linux, **WKWebView** on macOS. If the engine is missing, the text body is shown instead. HTML uses the OS engine: **WebView2** on Windows (profile under the data folder), **WebKitGTK** on Linux, **WKWebView** on macOS. If the engine is missing, the text body is shown instead.
@ -143,7 +143,7 @@ Get Messages indexes the **whole IMAP folder** into a local SQLite store (`mail.
- **Settings → Indices…**: keyword index (FTS) plus optional EmbeddingGemma 300M (~300 MB) meaning index. The ONNX package downloads once into the machine folder (`ProgramData\MaksIT\Postclient\models`, or `/var/lib/maksit/postclient/models` on Linux) and is shared by every account and the sync service. Vectors stay in `mail.db` on this PC. Device: Auto / CPU / GPU (Windows DirectML). Rebuild or repair if search looks wrong. Weights use Google’s Gemma Terms. - **Settings → Indices…**: keyword index (FTS) plus optional EmbeddingGemma 300M (~300 MB) meaning index. The ONNX package downloads once into the machine folder (`ProgramData\MaksIT\Postclient\models`, or `/var/lib/maksit/postclient/models` on Linux) and is shared by every account and the sync service. Vectors stay in `mail.db` on this PC. Device: Auto / CPU / GPU (Windows DirectML). Rebuild or repair if search looks wrong. Weights use Google’s Gemma Terms.
Search (current folder) covers subject, body, attachment text (PDF and text/XML/HTML/CSV/JSON), practice labels, and meaning when the model is ready. Labels exist only on this PC. Search (current folder) covers subject, body, attachment text (PDF and text/XML/HTML/CSV/JSON), practice labels, and meaning when the model is ready. The search box has an × that clears the text. Labels exist only on this PC.
The archive is **only on this PC**. File → Open archive folder / Export archive copies `mail.db` and the `.eml` files. Paths: The archive is **only on this PC**. File → Open archive folder / Export archive copies `mail.db` and the `.eml` files. Paths:
@ -170,6 +170,9 @@ IMAP **QUOTA** from the gestore (when the server supports it) is a thin bar on t
| Retention | **Settings → Retention…**: days per folder (`0` = forever). Expired folder mail is moved to Trash after Get Messages. On Trash, `0` keeps it forever and a number of days permanently deletes older Trash. | | Retention | **Settings → Retention…**: days per folder (`0` = forever). Expired folder mail is moved to Trash after Get Messages. On Trash, `0` keeps it forever and a number of days permanently deletes older Trash. |
| Background sync | **Settings → Background sync**: a boot-time service (Windows service, Linux system systemd unit). It syncs every saved mailbox with nobody signed in. **Share this account** only lets other people on this PC open that mailbox. | | Background sync | **Settings → Background sync**: a boot-time service (Windows service, Linux system systemd unit). It syncs every saved mailbox with nobody signed in. **Share this account** only lets other people on this PC open that mailbox. |
| Shared account | **Account settings → Share this account with other users on this PC**: that mailbox moves to the machine folder (`ProgramData\MaksIT\Postclient`, or `Public` when ProgramData is not writable; `/var/lib/maksit/postclient` on Linux; `/Users/Shared/MaksIT/Postclient` on macOS). Other users see it when they open Postclient. Passwords are not copied. | | Shared account | **Account settings → Share this account with other users on this PC**: that mailbox moves to the machine folder (`ProgramData\MaksIT\Postclient`, or `Public` when ProgramData is not writable; `/var/lib/maksit/postclient` on Linux; `/Users/Shared/MaksIT/Postclient` on macOS). Other users see it when they open Postclient. Passwords are not copied. |
| Easy Migration | **File → Easy Migration → Create bundle…** asks for a passphrase twice in one window, then Save. The `.postbundle` holds mail, accounts, secrets, and the shared model folder. **Open bundle…** restores it into this PC’s Postclient folders and rewrites store paths. Background sync stays off until you turn it on here. |
| Spam marks | **Mark as spam** remembers that message on this PC and teaches every mailbox, even after the message or mailbox is gone. Similar mail is only a hint. **Not spam** drops that memory and returns the message to Inbox. Moving mail into Junk does not teach the filter. **Settings → Indices… → Spam marks…** lists what was learned. |
| Updates and logs | **Help → Check for updates…** asks GitHub for the latest release and can download the package for this system. **Help → Logs** opens a copyable window over the app log folder. **Help → About** lists Info, Privacy, Security, and Support at maks-it.com. |
| Rules | **Settings → Rules**: **Import rules…** reads this app’s JSON export or a legacy `.rwz` file. **Export rules…** writes JSON. Each rule is bound to an **account**; a move folder can live on another mailbox (including a store). They run on Get Messages, on import, and from **Run all rules**. | | Rules | **Settings → Rules**: **Import rules…** reads this app’s JSON export or a legacy `.rwz` file. **Export rules…** writes JSON. Each rule is bound to an **account**; a move folder can live on another mailbox (including a store). They run on Get Messages, on import, and from **Run all rules**. |
| Print | Readable HTML of the (optionally unwrapped) message | | Print | Readable HTML of the (optionally unwrapped) message |
| Save PDF | Same content as a PDF | | Save PDF | Same content as a PDF |

View File

@ -30,6 +30,11 @@
<display_length compare="ge">768</display_length> <display_length compare="ge">768</display_length>
</requires> </requires>
<releases> <releases>
<release version="0.4.1" date="2026-09-27">
<description>
<p>Create bundle asks for the passphrase and the repeat in one window, then opens Save. About lists Info, Privacy, Security, and Support. A Windows upgrade keeps an existing desktop shortcut and removes a duplicate icon.</p>
</description>
</release>
<release version="0.4.0" date="2026-09-27"> <release version="0.4.0" date="2026-09-27">
<description> <description>
<p>Background sync starts at boot and syncs every saved mailbox with nobody signed in. Easy Migration writes a passphrase-locked bundle and restores it on another PC. Mark as spam is remembered across mailboxes. Gmail folder reads no longer fail when the mailbox is already open.</p> <p>Background sync starts at boot and syncs every saved mailbox with nobody signed in. Easy Migration writes a passphrase-locked bundle and restores it on another PC. Mark as spam is remembered across mailboxes. Gmail folder reads no longer fail when the mailbox is already open.</p>

View File

@ -3,7 +3,7 @@
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Version>0.4.0</Version> <Version>0.4.1</Version>
<Product>Postclient</Product> <Product>Postclient</Product>
<AssemblyTitle>Postclient</AssemblyTitle> <AssemblyTitle>Postclient</AssemblyTitle>
<Authors>Maksym Sadovnychyy</Authors> <Authors>Maksym Sadovnychyy</Authors>

View File

@ -1,12 +1,19 @@
namespace MaksIT.PostClient.Shared.App; namespace MaksIT.PostClient.Shared.App;
public readonly record struct AppContact(string Role, string Address) {
public string Uri => "mailto:" + Address;
}
public static class AppInfo { public static class AppInfo {
public const string Brand = "MaksIT"; public const string Brand = "MaksIT";
public const string ProductName = "Postclient"; public const string ProductName = "Postclient";
public const string Credits = "Maksym Sadovnychyy"; public const string Credits = "Maksym Sadovnychyy";
public const string Email = "maksym.sadovnychyy@gmail.com"; public static readonly AppContact Info = new("Info", "info@maks-it.com");
public const string EmailUri = "mailto:maksym.sadovnychyy@gmail.com"; public static readonly AppContact Privacy = new("Privacy", "privacy@maks-it.com");
public static readonly AppContact Security = new("Security", "security@maks-it.com");
public static readonly AppContact Support = new("Support", "support@maks-it.com");
public const string Site = "maks-it.com"; public const string Site = "maks-it.com";
public const string SiteUri = "https://maks-it.com"; public const string SiteUri = "https://maks-it.com";
public const string License = "Apache License 2.0"; public const string License = "Apache License 2.0";

View File

@ -9,7 +9,11 @@ public class AppInfoTests {
Assert.Equal("MaksIT", AppInfo.Brand); Assert.Equal("MaksIT", AppInfo.Brand);
Assert.Equal("Postclient", AppInfo.ProductName); Assert.Equal("Postclient", AppInfo.ProductName);
Assert.Equal("Maksym Sadovnychyy", AppInfo.Credits); Assert.Equal("Maksym Sadovnychyy", AppInfo.Credits);
Assert.Equal("maksym.sadovnychyy@gmail.com", AppInfo.Email); Assert.Equal("info@maks-it.com", AppInfo.Info.Address);
Assert.Equal("privacy@maks-it.com", AppInfo.Privacy.Address);
Assert.Equal("security@maks-it.com", AppInfo.Security.Address);
Assert.Equal("support@maks-it.com", AppInfo.Support.Address);
Assert.Equal("mailto:support@maks-it.com", AppInfo.Support.Uri);
Assert.Equal("https://maks-it.com", AppInfo.SiteUri); Assert.Equal("https://maks-it.com", AppInfo.SiteUri);
Assert.Equal("Desktop mail client for PEC, REM, and IMAP. Mail stays on this PC.", AppInfo.Summary); Assert.Equal("Desktop mail client for PEC, REM, and IMAP. Mail stays on this PC.", AppInfo.Summary);
Assert.Contains(DateTime.UtcNow.Year.ToString(), AppInfo.Copyright, StringComparison.Ordinal); Assert.Contains(DateTime.UtcNow.Year.ToString(), AppInfo.Copyright, StringComparison.Ordinal);

View File

@ -174,7 +174,13 @@ public class EnvelopeUnwrapperTests {
if (files.Length == 0) if (files.Length == 0)
return; return;
foreach (var file in files) { foreach (var file in files) {
var mime = MimeMessage.Load(file, TestContext.Current.CancellationToken); MimeMessage mime;
try {
mime = MimeMessage.Load(file, TestContext.Current.CancellationToken);
}
catch (IOException) {
continue;
}
var parsed = EnvelopeUnwrapper.Inspect(mime); var parsed = EnvelopeUnwrapper.Inspect(mime);
if (parsed.Info.Kind == EnvelopeKind.Ordinary) if (parsed.Info.Kind == EnvelopeKind.Ordinary)
continue; continue;

View File

@ -8,6 +8,7 @@ using Avalonia.Input;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.Threading;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using MaksIT.PostClient.UI.Windows; using MaksIT.PostClient.UI.Windows;
using MaksIT.PostClient.UI.ViewModels; using MaksIT.PostClient.UI.ViewModels;
@ -661,6 +662,8 @@ public partial class MainWindow : Window {
if (DataContext is not MainViewModel vm) if (DataContext is not MainViewModel vm)
return null; return null;
var kind = new FilePickerFileType(vm.Copy.EasyMigration) { Patterns = ["*.postbundle"] }; var kind = new FilePickerFileType(vm.Copy.EasyMigration) { Patterns = ["*.postbundle"] };
Activate();
await Dispatcher.UIThread.InvokeAsync(static () => { }, DispatcherPriority.ApplicationIdle);
if (save) { if (save) {
var result = await StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions { var result = await StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions {
Title = vm.Copy.CreateBundle, Title = vm.Copy.CreateBundle,

View File

@ -19,7 +19,13 @@ public partial class AboutViewModel {
public string Credits => AppInfo.Credits; public string Credits => AppInfo.Credits;
public string Email => AppInfo.Email; public AppContact InfoContact => AppInfo.Info;
public AppContact PrivacyContact => AppInfo.Privacy;
public AppContact SecurityContact => AppInfo.Security;
public AppContact SupportContact => AppInfo.Support;
public string License => Copy.AboutLicense; public string License => Copy.AboutLicense;
@ -28,8 +34,11 @@ public partial class AboutViewModel {
public string Site => AppInfo.Site; public string Site => AppInfo.Site;
[RelayCommand] [RelayCommand]
private void OpenEmail() => private void OpenContact(string? uri) {
OpenUrl(AppInfo.EmailUri); if (string.IsNullOrWhiteSpace(uri))
return;
OpenUrl(uri);
}
[RelayCommand] [RelayCommand]
private void OpenSite() => private void OpenSite() =>

View File

@ -1070,26 +1070,14 @@ public partial class MainViewModel : ObservableObject, IAsyncDisposable {
var pass = await PromptRequested(new PromptRequest { var pass = await PromptRequested(new PromptRequest {
Title = Copy.EasyMigration, Title = Copy.EasyMigration,
Message = Copy.EasyMigrationPassphrase, Message = Copy.EasyMigrationPassphrase,
Password = true Password = true,
RepeatPassword = true,
RepeatMessage = Copy.EasyMigrationConfirmPassphrase,
RequiredMessage = Copy.EasyMigrationPassphraseRequired,
MismatchMessage = Copy.EasyMigrationPassphraseMismatch
}); });
if (pass is null) if (string.IsNullOrWhiteSpace(pass))
return; return;
if (string.IsNullOrWhiteSpace(pass)) {
Status = Copy.EasyMigrationPassphraseRequired;
return;
}
var again = await PromptRequested(new PromptRequest {
Title = Copy.EasyMigration,
Message = Copy.EasyMigrationConfirmPassphrase,
Password = true
});
if (again is null)
return;
if (!string.Equals(pass, again, StringComparison.Ordinal)) {
Status = Copy.EasyMigrationPassphraseMismatch;
return;
}
var path = await PickBundleRequested(true); var path = await PickBundleRequested(true);
if (string.IsNullOrWhiteSpace(path)) if (string.IsNullOrWhiteSpace(path))
@ -5210,6 +5198,14 @@ public sealed class PromptRequest {
public bool Password { get; init; } public bool Password { get; init; }
public bool RepeatPassword { get; init; }
public string RepeatMessage { get; init; } = "";
public string RequiredMessage { get; init; } = "";
public string MismatchMessage { get; init; } = "";
public bool ConfirmOnly { get; init; } public bool ConfirmOnly { get; init; }
public string? ConfirmLabel { get; init; } public string? ConfirmLabel { get; init; }

View File

@ -3,7 +3,7 @@
xmlns:vm="using:MaksIT.PostClient.UI.ViewModels" xmlns:vm="using:MaksIT.PostClient.UI.ViewModels"
x:Class="MaksIT.PostClient.UI.Windows.AboutWindow" x:Class="MaksIT.PostClient.UI.Windows.AboutWindow"
x:DataType="vm:AboutViewModel" x:DataType="vm:AboutViewModel"
Width="440" Width="480"
SizeToContent="Height" SizeToContent="Height"
CanResize="False" CanResize="False"
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"
@ -16,7 +16,7 @@
<TextBlock Text="{Binding ProductName}" Classes="section" FontSize="20" /> <TextBlock Text="{Binding ProductName}" Classes="section" FontSize="20" />
<TextBlock Text="{Binding Summary}" Classes="hint" TextWrapping="Wrap" /> <TextBlock Text="{Binding Summary}" Classes="hint" TextWrapping="Wrap" />
<Grid ColumnDefinitions="100,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto" <Grid ColumnDefinitions="100,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto"
Margin="0,8,0,0"> Margin="0,8,0,0">
<TextBlock Text="{Binding Copy.AboutVersion}" Foreground="{StaticResource MutedBrush}" /> <TextBlock Text="{Binding Copy.AboutVersion}" Foreground="{StaticResource MutedBrush}" />
<TextBlock Grid.Column="1" Text="{Binding Version}" FontWeight="SemiBold" /> <TextBlock Grid.Column="1" Text="{Binding Version}" FontWeight="SemiBold" />
@ -24,16 +24,31 @@
<TextBlock Grid.Row="1" Text="{Binding Copy.AboutCredits}" Foreground="{StaticResource MutedBrush}" Margin="0,8,0,0" /> <TextBlock Grid.Row="1" Text="{Binding Copy.AboutCredits}" Foreground="{StaticResource MutedBrush}" Margin="0,8,0,0" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Credits}" Margin="0,8,0,0" /> <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Credits}" Margin="0,8,0,0" />
<TextBlock Grid.Row="2" Text="{Binding Copy.AboutEmail}" Foreground="{StaticResource MutedBrush}" Margin="0,8,0,0" /> <TextBlock Grid.Row="2" Text="{Binding InfoContact.Role}" Foreground="{StaticResource MutedBrush}" Margin="0,8,0,0" />
<Button Grid.Row="2" Grid.Column="1" Content="{Binding Email}" <Button Grid.Row="2" Grid.Column="1" Content="{Binding InfoContact.Address}"
Command="{Binding OpenEmailCommand}" Classes="ghost" Command="{Binding OpenContactCommand}" CommandParameter="{Binding InfoContact.Uri}"
HorizontalAlignment="Left" Padding="8,4" Margin="0,6,0,0" /> Classes="ghost" HorizontalAlignment="Left" Padding="8,4" Margin="0,6,0,0" />
<TextBlock Grid.Row="3" Text="{Binding Copy.AboutLicenseLabel}" Foreground="{StaticResource MutedBrush}" Margin="0,8,0,0" /> <TextBlock Grid.Row="3" Text="{Binding PrivacyContact.Role}" Foreground="{StaticResource MutedBrush}" Margin="0,8,0,0" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding License}" Margin="0,8,0,0" /> <Button Grid.Row="3" Grid.Column="1" Content="{Binding PrivacyContact.Address}"
Command="{Binding OpenContactCommand}" CommandParameter="{Binding PrivacyContact.Uri}"
Classes="ghost" HorizontalAlignment="Left" Padding="8,4" Margin="0,6,0,0" />
<TextBlock Grid.Row="4" Text="{Binding Copy.AboutCopyright}" Foreground="{StaticResource MutedBrush}" Margin="0,8,0,0" /> <TextBlock Grid.Row="4" Text="{Binding SecurityContact.Role}" Foreground="{StaticResource MutedBrush}" Margin="0,8,0,0" />
<TextBlock Grid.Row="4" Grid.Column="1" Text="{Binding Copyright}" Classes="hint" Margin="0,8,0,0" TextWrapping="Wrap" /> <Button Grid.Row="4" Grid.Column="1" Content="{Binding SecurityContact.Address}"
Command="{Binding OpenContactCommand}" CommandParameter="{Binding SecurityContact.Uri}"
Classes="ghost" HorizontalAlignment="Left" Padding="8,4" Margin="0,6,0,0" />
<TextBlock Grid.Row="5" Text="{Binding SupportContact.Role}" Foreground="{StaticResource MutedBrush}" Margin="0,8,0,0" />
<Button Grid.Row="5" Grid.Column="1" Content="{Binding SupportContact.Address}"
Command="{Binding OpenContactCommand}" CommandParameter="{Binding SupportContact.Uri}"
Classes="ghost" HorizontalAlignment="Left" Padding="8,4" Margin="0,6,0,0" />
<TextBlock Grid.Row="6" Text="{Binding Copy.AboutLicenseLabel}" Foreground="{StaticResource MutedBrush}" Margin="0,8,0,0" />
<TextBlock Grid.Row="6" Grid.Column="1" Text="{Binding License}" Margin="0,8,0,0" />
<TextBlock Grid.Row="7" Text="{Binding Copy.AboutCopyright}" Foreground="{StaticResource MutedBrush}" Margin="0,8,0,0" />
<TextBlock Grid.Row="7" Grid.Column="1" Text="{Binding Copyright}" Classes="hint" Margin="0,8,0,0" TextWrapping="Wrap" />
</Grid> </Grid>
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Right" Margin="0,12,0,0"> <StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Right" Margin="0,12,0,0">

View File

@ -13,7 +13,10 @@
</StackPanel> </StackPanel>
<StackPanel Spacing="10"> <StackPanel Spacing="10">
<TextBlock x:Name="MessageText" TextWrapping="Wrap" /> <TextBlock x:Name="MessageText" TextWrapping="Wrap" />
<TextBox x:Name="ValueBox" /> <TextBox x:Name="ValueBox" TextChanged="OnValueChanged" />
<TextBlock x:Name="RepeatLabel" Classes="hint" IsVisible="False" />
<TextBox x:Name="RepeatBox" PasswordChar="●" IsVisible="False" TextChanged="OnValueChanged" />
<TextBlock x:Name="ErrorText" Classes="unresolved" TextWrapping="Wrap" IsVisible="False" />
</StackPanel> </StackPanel>
</DockPanel> </DockPanel>
</Window> </Window>

View File

@ -21,6 +21,12 @@ public partial class PromptWindow : Window {
window.ValueBox.PlaceholderText = request.Placeholder; window.ValueBox.PlaceholderText = request.Placeholder;
window.ValueBox.PasswordChar = request.Password ? '●' : '\0'; window.ValueBox.PasswordChar = request.Password ? '●' : '\0';
window.ValueBox.IsVisible = !request.ConfirmOnly; window.ValueBox.IsVisible = !request.ConfirmOnly;
window._repeat = request.RepeatPassword;
window._requiredMessage = request.RequiredMessage;
window._mismatchMessage = request.MismatchMessage;
window.RepeatLabel.Text = request.RepeatMessage;
window.RepeatLabel.IsVisible = request.RepeatPassword;
window.RepeatBox.IsVisible = request.RepeatPassword;
if (!request.ConfirmOnly) if (!request.ConfirmOnly)
window.ValueBox.Focus(); window.ValueBox.Focus();
await window.ShowDialog(owner); await window.ShowDialog(owner);
@ -28,14 +34,40 @@ public partial class PromptWindow : Window {
} }
private bool _accepted; private bool _accepted;
private bool _repeat;
private string? _value; private string? _value;
private string _requiredMessage = "";
private string _mismatchMessage = "";
private void OnOkClick(object? sender, RoutedEventArgs e) { private void OnOkClick(object? sender, RoutedEventArgs e) {
var value = ValueBox.IsVisible ? ValueBox.Text ?? "" : "";
if (_repeat) {
if (string.IsNullOrWhiteSpace(value)) {
ShowError(_requiredMessage);
ValueBox.Focus();
return;
}
if (!string.Equals(value, RepeatBox.Text ?? "", StringComparison.Ordinal)) {
ShowError(_mismatchMessage);
RepeatBox.Focus();
return;
}
}
_accepted = true; _accepted = true;
_value = ValueBox.IsVisible ? ValueBox.Text ?? "" : ""; _value = value;
Close(); Close();
} }
private void OnValueChanged(object? sender, TextChangedEventArgs e) =>
ErrorText.IsVisible = false;
private void ShowError(string message) {
ErrorText.Text = message;
ErrorText.IsVisible = !string.IsNullOrWhiteSpace(message);
}
private void OnCancelClick(object? sender, RoutedEventArgs e) { private void OnCancelClick(object? sender, RoutedEventArgs e) {
_accepted = false; _accepted = false;
Close(); Close();

View File

@ -323,8 +323,33 @@ function New-WixPackageXml {
$desktopProp = $xml.CreateElement('Property', $ns) $desktopProp = $xml.CreateElement('Property', $ns)
$null = $desktopProp.SetAttribute('Id', 'INSTALLDESKTOPSHORTCUT') $null = $desktopProp.SetAttribute('Id', 'INSTALLDESKTOPSHORTCUT')
$null = $desktopProp.SetAttribute('Value', '0') $null = $desktopProp.SetAttribute('Value', '0')
$null = $desktopProp.SetAttribute('Secure', 'yes')
$null = $package.AppendChild($desktopProp) $null = $package.AppendChild($desktopProp)
# Present when a previous install wrote the desktop shortcut marker.
# Burn also searches HKCU as the user and passes DESKTOPSHORTCUTFOUND,
# because a per-machine execute sequence runs as SYSTEM.
$foundProp = $xml.CreateElement('Property', $ns)
$null = $foundProp.SetAttribute('Id', 'DESKTOPSHORTCUTFOUND')
$null = $foundProp.SetAttribute('Secure', 'yes')
$foundSearch = $xml.CreateElement('RegistrySearch', $ns)
$null = $foundSearch.SetAttribute('Id', 'DesktopShortcutFoundSearch')
$null = $foundSearch.SetAttribute('Root', 'HKCU')
$null = $foundSearch.SetAttribute('Key', "Software\$Manufacturer\$AppName")
$null = $foundSearch.SetAttribute('Name', 'desktop')
$null = $foundSearch.SetAttribute('Type', 'raw')
$null = $foundProp.AppendChild($foundSearch)
$null = $package.AppendChild($foundProp)
# Checkbox off on reinstall must not drop an icon that is already there.
# Major upgrade removes the previous shortcut; this installs that one again.
$keepShortcut = $xml.CreateElement('SetProperty', $ns)
$null = $keepShortcut.SetAttribute('Id', 'INSTALLDESKTOPSHORTCUT')
$null = $keepShortcut.SetAttribute('Value', '1')
$null = $keepShortcut.SetAttribute('Before', 'CostFinalize')
$null = $keepShortcut.SetAttribute('Condition', 'DESKTOPSHORTCUTFOUND = "1"')
$null = $package.AppendChild($keepShortcut)
$productFolder = Get-DesktopInstallFolderName ` $productFolder = Get-DesktopInstallFolderName `
-AppName $AppName ` -AppName $AppName `
-Manufacturer $Manufacturer ` -Manufacturer $Manufacturer `
@ -449,7 +474,8 @@ function New-WixPackageXml {
$desktop = $xml.CreateElement('Component', $ns) $desktop = $xml.CreateElement('Component', $ns)
$null = $desktop.SetAttribute('Id', 'DesktopShortcut') $null = $desktop.SetAttribute('Id', 'DesktopShortcut')
$null = $desktop.SetAttribute('Directory', 'DesktopFolder') $null = $desktop.SetAttribute('Directory', 'DesktopFolder')
$null = $desktop.SetAttribute('Guid', '*') $shortcutGuid = Get-DesktopShortcutComponentGuid -Manufacturer $Manufacturer -AppName $AppName
$null = $desktop.SetAttribute('Guid', $shortcutGuid.ToString('D'))
$null = $desktop.SetAttribute('Condition', 'INSTALLDESKTOPSHORTCUT = 1') $null = $desktop.SetAttribute('Condition', 'INSTALLDESKTOPSHORTCUT = 1')
$desktopShortcut = $xml.CreateElement('Shortcut', $ns) $desktopShortcut = $xml.CreateElement('Shortcut', $ns)
$null = $desktopShortcut.SetAttribute('Id', 'AppDesktopShortcut') $null = $desktopShortcut.SetAttribute('Id', 'AppDesktopShortcut')
@ -461,6 +487,26 @@ function New-WixPackageXml {
} }
$null = $desktop.AppendChild($desktopShortcut) $null = $desktop.AppendChild($desktopShortcut)
# Same file name is replaced. A second ".lnk" (and the "(2)" copy) is removed
# before CreateShortcuts so a checked box does not leave two desktop icons.
$cleanupNames = [System.Collections.Generic.List[string]]::new()
$cleanupNames.Add($productFolder)
if (-not $cleanupNames.Contains($AppName)) {
$cleanupNames.Add($AppName)
}
$cleanupIndex = 0
foreach ($shortcutName in $cleanupNames) {
foreach ($suffix in @('', ' (2)')) {
$cleanupIndex++
$removeLnk = $xml.CreateElement('RemoveFile', $ns)
$null = $removeLnk.SetAttribute('Id', "DesktopShortcutCleanup$cleanupIndex")
$null = $removeLnk.SetAttribute('Name', "$shortcutName$suffix.lnk")
$null = $removeLnk.SetAttribute('On', 'install')
$null = $desktop.AppendChild($removeLnk)
}
}
$desktopReg = $xml.CreateElement('RegistryValue', $ns) $desktopReg = $xml.CreateElement('RegistryValue', $ns)
$null = $desktopReg.SetAttribute('Root', 'HKCU') $null = $desktopReg.SetAttribute('Root', 'HKCU')
$null = $desktopReg.SetAttribute('Key', "Software\$Manufacturer\$AppName") $null = $desktopReg.SetAttribute('Key', "Software\$Manufacturer\$AppName")
@ -874,6 +920,27 @@ function Assert-FlatpakAppId {
} }
} }
function Get-DesktopShortcutComponentGuid {
param(
[Parameter(Mandatory = $true)]
[string]$Manufacturer,
[Parameter(Mandatory = $true)]
[string]$AppName
)
$identity = "maksit-desktop-shortcut|$Manufacturer|$AppName"
$md5 = [System.Security.Cryptography.MD5]::Create()
try {
$hash = $md5.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($identity))
}
finally {
$md5.Dispose()
}
return [guid]::new($hash)
}
function Get-DerivedBundleUpgradeCode { function Get-DerivedBundleUpgradeCode {
param( param(
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
@ -1196,15 +1263,18 @@ function New-WixBundleXml {
$folderRoot + $Manufacturer + '\' + $productFolder $folderRoot + $Manufacturer + '\' + $productFolder
} }
$escapedFolder = [System.Security.SecurityElement]::Escape($folderPath) $escapedFolder = [System.Security.SecurityElement]::Escape($folderPath)
$escapedShortcutKey = [System.Security.SecurityElement]::Escape("Software\$Manufacturer\$AppName")
# WiX v7 Bundle has no Scope attribute (WIX0004). MSI Package/@Scope plus # WiX v7 Bundle has no Scope attribute (WIX0004). MSI Package/@Scope plus
# InstallFolder tokens decide per-machine vs per-user; Burn infers bundle scope. # InstallFolder tokens decide per-machine vs per-user; Burn infers bundle scope.
return @" return @"
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:bal="http://wixtoolset.org/schemas/v4/wxs/bal"> <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:bal="http://wixtoolset.org/schemas/v4/wxs/bal" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
<Bundle Name="$escapedName" Manufacturer="$escapedMfr" Version="$ProductVersion" UpgradeCode="$($bundleUpgrade.ToString('D'))"$iconAttr> <Bundle Name="$escapedName" Manufacturer="$escapedMfr" Version="$ProductVersion" UpgradeCode="$($bundleUpgrade.ToString('D'))"$iconAttr>
<Variable Name="InstallFolder" Type="formatted" Value="$escapedFolder" bal:Overridable="yes" /> <Variable Name="InstallFolder" Type="formatted" Value="$escapedFolder" bal:Overridable="yes" />
<Variable Name="InstallDesktopShortcut" Type="numeric" Value="0" bal:Overridable="yes" /> <Variable Name="InstallDesktopShortcut" Type="numeric" Value="0" bal:Overridable="yes" />
<Variable Name="DesktopShortcutFound" Type="string" Value="" bal:Overridable="yes" />
<util:RegistrySearch Id="DesktopShortcutFoundSearch" Variable="DesktopShortcutFound" Root="HKCU" Key="$escapedShortcutKey" Value="desktop" Result="exists" />
<BootstrapperApplication> <BootstrapperApplication>
<bal:WixStandardBootstrapperApplication Theme="$theme" LicenseUrl=""$logoAttrs /> <bal:WixStandardBootstrapperApplication Theme="$theme" LicenseUrl=""$logoAttrs />
</BootstrapperApplication> </BootstrapperApplication>
@ -1212,6 +1282,7 @@ function New-WixBundleXml {
<MsiPackage SourceFile="$escapedMsi" Compressed="yes" Vital="yes"> <MsiPackage SourceFile="$escapedMsi" Compressed="yes" Vital="yes">
<MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]" /> <MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]" />
<MsiProperty Name="INSTALLDESKTOPSHORTCUT" Value="[InstallDesktopShortcut]" /> <MsiProperty Name="INSTALLDESKTOPSHORTCUT" Value="[InstallDesktopShortcut]" />
<MsiProperty Name="DESKTOPSHORTCUTFOUND" Value="[DesktopShortcutFound]" />
</MsiPackage> </MsiPackage>
</Chain> </Chain>
</Bundle> </Bundle>

View File

@ -13,7 +13,8 @@
per-machine installs go to `C:\Program Files`, not Program Files (x86). per-machine installs go to `C:\Program Files`, not Program Files (x86).
Requires the WiX CLI (`dotnet tool install -g wix`). WiX v7: accept the Requires the WiX CLI (`dotnet tool install -g wix`). WiX v7: accept the
OSMF EULA (`wix eula accept wix7` or `-acceptEula wix7`) and OSMF EULA (`wix eula accept wix7` or `-acceptEula wix7`) and
`wix extension add -g WixToolset.BootstrapperApplications.wixext`. `wix extension add -g WixToolset.BootstrapperApplications.wixext` and
`wix extension add -g WixToolset.Util.wixext`.
Reopen the shell so `%USERPROFILE%\.dotnet\tools` is on PATH. Reopen the shell so `%USERPROFILE%\.dotnet\tools` is on PATH.
#> #>
@ -40,7 +41,7 @@ function Test-WixMissingException {
} }
function Get-WixMissingInstallMessage { function Get-WixMissingInstallMessage {
return "WiX CLI ('wix') is not on PATH. Install with: dotnet tool install -g wix. For WiX v7 also run: wix eula accept wix7; wix extension add -g WixToolset.BootstrapperApplications.wixext. Reopen the terminal so %USERPROFILE%\.dotnet\tools is on PATH." return "WiX CLI ('wix') is not on PATH. Install with: dotnet tool install -g wix. For WiX v7 also run: wix eula accept wix7; wix extension add -g WixToolset.BootstrapperApplications.wixext; wix extension add -g WixToolset.Util.wixext. Reopen the terminal so %USERPROFILE%\.dotnet\tools is on PATH."
} }
function Get-WixCliVersionText { function Get-WixCliVersionText {
@ -280,7 +281,8 @@ function Invoke-Plugin {
Write-Log -Level "STEP" -Message "Building Windows installer exe ($wixArch)..." Write-Log -Level "STEP" -Message "Building Windows installer exe ($wixArch)..."
try { try {
Invoke-ExternalCommand -Name wix -ArgumentList (@('build') + $eulaArgs + $archArgs + @($bundleWxsPath, '-ext', $bundleExt, '-o', $exePath)) | Out-Null Invoke-ExternalCommand -Name wix -ArgumentList (@('extension', 'add', '-g', 'WixToolset.Util.wixext') + $eulaArgs) | Out-Null
Invoke-ExternalCommand -Name wix -ArgumentList (@('build') + $eulaArgs + $archArgs + @($bundleWxsPath, '-ext', $bundleExt, '-ext', 'WixToolset.Util.wixext', '-o', $exePath)) | Out-Null
} }
catch { catch {
if (Test-WixMissingException -ErrorRecord $_) { if (Test-WixMissingException -ErrorRecord $_) {