diff --git a/README.md b/README.md index 281d351..e079821 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,92 @@ -# Templates Reply -## Mozilla Thunderbird Add-On - -Templates Reply is a completely free and open-source Thunderbird extension that helps you create, manage, and reuse message templates directly from your compose window. -It is designed for speed, simplicity, and convenience — no sign-ups, no limits, and absolutely no data collection. - -This add-on does not track, store, or transmit any personal information. -All templates and settings are saved locally on your device and never leave your computer. -No analytics, telemetry, or remote servers are involved — your privacy is 100% respected. - -You can freely use, modify, and distribute the source code under the terms of the MIT License. -## Screenshots - - - - - - -## Install Locally (Temporary / Developer Mode) -``` -Open Thunderbird -Go to Tools → Add-ons and Themes -Click the ⚙️ (gear icon) in the top-right corner -Select “Debug Add-ons” -Click “Load Temporary Add-on…” -Choose the manifest.json file from your add-on directory - -⚠️ Note: This installation is temporary and will be removed when Thunderbird is restarted. -``` - -## OR via Thunderbird Add-on Manager -``` -Open Thunderbird -Go toTools → Add-ons and Themes -Search for the Templates Reply add-on by name -Click Add to Thunderbird -Confirm installation -``` +# HPS Vorlagen & Signaturen + +Thunderbird-Plugin (v2.2.0) zur zentralen Verwaltung von E-Mail-Vorlagen und Signaturen für Hotel Park Soltau. Vorlagen und Signaturen werden über ein Gitea/Forgejo-Repository synchronisiert und stehen so allen Mitarbeitern zur Verfügung. + +## Features + +- **E-Mail-Vorlagen** erstellen, bearbeiten und per Klick in Compose-Fenster einfügen +- **3 Sichtbarkeitsstufen** pro Vorlage: + - **Persönlich** — nur für den eigenen Account, gesynct in `_benutzer/{email}/` + - **Abteilung** — für alle in der Abteilung, gesynct in den Abteilungsordner + - **Alle Abteilungen** — firmenweit, gesynct in `_gemeinsam/` +- **Signaturen-Verwaltung** mit persönlichem Kopfbereich + gemeinsamer Fußzeile pro Abteilung +- **Git-Sync** über Gitea/Forgejo API (Pull + Push, automatisch alle 15 Min.) +- **Auto-Erkennung** von Abteilung und Benutzer via `_config/abteilungen.json` +- **WYSIWYG-Editor** mit Schriftart, Farben, Listen, Bildern, Links +- **Sichtbarkeit direkt änderbar** per klickbarem Badge in der Vorlagenliste + +## Repository-Struktur (Gitea) + +``` +repo/ +├── _gemeinsam/ # Vorlagen für alle Abteilungen +│ └── beispiel-vorlage.html +├── _benutzer/ # Persönliche Vorlagen pro User +│ ├── max@hotel-park-soltau.de/ +│ └── anna@hotel-park-soltau.de/ +├── _config/ +│ └── abteilungen.json # E-Mail → Abteilung Mapping +├── Rezeption/ # Abteilungsvorlagen +├── IT/ +├── signatures/ +│ ├── headers/ # Persönliche Signatur-Köpfe +│ │ └── max@hotel.de.max-mustermann.html +│ └── footers/ # Gemeinsame Fußbereiche +│ └── Rezeption.html +``` + +### `_config/abteilungen.json` + +Mapping von Abteilungs-E-Mail-Adressen zu Ordnernamen. Wird vom Plugin gelesen, um Abteilung und persönliche E-Mail automatisch zu erkennen: + +```json +{ + "info@hotel-park-soltau.de": "Rezeption", + "veranstaltungs@hotel-park-soltau.de": "Veranstaltungsbuero", + "it@hotel-park-soltau.de": "IT", + "haustechnik@hotel-park-soltau.de": "Haustechnik" +} +``` + +## Plugin-Aufbau + +| Datei | Funktion | +|---|---| +| `manifest.json` | Extension-Manifest (Thunderbird WebExtension v2) | +| `background.js` | Template-Insertion ins Compose-Fenster | +| `popup.html` / `popup.js` | Popup beim Klick auf "Vorlagen" im Compose | +| `lib/gitea-sync.js` | Gitea-API-Client + Sync-Manager | +| `lib/mdi/` | Material Design Icons (Subset) | +| `templates_options/` | Einstellungsseite (Vorlagen, Signaturen, Verbindung) | + +## Installation + +### Lokal (Entwicklung) + +1. Thunderbird öffnen +2. Extras → Add-ons und Themes +3. Zahnrad-Icon → Add-on aus Datei installieren +4. `templates-reply-hotel.xpi` auswählen + +### XPI bauen + +```bash +7z a templates-reply-hotel.xpi manifest.json background.js popup.html popup.js lib/ templates_options/ icons/ +``` + +## Einrichtung + +1. **Verbindung konfigurieren**: Einstellungen-Tab (⚙) → Server-URL, Repository, Token eingeben → Verbindung speichern +2. **Abteilung wählen** (oder automatisch erkannt via `abteilungen.json`) +3. **Vorlagen erstellen**: Vorlagen-Tab → Neue Vorlage → Sichtbarkeit wählen → Speichern +4. **Signaturen einrichten**: Signaturen-Tab → Identität wählen → Kopfbereich bearbeiten → Speichern + +## Voraussetzungen + +- Mozilla Thunderbird >= 109.0 +- Gitea/Forgejo-Server mit API-Zugang +- API-Token mit Repository-Schreibrechten + +## Lizenz + +MIT License diff --git a/lib/gitea-sync.js b/lib/gitea-sync.js index fdcc2ba..8755d91 100644 --- a/lib/gitea-sync.js +++ b/lib/gitea-sync.js @@ -5,6 +5,8 @@ const SYNC_STATE_KEY = 'sync_state'; const TEMPLATE_STORAGE_KEY = 'message_templates'; const SYNC_INTERVAL_MS = 15 * 60 * 1000; // 15 minutes const SHARED_FOLDER = '_gemeinsam'; +const USER_FOLDER = '_benutzer'; +const CONFIG_FOLDER = '_config'; // ── Gitea API Client ── @@ -133,6 +135,16 @@ class GiteaClient { if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); return res.json(); } + + async getConfig() { + const data = await this.getFile(`${CONFIG_FOLDER}/abteilungen.json`); + if (!data) return null; + try { + return JSON.parse(GiteaClient.fromBase64(data.content)); + } catch (_) { + return null; + } + } } // ── Sync Manager ── @@ -162,6 +174,15 @@ class SyncManager { return this.config?.department || ''; } + get authorEmail() { + return this.config?.authorEmail || ''; + } + + async autoDetect() { + if (!this.isConfigured) return null; + return await this.client.getConfig(); + } + async getSyncState() { const result = await browser.storage.local.get(SYNC_STATE_KEY); return result[SYNC_STATE_KEY] || { fileShas: {} }; @@ -200,7 +221,7 @@ class SyncManager { const entries = await this.client.listDir(''); const departments = []; for (const entry of entries) { - if (entry.type === 'dir' && entry.name !== SHARED_FOLDER && entry.name !== 'signatures' && !entry.name.startsWith('.')) { + if (entry.type === 'dir' && entry.name !== SHARED_FOLDER && entry.name !== USER_FOLDER && entry.name !== CONFIG_FOLDER && entry.name !== 'signatures' && !entry.name.startsWith('.')) { departments.push(entry.name); } } @@ -212,10 +233,11 @@ class SyncManager { */ async checkRemoteShas() { if (!this.isConfigured) throw new Error('Sync nicht konfiguriert'); - if (!this.department) throw new Error('Keine Abteilung ausgewählt'); const remoteShas = {}; // "folder/filename" -> sha - const folders = [SHARED_FOLDER, this.department]; + const folders = [SHARED_FOLDER]; + if (this.department) folders.push(this.department); + if (this.authorEmail) folders.push(`${USER_FOLDER}/${this.authorEmail}`); for (const folder of folders) { const files = await this.client.listDir(folder); @@ -234,15 +256,16 @@ class SyncManager { */ async pullTemplates() { if (!this.isConfigured) throw new Error('Sync nicht konfiguriert'); - if (!this.department) throw new Error('Keine Abteilung ausgewählt'); const syncState = await this.getSyncState(); const newTemplates = []; const newShas = {}; let updated = 0; - // Load from both folders - const folders = [SHARED_FOLDER, this.department]; + // Load from shared + department + personal folders + const folders = [SHARED_FOLDER]; + if (this.department) folders.push(this.department); + if (this.authorEmail) folders.push(`${USER_FOLDER}/${this.authorEmail}`); for (const folder of folders) { const files = await this.client.listDir(folder); @@ -280,7 +303,6 @@ class SyncManager { */ async pushTemplates() { if (!this.isConfigured) throw new Error('Sync nicht konfiguriert'); - if (!this.department) throw new Error('Keine Abteilung ausgewählt'); if (!this.config.authorName) throw new Error('Bitte Name eintragen (für Commit-Zuordnung)'); const templates = await this.getLocalTemplates(); @@ -289,15 +311,21 @@ class SyncManager { // Group templates by folder const byFolder = {}; for (const t of templates) { - const folder = t.folder || this.department; + let folder; + if (t.folder === SHARED_FOLDER) folder = SHARED_FOLDER; + else if (t.folder?.startsWith(USER_FOLDER + '/')) folder = t.folder; + else folder = t.folder || this.department; + if (!folder) continue; // skip if no department and no explicit folder if (!byFolder[folder]) byFolder[folder] = []; byFolder[folder].push(t); } let pushed = 0; - // Only push to folders the user has templates in - const allowedFolders = [SHARED_FOLDER, this.department]; + // Allowed folders: shared + department + personal + const allowedFolders = [SHARED_FOLDER]; + if (this.department) allowedFolders.push(this.department); + if (this.authorEmail) allowedFolders.push(`${USER_FOLDER}/${this.authorEmail}`); for (const folder of allowedFolders) { const localInFolder = byFolder[folder] || []; @@ -662,7 +690,7 @@ const syncManager = new SyncManager(); // ── Background message handler for sync ── browser.runtime.onMessage.addListener(async (msg, sender) => { - const syncActions = ['testConnection', 'pullTemplates', 'pushTemplates', 'pullSingleTemplate', 'pushSingleTemplate', 'deleteRemoteTemplate', 'listDepartments', 'checkRemoteShas', 'pullSignatures', 'pushSignatures', 'loadSignatureTemplate', 'loadFooter', 'pushFooter']; + const syncActions = ['testConnection', 'pullTemplates', 'pushTemplates', 'pullSingleTemplate', 'pushSingleTemplate', 'deleteRemoteTemplate', 'listDepartments', 'checkRemoteShas', 'pullSignatures', 'pushSignatures', 'loadSignatureTemplate', 'loadFooter', 'pushFooter', 'autoDetect']; if (!syncActions.includes(msg.action)) return; try { @@ -713,6 +741,10 @@ browser.runtime.onMessage.addListener(async (msg, sender) => { case 'pushFooter': return await syncManager.pushFooter(msg.html); + case 'autoDetect': + const config = await syncManager.autoDetect(); + return { success: true, config }; + default: return { success: false, error: 'Unbekannte Aktion' }; } diff --git a/lib/mdi/mdi-editor.css b/lib/mdi/mdi-editor.css index 5bc53dd..19172ea 100644 --- a/lib/mdi/mdi-editor.css +++ b/lib/mdi/mdi-editor.css @@ -27,3 +27,13 @@ .mdi-link::before { content: "\F0337"; } .mdi-format-clear::before { content: "\F0265"; } .mdi-image::before { content: "\F02E9"; } +.mdi-cloud-upload::before { content: "\F0167"; } +.mdi-cloud-download::before { content: "\F0162"; } +.mdi-cloud-check::before { content: "\F0157"; } +.mdi-cloud-alert::before { content: "\F09DF"; } +.mdi-sync::before { content: "\F04E6"; } +.mdi-cog::before { content: "\F0493"; } +.mdi-eye::before { content: "\F0208"; } +.mdi-eye-off::before { content: "\F0209"; } +.mdi-account-group::before { content: "\F0849"; } +.mdi-account::before { content: "\F0004"; } diff --git a/manifest.json b/manifest.json index 3f09dbe..24b7bb8 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "HPS Vorlagen & Signaturen", - "version": "2.0.0", + "version": "2.2.0", "description": "Vorlagen- und Signaturverwaltung für Hotel Park Soltau mit Git-Sync", "browser_specific_settings": { "gecko": { diff --git a/popup.html b/popup.html index 409fea1..01f3b81 100644 --- a/popup.html +++ b/popup.html @@ -96,10 +96,11 @@