From 864be546468021211f3bdf242644886942583ce3 Mon Sep 17 00:00:00 2001 From: Kendrick Bollens Date: Wed, 6 May 2026 22:04:46 +0200 Subject: [PATCH] v2.2.0: 3-Stufen-Sichtbarkeit, UX-Verbesserungen, Auto-Erkennung MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 3 Sichtbarkeitsstufen für Vorlagen: Persönlich / Abteilung / Alle - Persönliche Vorlagen werden in _benutzer/{email}/ synchronisiert - Sichtbarkeit direkt in der Liste per Dropdown änderbar - Warnung beim Verringern der Sichtbarkeit (Server-Löschung) - Auto-Erkennung von Abteilung + E-Mail via _config/abteilungen.json - Toast-Benachrichtigungen statt unsichtbare Status-Badges - Lade-Spinner bei Sync-Operationen - Sync-Dots mit Symbolen (nicht nur Farbe) für Barrierefreiheit - Custom Delete-Modal statt browser confirm() - Collapsible-Sections visuell als klickbar erkennbar - Token-Feld mit Show/Hide-Toggle - Inline-Validierung für Template-Namen - Checkbox-Klickflächen vergrößert + Label-Klick - Offline-Erkennung mit Banner - Font-Dropdown Viewport-Fix - Popup: Prefix-Dropdown verständlicher - Signaturen: erste Identität automatisch ausgewählt - README komplett neu geschrieben --- README.md | 130 ++++-- lib/gitea-sync.js | 54 ++- lib/mdi/mdi-editor.css | 10 + manifest.json | 2 +- popup.html | 5 +- templates-reply-hotel.xpi | Bin 424439 -> 428841 bytes templates_options/templates_options.html | 256 +++++++++- templates_options/templates_options.js | 564 +++++++++++++++++++---- 8 files changed, 861 insertions(+), 160 deletions(-) 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 - -![App Screenshot](https://addons.thunderbird.net/user-media/previews/full/207/207836.png?modified=1762427184) -![App Screenshot](https://addons.thunderbird.net/user-media/previews/full/207/207837.png?modified=1762427184) -![App Screenshot](https://addons.thunderbird.net/user-media/previews/full/207/207838.png?modified=1762427184) - - -## 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 @@
Vorlage auswählen
diff --git a/templates-reply-hotel.xpi b/templates-reply-hotel.xpi index f0bbedd3ecc2953a67f1c36fa3242c4d413efe2e..70d48dcae62e821fc2f40b4e4429a863c36bf271 100644 GIT binary patch delta 27658 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

D=Hs*>#bNg|Wpi!Ni{jG4w90~FT|#Ee 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 delta 23215 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 L1_0pb^*jA9*6${= diff --git a/templates_options/templates_options.html b/templates_options/templates_options.html index 050f837..16b83f6 100644 --- a/templates_options/templates_options.html +++ b/templates_options/templates_options.html @@ -327,17 +327,25 @@ .template-item:hover { background: #fafff8; } .template-item input[type="checkbox"] { - width: 16px; - height: 16px; - margin-right: 12px; + width: 18px; + height: 18px; + margin-right: 0; accent-color: #4a7c59; cursor: pointer; + flex-shrink: 0; + } + + .template-label { + display: flex; + align-items: center; + flex: 1; + cursor: pointer; + gap: 10px; } .template-name { font-weight: 500; font-size: 13.5px; - flex: 1; } .template-actions { display: flex; gap: 6px; } @@ -354,8 +362,9 @@ .template-actions .push-btn, .template-actions .pull-btn { - font-weight: bold; - font-size: 14px; + font-weight: 500; + font-size: 12px; + white-space: nowrap; } .template-actions .push-btn { color: #4a7c59; } .template-actions .push-btn:hover { background: #e8f0eb; border-color: #4a7c59; } @@ -510,6 +519,208 @@ border: 1px dashed #e0e0e0; border-radius: 6px; } + + /* Badges for shared/private */ + .badge { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 11px; + font-weight: 500; + padding: 2px 8px; + border-radius: 10px; + margin-left: 8px; + white-space: nowrap; + } + .badge-shared { background: #e3f2fd; color: #1565c0; } + .badge-dept { background: #fff3e0; color: #e65100; } + .badge-private { background: #f3e5f5; color: #7b1fa2; } + + /* Badge as inline select */ + select.scope-select { + border: none; + font-size: 11px; + font-weight: 500; + padding: 2px 4px 2px 8px; + border-radius: 10px; + cursor: pointer; + margin-left: 8px; + outline: none; + } + select.scope-select.badge-shared { background: #e3f2fd; color: #1565c0; } + select.scope-select.badge-dept { background: #fff3e0; color: #e65100; } + select.scope-select.badge-private { background: #f3e5f5; color: #7b1fa2; } + + /* Toast notifications */ + #toast-container { + position: fixed; + bottom: 20px; + right: 20px; + z-index: 9999; + display: flex; + flex-direction: column; + gap: 8px; + max-width: 320px; + } + .toast { + padding: 12px 16px; + border-radius: 8px; + font-size: 13px; + color: white; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + animation: toast-in 0.3s ease; + display: flex; + align-items: center; + gap: 8px; + } + .toast-success { background: #43a047; } + .toast-error { background: #e53935; } + .toast-info { background: #1e88e5; } + .toast-out { animation: toast-out 0.3s ease forwards; } + @keyframes toast-in { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } + @keyframes toast-out { from { opacity: 1; } to { opacity: 0; transform: translateY(10px); } } + + /* Spinner */ + .spinner { + display: inline-block; + width: 14px; + height: 14px; + border: 2px solid rgba(255,255,255,0.3); + border-top-color: currentColor; + border-radius: 50%; + animation: spin 0.6s linear infinite; + vertical-align: middle; + } + .spinner-dark { + border-color: rgba(0,0,0,0.15); + border-top-color: #4a7c59; + } + @keyframes spin { to { transform: rotate(360deg); } } + + /* Improved sync dots */ + .sync-dot.in-sync::after { + content: "✓"; + font-size: 8px; + color: white; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + .sync-dot.out-of-sync::after { + content: "!"; + font-size: 8px; + font-weight: bold; + color: white; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + .sync-dot { + position: relative; + width: 14px; + height: 14px; + } + + /* Collapsible improvements */ + .collapsible-header { + background: #f9f9f9; + border: 1px solid #e8e8e8; + border-radius: 6px; + padding: 8px 12px; + margin-bottom: 4px; + } + .collapsible-header:hover { + background: #f0f5f1; + border-color: #c0d4c7; + } + .collapsible-header .arrow { font-size: 13px; } + + /* Token toggle */ + .password-wrapper { + position: relative; + } + .password-wrapper input { + padding-right: 36px; + } + .password-toggle { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + border: none; + background: none; + cursor: pointer; + color: #999; + padding: 2px; + line-height: 1; + } + .password-toggle:hover { color: #555; } + + /* Offline banner */ + .offline-banner { + display: none; + background: #ff9800; + color: white; + text-align: center; + padding: 10px; + font-size: 13px; + font-weight: 500; + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 9998; + } + .offline-banner.visible { display: block; } + + /* Confirm modal */ + .modal-backdrop { + display: none; + position: fixed; + inset: 0; + background: rgba(0,0,0,0.4); + z-index: 10000; + align-items: center; + justify-content: center; + } + .modal-backdrop.open { display: flex; } + .modal-card { + background: white; + border-radius: 10px; + padding: 24px; + max-width: 400px; + width: 90%; + box-shadow: 0 8px 32px rgba(0,0,0,0.2); + } + .modal-card h3 { + font-size: 16px; + margin-bottom: 12px; + color: #333; + } + .modal-card p { + font-size: 13px; + color: #666; + margin-bottom: 20px; + line-height: 1.5; + } + .modal-actions { + display: flex; + gap: 8px; + justify-content: flex-end; + } + + /* Validation */ + .input-error { border-color: #e53935 !important; } + .input-valid { border-color: #43a047 !important; } + .validation-msg { + font-size: 11px; + margin-top: -6px; + margin-bottom: 8px; + } + .validation-msg.error { color: #e53935; } + .validation-msg.success { color: #43a047; } @@ -584,9 +795,13 @@ -

- - +
+ +
@@ -811,7 +1026,10 @@
- +
+ + +
@@ -825,6 +1043,24 @@
+ +
Keine Internetverbindung – Synchronisation nicht möglich
+ + +
+ + + + diff --git a/templates_options/templates_options.js b/templates_options/templates_options.js index 79238ea..e26a688 100644 --- a/templates_options/templates_options.js +++ b/templates_options/templates_options.js @@ -6,6 +6,112 @@ const SYNC_CONFIG_KEY = 'gitea_config'; const SIG_SOURCE_KEY = 'sig_source_map'; const SIG_FOOTER_KEY = 'sig_footer_cache'; +// ── Toast, Modal, Offline, Token Toggle ── + +function showToast(message, type = 'info', duration = 4000) { + const container = document.getElementById('toast-container'); + const toast = document.createElement('div'); + toast.className = `toast toast-${type}`; + toast.textContent = message; + container.appendChild(toast); + setTimeout(() => { + toast.classList.add('toast-out'); + setTimeout(() => toast.remove(), 300); + }, duration); +} + +function showConfirmDialog(title, message, confirmText = 'Löschen') { + return new Promise(resolve => { + const modal = document.getElementById('confirm-modal'); + document.getElementById('modal-title').textContent = title; + document.getElementById('modal-message').textContent = message; + document.getElementById('modal-confirm').textContent = confirmText; + modal.classList.add('open'); + + function cleanup(result) { + modal.classList.remove('open'); + document.getElementById('modal-confirm').removeEventListener('click', onConfirm); + document.getElementById('modal-cancel').removeEventListener('click', onCancel); + resolve(result); + } + function onConfirm() { cleanup(true); } + function onCancel() { cleanup(false); } + document.getElementById('modal-confirm').addEventListener('click', onConfirm); + document.getElementById('modal-cancel').addEventListener('click', onCancel); + }); +} + +function checkOnline() { + if (!navigator.onLine) { + showToast('Keine Internetverbindung – Synchronisation nicht möglich.', 'error', 5000); + return false; + } + return true; +} + +window.addEventListener('online', () => { + document.getElementById('offline-banner').classList.remove('visible'); + showToast('Wieder online.', 'success', 2000); +}); +window.addEventListener('offline', () => { + document.getElementById('offline-banner').classList.add('visible'); +}); + +// Token toggle + name validation +document.addEventListener('DOMContentLoaded', () => { + const toggle = document.getElementById('token-toggle'); + if (toggle) { + toggle.addEventListener('click', () => { + const input = document.getElementById('sync-token'); + const icon = toggle.querySelector('.mdi'); + if (input.type === 'password') { + input.type = 'text'; + icon.className = 'mdi mdi-eye-off'; + } else { + input.type = 'password'; + icon.className = 'mdi mdi-eye'; + } + }); + } + + // Template name inline validation + const nameInput = document.getElementById('template-name'); + if (nameInput) { + nameInput.addEventListener('input', async () => { + const val = nameInput.value.trim(); + nameInput.classList.remove('input-error', 'input-valid'); + const msgEl = nameInput.parentElement.querySelector('.validation-msg'); + if (msgEl) msgEl.remove(); + + if (!val) { + nameInput.classList.add('input-error'); + nameInput.insertAdjacentHTML('afterend', 'Bitte Titel eingeben'); + return; + } + + const templates = await getTemplates(); + const editId = document.getElementById('template-id').value; + const duplicate = templates.find(t => t.name.toLowerCase() === val.toLowerCase() && t.id !== editId); + if (duplicate) { + nameInput.classList.add('input-error'); + nameInput.insertAdjacentHTML('afterend', 'Titel bereits vergeben'); + } else { + nameInput.classList.add('input-valid'); + } + }); + } +}); + +// ── Cached author email (for personal folder path) ── +let cachedAuthorEmail = ''; + +async function getAuthorEmail() { + if (cachedAuthorEmail) return cachedAuthorEmail; + const result = await browser.storage.local.get(SYNC_CONFIG_KEY); + cachedAuthorEmail = result[SYNC_CONFIG_KEY]?.authorEmail || ''; + return cachedAuthorEmail; +} + // ── DOM Elements ── const editorPanel = document.getElementById('tpl-editor-panel'); @@ -62,7 +168,20 @@ function renderFontDropdown(filter) { }); fontDropdown.appendChild(div); } - fontDropdown.classList.toggle('open', matches.length > 0); + const isOpen = matches.length > 0; + fontDropdown.classList.toggle('open', isOpen); + if (isOpen) { + requestAnimationFrame(() => { + const rect = fontDropdown.getBoundingClientRect(); + if (rect.bottom > window.innerHeight) { + fontDropdown.style.top = 'auto'; + fontDropdown.style.bottom = '100%'; + } else { + fontDropdown.style.top = '100%'; + fontDropdown.style.bottom = 'auto'; + } + }); + } } fontInput.addEventListener('focus', () => renderFontDropdown(fontInput.value)); @@ -158,7 +277,20 @@ function renderSigFontDropdown(filter) { }); sigFontDropdown.appendChild(div); } - sigFontDropdown.classList.toggle('open', matches.length > 0); + const isOpen = matches.length > 0; + sigFontDropdown.classList.toggle('open', isOpen); + if (isOpen) { + requestAnimationFrame(() => { + const rect = sigFontDropdown.getBoundingClientRect(); + if (rect.bottom > window.innerHeight) { + sigFontDropdown.style.top = 'auto'; + sigFontDropdown.style.bottom = '100%'; + } else { + sigFontDropdown.style.top = '100%'; + sigFontDropdown.style.bottom = 'auto'; + } + }); + } } sigFontInput.addEventListener('focus', () => renderSigFontDropdown(sigFontInput.value)); @@ -374,19 +506,24 @@ function renderTemplates(templates) { const item = document.createElement('div'); item.className = 'template-item'; const syncClass = getTplSyncClass(template); - const folderBadge = template.folder - ? `[${template.folder}]` - : ''; + const scopeValue = template.folder === '_gemeinsam' ? 'shared' + : template.folder?.startsWith('_benutzer/') ? 'private' : 'department'; + const badgeClass = scopeValue === 'shared' ? 'badge-shared' + : scopeValue === 'department' ? 'badge-dept' : 'badge-private'; + const folderBadge = ``; const pushBtn = syncClass === 'out-of-sync' - ? `` + ? `` : ''; const pullBtn = hasServerUpdate(template) - ? `` + ? `` : ''; item.innerHTML = ` - - - ${template.name}${folderBadge} + +
${pullBtn}${pushBtn} @@ -400,12 +537,17 @@ function renderTemplates(templates) { document.querySelectorAll('.delete-btn').forEach(b => b.addEventListener('click', handleDelete)); document.querySelectorAll('.push-btn').forEach(b => b.addEventListener('click', handlePushSingle)); document.querySelectorAll('.pull-btn').forEach(b => b.addEventListener('click', handlePullSingle)); + document.querySelectorAll('.scope-select').forEach(sel => sel.addEventListener('change', handleScopeChange)); } // ── Inline Editor Panel ── function openEditorPanel() { editorPanel.classList.add('open'); + // Update department name in scope dropdown + const deptName = document.getElementById('sync-department')?.value || 'Abteilung'; + const deptOption = document.querySelector('#tpl-scope-select option[value="department"]'); + if (deptOption) deptOption.textContent = deptName; editorPanel.scrollIntoView({ behavior: 'smooth', block: 'start' }); } @@ -420,7 +562,7 @@ function closeEditorPanel() { document.getElementById('new-template-button').addEventListener('click', () => { closeEditorPanel(); - document.getElementById('tpl-shared-toggle').checked = false; + document.getElementById('tpl-scope-select').value = 'private'; formLegend.textContent = 'Neue Vorlage erstellen'; saveButton.textContent = 'Speichern'; openEditorPanel(); @@ -437,30 +579,73 @@ templateForm.addEventListener('submit', async (e) => { const content = getEditorContent(); if (!content.trim()) { - alert('Bitte Inhalt eingeben.'); + showToast('Bitte Inhalt eingeben.', 'error'); return; } let templates = await getTemplates(); - const isShared = document.getElementById('tpl-shared-toggle').checked; - const folder = isShared ? '_gemeinsam' : undefined; + const scope = document.getElementById('tpl-scope-select').value; + const authorEmail = await getAuthorEmail(); + let folder; + if (scope === 'shared') folder = '_gemeinsam'; + else if (scope === 'private') folder = `_benutzer/${authorEmail}`; + else folder = undefined; // department — pushTemplates sets this.department + + if (scope === 'private' && !authorEmail) { + showToast('Bitte E-Mail in den Einstellungen eintragen für persönliche Vorlagen.', 'error'); + return; + } if (id) { const index = templates.findIndex(t => t.id === id); if (index > -1) { - const remotePath = templates[index].remotePath; - const effectiveFolder = folder !== undefined ? folder : templates[index].folder; - templates[index] = { id, name, content, folder: effectiveFolder, remotePath }; + const existingTemplate = templates[index]; + const oldFolder = existingTemplate.folder; + const scopeChanged = oldFolder !== folder; + + // Handle scope change: delete old remote file if needed + if (scopeChanged && existingTemplate.remotePath) { + const isDowngrade = (oldFolder === '_gemeinsam' && folder !== '_gemeinsam') || + (!oldFolder?.startsWith('_benutzer/') && folder?.startsWith('_benutzer/')); + + if (isDowngrade) { + const confirmed = await showConfirmDialog( + 'Sichtbarkeit verringern?', + 'Die Vorlage wird am bisherigen Ort gelöscht. Andere Nutzer verlieren ggf. den Zugriff.', + 'Trotzdem ändern' + ); + if (!confirmed) return; + } + try { + await browser.runtime.sendMessage({ + action: 'deleteRemoteTemplate', + remotePath: existingTemplate.remotePath + }); + } catch (_) {} + existingTemplate.remotePath = undefined; + } + + templates[index] = { id, name, content, folder, remotePath: existingTemplate.remotePath }; } } else { - templates.push({ id: Date.now().toString(), name, content, folder: folder || undefined }); + templates.push({ id: Date.now().toString(), name, content, folder }); } await saveTemplates(templates); renderTemplates(templates); closeEditorPanel(); updateTplSyncIndicator(); + + // Auto-push to server + try { + await browser.runtime.sendMessage({ action: 'pushTemplates' }); + const freshTemplates = await getTemplates(); + await checkForServerUpdates(); + storeTplHashes(freshTemplates); + renderTemplates(freshTemplates); + updateTplSyncIndicator(); + } catch (_) {} }); // ── Edit / Delete / Push / Pull ── @@ -473,7 +658,10 @@ async function handleEdit(e) { document.getElementById('template-id').value = template.id; document.getElementById('template-name').value = template.name; - document.getElementById('tpl-shared-toggle').checked = (template.folder === '_gemeinsam'); + const scopeSelect = document.getElementById('tpl-scope-select'); + if (template.folder === '_gemeinsam') scopeSelect.value = 'shared'; + else if (template.folder?.startsWith('_benutzer/')) scopeSelect.value = 'private'; + else scopeSelect.value = 'department'; setEditorContent(template.content); formLegend.textContent = 'Vorlage bearbeiten'; saveButton.textContent = 'Aktualisieren'; @@ -487,12 +675,12 @@ async function handleDelete(e) { if (!template) return; if (template.remotePath) { - const choice = confirm( - `"${template.name}" löschen?\n\n` + - `OK = Lokal UND vom Server löschen (für alle)\n` + - `Abbrechen = Nicht löschen` + const confirmed = await showConfirmDialog( + `"${template.name}" löschen?`, + 'Die Vorlage wird lokal und vom Server gelöscht (für alle Nutzer).', + 'Überall löschen' ); - if (!choice) return; + if (!confirmed) return; // Delete from server try { @@ -501,29 +689,37 @@ async function handleDelete(e) { remotePath: template.remotePath }); if (!result?.success) { - alert('Fehler beim Löschen vom Server: ' + (result?.error || 'Unbekannt')); + showToast('Fehler beim Löschen vom Server: ' + (result?.error || 'Unbekannt'), 'error', 6000); } } catch (err) { - alert('Fehler beim Löschen vom Server: ' + err.message); + showToast('Fehler beim Löschen vom Server: ' + err.message, 'error', 6000); } } else { - if (!confirm('Diese Vorlage wirklich löschen?')) return; + const confirmed = await showConfirmDialog( + 'Vorlage löschen?', + `"${template.name}" wirklich löschen?` + ); + if (!confirmed) return; } templates = templates.filter(t => t.id !== id); await saveTemplates(templates); renderTemplates(templates); updateTplSyncIndicator(); + showToast('Vorlage gelöscht.', 'info'); } async function handlePullSingle(e) { - const id = e.target.dataset.id; + const btn = e.target.closest('.pull-btn') || e.target; + const id = btn.dataset.id; const templates = await getTemplates(); const template = templates.find(t => t.id === id); if (!template || !template.remotePath) return; + if (!checkOnline()) return; - e.target.textContent = '...'; - e.target.disabled = true; + const origHTML = btn.innerHTML; + btn.innerHTML = ''; + btn.disabled = true; try { const result = await browser.runtime.sendMessage({ @@ -540,26 +736,30 @@ async function handlePullSingle(e) { saveSyncHashes(); renderTemplates(templates); updateTplSyncIndicator(); + showToast('Vorlage heruntergeladen.', 'success'); } else { - alert('Fehler: ' + (result?.error || 'Unbekannt')); - e.target.textContent = '\u2193'; - e.target.disabled = false; + showToast('Fehler: ' + (result?.error || 'Unbekannt'), 'error', 6000); + btn.innerHTML = origHTML; + btn.disabled = false; } } catch (err) { - alert('Fehler: ' + err.message); - e.target.textContent = '\u2193'; - e.target.disabled = false; + showToast('Fehler: ' + err.message, 'error', 6000); + btn.innerHTML = origHTML; + btn.disabled = false; } } async function handlePushSingle(e) { - const id = e.target.dataset.id; + const btn = e.target.closest('.push-btn') || e.target; + const id = btn.dataset.id; const templates = await getTemplates(); const template = templates.find(t => t.id === id); if (!template) return; + if (!checkOnline()) return; - e.target.textContent = '...'; - e.target.disabled = true; + const origHTML = btn.innerHTML; + btn.innerHTML = ''; + btn.disabled = true; try { const result = await browser.runtime.sendMessage({ @@ -571,18 +771,87 @@ async function handlePushSingle(e) { saveSyncHashes(); renderTemplates(templates); updateTplSyncIndicator(); + showToast('Vorlage hochgeladen.', 'success'); } else { - alert('Fehler: ' + (result?.error || 'Unbekannt')); - e.target.textContent = '\u2191'; - e.target.disabled = false; + showToast('Fehler: ' + (result?.error || 'Unbekannt'), 'error', 6000); + btn.innerHTML = origHTML; + btn.disabled = false; } } catch (err) { - alert('Fehler: ' + err.message); - e.target.textContent = '\u2191'; - e.target.disabled = false; + showToast('Fehler: ' + err.message, 'error', 6000); + btn.innerHTML = origHTML; + btn.disabled = false; } } +// ── Inline Scope Change ── + +async function handleScopeChange(e) { + const id = e.target.dataset.id; + const newScope = e.target.value; + let templates = await getTemplates(); + const template = templates.find(t => t.id === id); + if (!template) return; + + const authorEmail = await getAuthorEmail(); + const oldFolder = template.folder; + let newFolder; + if (newScope === 'shared') newFolder = '_gemeinsam'; + else if (newScope === 'private') newFolder = `_benutzer/${authorEmail}`; + else newFolder = undefined; + + if (oldFolder === newFolder) return; + + if (newScope === 'private' && !authorEmail) { + showToast('Bitte E-Mail in den Einstellungen eintragen.', 'error'); + // Reset + e.target.value = oldFolder === '_gemeinsam' ? 'shared' + : oldFolder?.startsWith('_benutzer/') ? 'private' : 'department'; + return; + } + + // Warn on downgrade + const isDowngrade = (oldFolder === '_gemeinsam' && newFolder !== '_gemeinsam') || + (!oldFolder?.startsWith('_benutzer/') && newFolder?.startsWith('_benutzer/')); + + if (isDowngrade && template.remotePath) { + const confirmed = await showConfirmDialog( + 'Sichtbarkeit verringern?', + 'Die Vorlage wird am bisherigen Ort gelöscht. Andere Nutzer verlieren ggf. den Zugriff.', + 'Trotzdem ändern' + ); + if (!confirmed) { + e.target.value = oldFolder === '_gemeinsam' ? 'shared' + : oldFolder?.startsWith('_benutzer/') ? 'private' : 'department'; + return; + } + } + + // Delete old remote file if exists + if (template.remotePath) { + try { + await browser.runtime.sendMessage({ + action: 'deleteRemoteTemplate', + remotePath: template.remotePath + }); + } catch (_) {} + template.remotePath = undefined; + } + + template.folder = newFolder; + await saveTemplates(templates); + + // Auto-push to new location + try { + await browser.runtime.sendMessage({ action: 'pushTemplates' }); + templates = await getTemplates(); + } catch (_) {} + + renderTemplates(templates); + updateTplSyncIndicator(); + showToast('Sichtbarkeit geändert.', 'success'); +} + // ── Bulk Actions ── document.getElementById('select-all-button').addEventListener('click', () => { @@ -594,13 +863,19 @@ document.getElementById('select-all-button').addEventListener('click', () => { document.getElementById('delete-selected-button').addEventListener('click', async () => { const checked = document.querySelectorAll('.template-checkbox:checked'); if (checked.length === 0) return; - if (!confirm(`${checked.length} Vorlage(n) wirklich löschen?`)) return; + const confirmed = await showConfirmDialog( + 'Ausgewählte löschen?', + `${checked.length} Vorlage(n) wirklich löschen?`, + `${checked.length} löschen` + ); + if (!confirmed) return; const idsToDelete = new Set(Array.from(checked).map(cb => cb.dataset.id)); let templates = await getTemplates(); templates = templates.filter(t => !idsToDelete.has(t.id)); await saveTemplates(templates); renderTemplates(templates); + showToast(`${checked.length} Vorlage(n) gelöscht.`, 'info'); }); // ── HTML File Import ── @@ -611,9 +886,7 @@ document.getElementById('import-button').addEventListener('click', async () => { const files = fileInput.files; if (files.length === 0) { - statusEl.textContent = 'Bitte Dateien auswählen!'; - statusEl.style.color = 'red'; - statusEl.style.display = 'inline'; + showToast('Bitte Dateien auswählen!', 'error'); return; } @@ -640,28 +913,26 @@ document.getElementById('import-button').addEventListener('click', async () => { renderTemplates(templates); updateTplSyncIndicator(); - statusEl.textContent = `${importCount} Vorlage(n) importiert!`; - statusEl.style.color = 'green'; - statusEl.style.display = 'inline'; + showToast(`${importCount} Vorlage(n) importiert!`, 'success'); fileInput.value = ''; - setTimeout(() => { statusEl.style.display = 'none'; }, 3000); }); // ── Sync "Aktualisieren" Button (Pull + Push) ── document.getElementById('sync-refresh-button').addEventListener('click', async () => { - const statusEl = document.getElementById('sync-sync-status'); - statusEl.textContent = 'Synchronisiere...'; - statusEl.style.color = '#777'; - statusEl.style.display = 'inline'; + if (!checkOnline()) return; + const btn = document.getElementById('sync-refresh-button'); + const origText = btn.textContent; + btn.disabled = true; + btn.innerHTML = ' Synchronisiere...'; try { // Pull first const pullResult = await browser.runtime.sendMessage({ action: 'pullTemplates' }); if (!pullResult?.success) { - statusEl.textContent = pullResult?.error || 'Fehler beim Laden'; - statusEl.style.color = 'red'; - setTimeout(() => { statusEl.style.display = 'none'; }, 4000); + showToast(pullResult?.error || 'Fehler beim Laden', 'error', 6000); + btn.disabled = false; + btn.textContent = origText; return; } @@ -674,14 +945,12 @@ document.getElementById('sync-refresh-button').addEventListener('click', async ( renderTemplates(templates); updateTplSyncIndicator(); - const msg = `${pullResult.updated || 0} geladen, ${pushResult?.pushed || 0} hochgeladen`; - statusEl.textContent = msg; - statusEl.style.color = 'green'; + showToast(`${pullResult.updated || 0} geladen, ${pushResult?.pushed || 0} hochgeladen`, 'success'); } catch (err) { - statusEl.textContent = 'Fehler: ' + err.message; - statusEl.style.color = 'red'; + showToast('Fehler: ' + err.message, 'error', 6000); } - setTimeout(() => { statusEl.style.display = 'none'; }, 4000); + btn.disabled = false; + btn.textContent = origText; }); // ── Signaturen ── @@ -756,6 +1025,12 @@ async function loadIdentities() { sigIdentitySelect.appendChild(opt); } } + + // Auto-select first identity and trigger change + if (allIdentities.length > 0 && !sigIdentitySelect.value) { + sigIdentitySelect.value = allIdentities[0].id; + sigIdentitySelect.dispatchEvent(new Event('change')); + } } // Load signature header into editor when identity is selected @@ -871,11 +1146,8 @@ document.getElementById('sig-load-template').addEventListener('click', async () }); function showSigStatus(message, color) { - const el = document.getElementById('sig-status'); - el.textContent = message; - el.style.color = color; - el.style.display = 'inline'; - setTimeout(() => { el.style.display = 'none'; }, 4000); + const type = color === 'green' ? 'success' : color === 'red' ? 'error' : 'info'; + showToast(message, type, type === 'error' ? 6000 : 4000); } // Save signature to Thunderbird identity (header + footer) @@ -979,18 +1251,19 @@ document.getElementById('sig-import-file').addEventListener('change', async (e) // Signature sync - "Aktualisieren" (pull + push) document.getElementById('sig-sync-refresh').addEventListener('click', async () => { - const statusEl = document.getElementById('sig-sync-status'); - statusEl.textContent = 'Synchronisiere...'; - statusEl.style.color = '#777'; - statusEl.style.display = 'inline'; + if (!checkOnline()) return; + const btn = document.getElementById('sig-sync-refresh'); + const origText = btn.textContent; + btn.disabled = true; + btn.innerHTML = ' Synchronisiere...'; try { // Pull first (gets footer + headers) const pullResult = await browser.runtime.sendMessage({ action: 'pullSignatures' }); if (!pullResult?.success) { - statusEl.textContent = pullResult?.error || 'Fehler'; - statusEl.style.color = 'red'; - setTimeout(() => { statusEl.style.display = 'none'; }, 4000); + showToast(pullResult?.error || 'Fehler', 'error', 6000); + btn.disabled = false; + btn.textContent = origText; return; } @@ -1005,14 +1278,12 @@ document.getElementById('sig-sync-refresh').addEventListener('click', async () = updateSigSyncIndicator(); sigIdentitySelect.dispatchEvent(new Event('change')); - const msg = `${pullResult.updated || 0} geladen, ${pushResult?.pushed || 0} hochgeladen`; - statusEl.textContent = msg; - statusEl.style.color = 'green'; + showToast(`${pullResult.updated || 0} geladen, ${pushResult?.pushed || 0} hochgeladen`, 'success'); } catch (err) { - statusEl.textContent = 'Fehler: ' + err.message; - statusEl.style.color = 'red'; + showToast('Fehler: ' + err.message, 'error', 6000); } - setTimeout(() => { statusEl.style.display = 'none'; }, 4000); + btn.disabled = false; + btn.textContent = origText; }); // ── Footer Editor ── @@ -1038,11 +1309,8 @@ document.getElementById('footer-toggle').addEventListener('click', async functio }); function showFooterStatus(message, color) { - const el = document.getElementById('footer-status'); - el.textContent = message; - el.style.color = color; - el.style.display = 'inline'; - setTimeout(() => { el.style.display = 'none'; }, 4000); + const type = color === 'green' ? 'success' : color === 'red' ? 'error' : 'info'; + showToast(message, type, type === 'error' ? 6000 : 4000); } // Load footer from server @@ -1096,6 +1364,7 @@ async function loadSyncConfig() { document.getElementById('sync-token').value = config.token || ''; document.getElementById('sync-author-name').value = config.authorName || ''; document.getElementById('sync-author-email').value = config.authorEmail || ''; + cachedAuthorEmail = config.authorEmail || ''; if (config.baseUrl && config.token) { updateSyncStatus('connected', 'Verbunden'); } @@ -1108,10 +1377,105 @@ async function loadSyncConfig() { deptSelect.appendChild(opt); } loadDepartments(); + + // Auto-detect department + author from server config + if (config.baseUrl && config.token) { + tryAutoDetect(config); + } } } catch (_) {} } +async function tryAutoDetect(currentConfig) { + try { + const result = await browser.runtime.sendMessage({ action: 'autoDetect' }); + if (!result?.success || !result.config) return; + + // result.config = { "info@hotel.de": "Rezeption", ... } + const deptMap = result.config; + + // Get all Thunderbird identities + const accounts = await browser.accounts.list(); + const tbEmails = []; + for (const account of accounts) { + for (const identity of account.identities) { + tbEmails.push(identity.email.toLowerCase()); + } + } + + // Find matching department email + let detectedDept = null; + let personalEmail = null; + const matchedEmails = []; + const unmatchedEmails = []; + + for (const email of tbEmails) { + if (deptMap[email]) { + detectedDept = deptMap[email]; + matchedEmails.push(email); + } else { + unmatchedEmails.push(email); + } + } + + // The personal email is the one NOT in the department map + if (unmatchedEmails.length > 0) { + personalEmail = unmatchedEmails[0]; + } + + let changed = false; + + // Auto-set department if not already set + if (detectedDept && !currentConfig.department) { + const deptSelect = document.getElementById('sync-department'); + // Add option if not exists + let found = false; + for (const opt of deptSelect.options) { + if (opt.value === detectedDept) { opt.selected = true; found = true; break; } + } + if (!found) { + const opt = document.createElement('option'); + opt.value = detectedDept; + opt.textContent = detectedDept; + opt.selected = true; + deptSelect.appendChild(opt); + } + currentConfig.department = detectedDept; + changed = true; + } + + // Auto-set author email if not already set + if (personalEmail && !currentConfig.authorEmail) { + document.getElementById('sync-author-email').value = personalEmail; + currentConfig.authorEmail = personalEmail; + cachedAuthorEmail = personalEmail; + changed = true; + } + + // Auto-set author name from TB identity if not already set + if (personalEmail && !currentConfig.authorName) { + for (const account of accounts) { + for (const identity of account.identities) { + if (identity.email.toLowerCase() === personalEmail && identity.name) { + document.getElementById('sync-author-name').value = identity.name; + currentConfig.authorName = identity.name; + changed = true; + break; + } + } + if (changed && currentConfig.authorName) break; + } + } + + if (changed) { + await browser.storage.local.set({ [SYNC_CONFIG_KEY]: currentConfig }); + showToast('Einstellungen automatisch erkannt.', 'info'); + } + } catch (err) { + console.log('Auto-detect failed (optional):', err.message); + } +} + function getSyncConfigFromForm() { return { baseUrl: document.getElementById('sync-url').value.replace(/\/+$/, ''), @@ -1132,11 +1496,8 @@ function updateSyncStatus(type, message) { } function showSyncActionStatus(elId, message, color) { - const el = document.getElementById(elId); - el.textContent = message; - el.style.color = color; - el.style.display = 'inline'; - setTimeout(() => { el.style.display = 'none'; }, 4000); + const type = color === 'green' ? 'success' : color === 'red' ? 'error' : 'info'; + showToast(message, type, type === 'error' ? 6000 : 4000); } function appendSyncLog(message) { @@ -1232,23 +1593,30 @@ for (const id of ['sync-author-name', 'sync-author-email']) { document.getElementById('refresh-departments').addEventListener('click', loadDepartments); document.getElementById('test-sync-connection').addEventListener('click', async () => { - showSyncActionStatus('sync-action-status', 'Teste...', '#777'); + if (!checkOnline()) return; + const btn = document.getElementById('test-sync-connection'); + const origText = btn.textContent; + btn.disabled = true; + btn.innerHTML = ' Teste...'; + try { const result = await browser.runtime.sendMessage({ action: 'testConnection' }); if (result && result.success) { updateSyncStatus('connected', 'Verbunden'); - showSyncActionStatus('sync-action-status', 'Verbindung erfolgreich!', 'green'); + showToast('Verbindung erfolgreich!', 'success'); appendSyncLog('Verbindungstest erfolgreich.'); loadDepartments(); } else { updateSyncStatus('error', 'Verbindung fehlgeschlagen'); - showSyncActionStatus('sync-action-status', result?.error || 'Fehler', 'red'); + showToast(result?.error || 'Fehler', 'error', 6000); appendSyncLog('Verbindungstest fehlgeschlagen: ' + (result?.error || 'Unbekannt')); } } catch (err) { updateSyncStatus('error', 'Verbindung fehlgeschlagen'); - showSyncActionStatus('sync-action-status', 'Sync nicht verfügbar.', 'red'); + showToast('Sync nicht verfügbar.', 'error', 6000); } + btn.disabled = false; + btn.textContent = origText; }); // ── Init ──