// templates_options/templates_options.js const TEMPLATE_STORAGE_KEY = 'message_templates'; // DOM elements const templateForm = document.getElementById('template-form'); const templateList = document.getElementById('templates-list'); const noTemplatesMessage = document.getElementById('no-templates'); const saveButton = document.getElementById('save-button'); const cancelButton = document.getElementById('cancel-edit'); const formLegend = document.getElementById('form-legend'); const editorArea = document.getElementById('editor-area'); // ── System Font Detection ── const FONT_CANDIDATES = [ 'Arial', 'Arial Black', 'Arial Narrow', 'Book Antiqua', 'Bookman Old Style', 'Calibri', 'Cambria', 'Candara', 'Century Gothic', 'Comic Sans MS', 'Consolas', 'Constantia', 'Corbel', 'Courier New', 'DejaVu Sans', 'DejaVu Sans Mono', 'DejaVu Serif', 'Droid Sans', 'Droid Serif', 'Franklin Gothic Medium', 'Garamond', 'Georgia', 'Gill Sans', 'Helvetica', 'Helvetica Neue', 'Impact', 'Liberation Mono', 'Liberation Sans', 'Liberation Serif', 'Lucida Console', 'Lucida Sans Unicode', 'Microsoft Sans Serif', 'Monaco', 'Noto Sans', 'Noto Serif', 'Open Sans', 'Palatino Linotype', 'Roboto', 'Segoe UI', 'Source Sans Pro', 'Tahoma', 'Times New Roman', 'Trebuchet MS', 'Ubuntu', 'Verdana' ]; // Build list of available system fonts const availableFonts = FONT_CANDIDATES.filter(f => document.fonts.check(`12px "${f}"`)); const fontInput = document.getElementById('font-input'); const fontDropdown = document.getElementById('font-dropdown'); function renderFontDropdown(filter) { fontDropdown.innerHTML = ''; const q = (filter || '').toLowerCase(); const matches = q ? availableFonts.filter(f => f.toLowerCase().includes(q)) : availableFonts; for (const font of matches) { const div = document.createElement('div'); div.className = 'font-option'; div.textContent = font; div.style.fontFamily = font; div.addEventListener('mousedown', (e) => { e.preventDefault(); // prevent blur before click fires fontInput.value = ''; fontDropdown.classList.remove('open'); editorArea.focus(); document.execCommand('fontName', false, font); }); fontDropdown.appendChild(div); } fontDropdown.classList.toggle('open', matches.length > 0); } fontInput.addEventListener('focus', () => renderFontDropdown(fontInput.value)); fontInput.addEventListener('input', () => renderFontDropdown(fontInput.value)); fontInput.addEventListener('blur', () => { // Small delay so mousedown on option fires first setTimeout(() => fontDropdown.classList.remove('open'), 150); }); // Apply custom font on Enter fontInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); const font = fontInput.value.trim(); if (!font) return; fontInput.value = ''; fontDropdown.classList.remove('open'); editorArea.focus(); document.execCommand('fontName', false, font); } }); // ── Contenteditable Editor ── function getEditorContent() { const html = editorArea.innerHTML; return (!html || html === '
') ? '' : html; } function setEditorContent(html) { editorArea.innerHTML = html || ''; } // ── Toolbar Commands ── document.querySelectorAll('.editor-toolbar button[data-cmd]').forEach(btn => { btn.addEventListener('click', () => { editorArea.focus(); const cmd = btn.dataset.cmd; let val = btn.dataset.val || null; if (val === 'ask') { if (cmd === 'createLink') { val = prompt('Link-URL eingeben:', 'https://'); if (!val) return; } else if (cmd === 'foreColor' || cmd === 'hiliteColor') { const colorInput = document.createElement('input'); colorInput.type = 'color'; colorInput.value = cmd === 'foreColor' ? '#000000' : '#ffff00'; colorInput.addEventListener('input', () => { editorArea.focus(); document.execCommand(cmd, false, colorInput.value); }); colorInput.click(); return; } } document.execCommand(cmd, false, val); }); }); // Font size dropdown document.querySelector('.editor-toolbar select[data-cmd="fontSize"]').addEventListener('change', function() { if (!this.value) return; editorArea.focus(); document.execCommand('fontSize', false, this.value); this.value = ''; }); // ── Tab Navigation ── document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); btn.classList.add('active'); document.getElementById('tab-' + btn.dataset.tab).classList.add('active'); }); }); // ── Template Storage ── async function getTemplates() { try { const result = await browser.storage.local.get(TEMPLATE_STORAGE_KEY); return result[TEMPLATE_STORAGE_KEY] || []; } catch (error) { console.error("Error retrieving templates:", error); return []; } } async function saveTemplates(templates) { try { await browser.storage.local.set({ [TEMPLATE_STORAGE_KEY]: templates }); } catch (error) { console.error("Error saving templates:", error); } } // ── Sync Status Tracking ── let tplSyncedHashes = {}; // template id -> hash of content after last pull/push let sigSyncedHashes = {}; // email -> hash of signature after last pull/push const HASH_STORAGE_KEY = 'sync_hashes'; function simpleHash(str) { let h = 0; for (let i = 0; i < str.length; i++) { h = ((h << 5) - h + str.charCodeAt(i)) | 0; } return h; } async function loadSyncHashes() { try { const result = await browser.storage.local.get(HASH_STORAGE_KEY); const data = result[HASH_STORAGE_KEY] || {}; tplSyncedHashes = data.tpl || {}; sigSyncedHashes = data.sig || {}; } catch (_) {} } async function saveSyncHashes() { await browser.storage.local.set({ [HASH_STORAGE_KEY]: { tpl: tplSyncedHashes, sig: sigSyncedHashes } }); } function storeTplHashes(templates) { tplSyncedHashes = {}; for (const t of templates) { tplSyncedHashes[t.id] = simpleHash(t.content || ''); } saveSyncHashes(); } function getTplSyncClass(template) { if (tplSyncedHashes[template.id] === undefined) return 'unknown'; return simpleHash(template.content || '') === tplSyncedHashes[template.id] ? 'in-sync' : 'out-of-sync'; } function updateTplSyncIndicator() { // Update the global indicator based on all templates const el = document.getElementById('tpl-sync-indicator'); if (!el) return; const dot = el.querySelector('.sync-dot'); const label = el.querySelector('span:last-child'); if (Object.keys(tplSyncedHashes).length === 0) { dot.className = 'sync-dot unknown'; label.textContent = 'Sync-Status unbekannt'; return; } getTemplates().then(templates => { const outOfSync = templates.filter(t => getTplSyncClass(t) === 'out-of-sync').length; const unknown = templates.filter(t => getTplSyncClass(t) === 'unknown').length; if (outOfSync > 0) { dot.className = 'sync-dot out-of-sync'; label.textContent = `${outOfSync} Vorlage(n) nicht hochgeladen`; } else if (unknown > 0) { dot.className = 'sync-dot unknown'; label.textContent = `${unknown} Vorlage(n) unbekannt`; } else { dot.className = 'sync-dot in-sync'; label.textContent = 'Alle Vorlagen synchron'; } }); } function updateSigSyncIndicator() { const el = document.getElementById('sig-sync-indicator'); if (!el) return; const dot = el.querySelector('.sync-dot'); const label = el.querySelector('span:last-child'); if (Object.keys(sigSyncedHashes).length === 0) { dot.className = 'sync-dot unknown'; label.textContent = 'Sync-Status unbekannt'; return; } let outOfSync = 0; for (const identity of allIdentities) { const email = identity.email.toLowerCase(); if (sigSyncedHashes[email] !== undefined) { if (simpleHash(identity.signature || '') !== sigSyncedHashes[email]) { outOfSync++; } } } if (outOfSync > 0) { dot.className = 'sync-dot out-of-sync'; label.textContent = `${outOfSync} Signatur(en) nicht hochgeladen`; } else { dot.className = 'sync-dot in-sync'; label.textContent = 'Alle Signaturen synchron'; } } // ── Template List Rendering ── function renderTemplates(templates) { templateList.innerHTML = ''; if (templates.length === 0) { templateList.appendChild(noTemplatesMessage); noTemplatesMessage.style.display = 'block'; return; } noTemplatesMessage.style.display = 'none'; templates.forEach(template => { const item = document.createElement('div'); item.className = 'template-item'; const syncClass = getTplSyncClass(template); const folderBadge = template.folder ? `[${template.folder}]` : ''; const pushBtn = syncClass === 'out-of-sync' ? `` : ''; const pullBtn = template.folder ? `` : ''; item.innerHTML = ` ${template.name}${folderBadge}
${pullBtn}${pushBtn}
`; templateList.appendChild(item); }); document.querySelectorAll('.edit-btn').forEach(button => { button.addEventListener('click', handleEdit); }); document.querySelectorAll('.delete-btn').forEach(button => { button.addEventListener('click', handleDelete); }); document.querySelectorAll('.push-btn').forEach(button => { button.addEventListener('click', handlePushSingle); }); document.querySelectorAll('.pull-btn').forEach(button => { button.addEventListener('click', handlePullSingle); }); } // ── Form Submit (Add/Edit) ── templateForm.addEventListener('submit', async (e) => { e.preventDefault(); const id = document.getElementById('template-id').value; const name = document.getElementById('template-name').value; const content = getEditorContent(); if (!content.trim()) { alert('Bitte Inhalt eingeben.'); return; } let templates = await getTemplates(); if (id) { const index = templates.findIndex(t => t.id === id); if (index > -1) { // Preserve folder info from synced templates const folder = templates[index].folder; const remotePath = templates[index].remotePath; templates[index] = { id, name, content, folder, remotePath }; } } else { const newId = Date.now().toString(); templates.push({ id: newId, name, content }); } await saveTemplates(templates); renderTemplates(templates); resetForm(); updateTplSyncIndicator(); }); // ── Edit / Delete ── async function handleDelete(e) { if (!confirm('Diese Vorlage wirklich löschen?')) return; const idToDelete = e.target.dataset.id; let templates = await getTemplates(); templates = templates.filter(t => t.id !== idToDelete); await saveTemplates(templates); renderTemplates(templates); updateTplSyncIndicator(); } async function handlePullSingle(e) { const id = e.target.dataset.id; const templates = await getTemplates(); const template = templates.find(t => t.id === id); if (!template || !template.remotePath) return; e.target.textContent = '...'; e.target.disabled = true; try { const result = await browser.runtime.sendMessage({ action: 'pullSingleTemplate', remotePath: template.remotePath }); if (result && result.success) { // Update local template with pulled content template.content = result.content; await saveTemplates(templates); tplSyncedHashes[id] = simpleHash(result.content || ''); saveSyncHashes(); renderTemplates(templates); updateTplSyncIndicator(); } else { alert('Fehler: ' + (result?.error || 'Unbekannt')); e.target.textContent = '\u2193'; e.target.disabled = false; } } catch (err) { alert('Fehler: ' + err.message); e.target.textContent = '\u2193'; e.target.disabled = false; } } async function handlePushSingle(e) { const id = e.target.dataset.id; const templates = await getTemplates(); const template = templates.find(t => t.id === id); if (!template) return; e.target.textContent = '...'; e.target.disabled = true; try { const result = await browser.runtime.sendMessage({ action: 'pushSingleTemplate', templateId: id }); if (result && result.success) { tplSyncedHashes[id] = simpleHash(template.content || ''); saveSyncHashes(); renderTemplates(templates); updateTplSyncIndicator(); } else { alert('Fehler: ' + (result?.error || 'Unbekannt')); e.target.textContent = '\u2191'; e.target.disabled = false; } } catch (err) { alert('Fehler: ' + err.message); e.target.textContent = '\u2191'; e.target.disabled = false; } } async function handleEdit(e) { const idToEdit = e.target.dataset.id; const templates = await getTemplates(); const template = templates.find(t => t.id === idToEdit); if (template) { document.getElementById('template-id').value = template.id; document.getElementById('template-name').value = template.name; setEditorContent(template.content); formLegend.textContent = 'Vorlage bearbeiten'; saveButton.textContent = 'Aktualisieren'; cancelButton.style.display = 'inline'; window.scrollTo(0, 0); } } function resetForm() { templateForm.reset(); document.getElementById('template-id').value = ''; setEditorContent(''); formLegend.textContent = 'Neue Vorlage erstellen'; saveButton.textContent = 'Speichern'; cancelButton.style.display = 'none'; } cancelButton.addEventListener('click', resetForm); // ── Bulk Actions ── document.getElementById('select-all-button').addEventListener('click', () => { const checkboxes = document.querySelectorAll('.template-checkbox'); const allChecked = Array.from(checkboxes).every(cb => cb.checked); checkboxes.forEach(cb => cb.checked = !allChecked); }); document.getElementById('delete-selected-button').addEventListener('click', async () => { const checked = document.querySelectorAll('.template-checkbox:checked'); if (checked.length === 0) return; if (!confirm(`${checked.length} Template(s) wirklich löschen?`)) 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); }); // ── HTML File Import ── document.getElementById('import-button').addEventListener('click', async () => { const fileInput = document.getElementById('import-files'); const statusEl = document.getElementById('import-status'); const files = fileInput.files; if (files.length === 0) { statusEl.textContent = 'Bitte Dateien auswählen!'; statusEl.style.color = 'red'; statusEl.style.display = 'inline'; return; } let templates = await getTemplates(); let importCount = 0; for (const file of files) { const content = await file.text(); const name = file.name.replace(/\.html?$/i, ''); let body = content; const bodyMatch = content.match(/]*>([\s\S]*)<\/body>/i); if (bodyMatch) { body = bodyMatch[1].trim(); } const existingIndex = templates.findIndex( t => t.name.toLowerCase() === name.toLowerCase() ); if (existingIndex > -1) { templates[existingIndex].content = body; } else { templates.push({ id: Date.now().toString() + importCount, name: name, content: body }); } importCount++; } await saveTemplates(templates); renderTemplates(templates); updateTplSyncIndicator(); statusEl.textContent = `${importCount} Vorlage(n) importiert!`; statusEl.style.color = 'green'; statusEl.style.display = 'inline'; fileInput.value = ''; setTimeout(() => { statusEl.style.display = 'none'; }, 3000); }); // ── Signaturen ── const sigIdentitySelect = document.getElementById('sig-identity-select'); const sigEditorArea = document.getElementById('sig-editor-area'); const sigPersonalToggle = document.getElementById('sig-personal-toggle'); const sigPersonalInfo = document.getElementById('sig-personal-info'); let allIdentities = []; // Persistent setting: which identities use personal signatures const SIG_PERSONAL_KEY = 'sig_personal_emails'; async function getPersonalEmails() { const result = await browser.storage.local.get(SIG_PERSONAL_KEY); return result[SIG_PERSONAL_KEY] || []; } async function setPersonalEmail(email, enabled) { const list = await getPersonalEmails(); const set = new Set(list); if (enabled) set.add(email.toLowerCase()); else set.delete(email.toLowerCase()); await browser.storage.local.set({ [SIG_PERSONAL_KEY]: [...set] }); } async function loadIdentities() { allIdentities = []; sigIdentitySelect.innerHTML = ''; const accounts = await browser.accounts.list(); for (const account of accounts) { const identities = await browser.identities.list(account.id); for (const identity of identities) { const label = identity.name ? `${identity.name} <${identity.email}>` : identity.email; allIdentities.push({ id: identity.id, email: identity.email, label: label, accountName: account.name, signature: identity.signature || '', signatureIsPlainText: identity.signatureIsPlainText || false }); const opt = document.createElement('option'); opt.value = identity.id; opt.textContent = `${label} (${account.name})`; sigIdentitySelect.appendChild(opt); } } } // Load signature into editor when identity is selected sigIdentitySelect.addEventListener('change', async () => { const identity = allIdentities.find(i => i.id === sigIdentitySelect.value); if (identity) { sigEditorArea.innerHTML = identity.signature || ''; const personalEmails = await getPersonalEmails(); const isPersonal = personalEmails.includes(identity.email.toLowerCase()); sigPersonalToggle.checked = isPersonal; updatePersonalInfo(identity.email, isPersonal); } else { sigEditorArea.innerHTML = ''; sigPersonalToggle.checked = false; sigPersonalInfo.textContent = ''; } }); function updatePersonalInfo(email, isPersonal) { const configName = document.getElementById('sync-author-name')?.value || ''; if (isPersonal && configName) { sigPersonalInfo.textContent = `(${email} → persönlich für ${configName})`; } else if (isPersonal) { sigPersonalInfo.textContent = '(Name in Sync-Einstellungen eintragen!)'; } else { sigPersonalInfo.textContent = '(gemeinsame Signatur für alle)'; } } // Toggle personal signature sigPersonalToggle.addEventListener('change', async () => { const identity = allIdentities.find(i => i.id === sigIdentitySelect.value); if (!identity) return; await setPersonalEmail(identity.email, sigPersonalToggle.checked); updatePersonalInfo(identity.email, sigPersonalToggle.checked); }); // Signature toolbar commands document.querySelectorAll('#sig-toolbar button[data-cmd]').forEach(btn => { btn.addEventListener('click', () => { sigEditorArea.focus(); const cmd = btn.dataset.cmd; let val = btn.dataset.val || null; if (val === 'ask') { if (cmd === 'createLink') { val = prompt('Link-URL eingeben:', 'https://'); if (!val) return; } else if (cmd === 'foreColor' || cmd === 'hiliteColor') { const colorInput = document.createElement('input'); colorInput.type = 'color'; colorInput.value = cmd === 'foreColor' ? '#000000' : '#ffff00'; colorInput.addEventListener('input', () => { sigEditorArea.focus(); document.execCommand(cmd, false, colorInput.value); }); colorInput.click(); return; } } document.execCommand(cmd, false, val); }); }); 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); } // Save signature to Thunderbird identity document.getElementById('sig-save-button').addEventListener('click', async () => { const identityId = sigIdentitySelect.value; if (!identityId) { showSigStatus('Bitte Identität auswählen.', 'red'); return; } const html = sigEditorArea.innerHTML; await browser.identities.update(identityId, { signature: html, signatureIsPlainText: false }); // Update local cache const identity = allIdentities.find(i => i.id === identityId); if (identity) identity.signature = html; updateSigSyncIndicator(); showSigStatus('Signatur gespeichert!', 'green'); }); // Import signature from HTML file document.getElementById('sig-import-file-btn').addEventListener('click', () => { document.getElementById('sig-import-file').click(); }); document.getElementById('sig-import-file').addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file) return; const content = await file.text(); let body = content; const bodyMatch = content.match(/]*>([\s\S]*)<\/body>/i); if (bodyMatch) body = bodyMatch[1].trim(); sigEditorArea.innerHTML = body; e.target.value = ''; showSigStatus('Datei geladen — jetzt "Signatur speichern" klicken.', '#555'); }); // Signature sync - pull document.getElementById('sig-sync-pull').addEventListener('click', async () => { const statusEl = document.getElementById('sig-sync-status'); statusEl.textContent = 'Lade...'; statusEl.style.color = '#777'; statusEl.style.display = 'inline'; try { const result = await browser.runtime.sendMessage({ action: 'pullSignatures' }); if (result && result.success) { statusEl.textContent = `${result.updated || 0} Signatur(en) geladen!`; statusEl.style.color = 'green'; await loadIdentities(); for (const id of allIdentities) { sigSyncedHashes[id.email.toLowerCase()] = simpleHash(id.signature || ''); } saveSyncHashes(); updateSigSyncIndicator(); sigIdentitySelect.dispatchEvent(new Event('change')); } else { statusEl.textContent = result?.error || 'Fehler'; statusEl.style.color = 'red'; } } catch (err) { statusEl.textContent = 'Fehler: ' + err.message; statusEl.style.color = 'red'; } setTimeout(() => { statusEl.style.display = 'none'; }, 4000); }); // Signature sync - push document.getElementById('sig-sync-push').addEventListener('click', async () => { const statusEl = document.getElementById('sig-sync-status'); statusEl.textContent = 'Lade hoch...'; statusEl.style.color = '#777'; statusEl.style.display = 'inline'; try { const result = await browser.runtime.sendMessage({ action: 'pushSignatures' }); if (result && result.success) { statusEl.textContent = `${result.pushed || 0} Signatur(en) hochgeladen!`; statusEl.style.color = 'green'; for (const id of allIdentities) { sigSyncedHashes[id.email.toLowerCase()] = simpleHash(id.signature || ''); } saveSyncHashes(); updateSigSyncIndicator(); } else { statusEl.textContent = result?.error || 'Fehler'; statusEl.style.color = 'red'; } } catch (err) { statusEl.textContent = 'Fehler: ' + err.message; statusEl.style.color = 'red'; } setTimeout(() => { statusEl.style.display = 'none'; }, 4000); }); // ── Sync Settings UI ── const SYNC_CONFIG_KEY = 'gitea_config'; async function loadSyncConfig() { try { const result = await browser.storage.local.get(SYNC_CONFIG_KEY); const config = result[SYNC_CONFIG_KEY]; if (config) { document.getElementById('sync-url').value = config.baseUrl || ''; document.getElementById('sync-owner').value = config.owner || ''; document.getElementById('sync-repo').value = config.repo || ''; document.getElementById('sync-branch').value = config.branch || 'main'; document.getElementById('sync-token').value = config.token || ''; document.getElementById('sync-author-name').value = config.authorName || ''; document.getElementById('sync-author-email').value = config.authorEmail || ''; if (config.baseUrl && config.token) { updateSyncStatus('connected', 'Verbunden'); } // Load department after a short delay to ensure config is available if (config.department) { const deptSelect = document.getElementById('sync-department'); // Add saved department as option in case list hasn't loaded yet const opt = document.createElement('option'); opt.value = config.department; opt.textContent = config.department; opt.selected = true; deptSelect.appendChild(opt); } // Try to load department list loadDepartments(); } } catch (_) {} } function getSyncConfigFromForm() { return { baseUrl: document.getElementById('sync-url').value.replace(/\/+$/, ''), owner: document.getElementById('sync-owner').value.trim(), repo: document.getElementById('sync-repo').value.trim(), branch: document.getElementById('sync-branch').value.trim() || 'main', token: document.getElementById('sync-token').value.trim(), authorName: document.getElementById('sync-author-name').value.trim(), authorEmail: document.getElementById('sync-author-email').value.trim(), department: document.getElementById('sync-department').value }; } function updateSyncStatus(type, message) { const bar = document.getElementById('sync-status-bar'); bar.className = 'sync-status ' + type; bar.textContent = 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); } function appendSyncLog(message) { const log = document.getElementById('sync-log'); log.style.display = 'block'; const time = new Date().toLocaleTimeString('de-DE'); log.textContent += `[${time}] ${message}\n`; log.scrollTop = log.scrollHeight; } async function loadDepartments() { try { const result = await browser.runtime.sendMessage({ action: 'listDepartments' }); if (result && result.success) { const select = document.getElementById('sync-department'); const currentVal = select.value; select.innerHTML = ''; for (const dept of result.departments) { const opt = document.createElement('option'); opt.value = dept; opt.textContent = dept; if (dept === currentVal) opt.selected = true; select.appendChild(opt); } } } catch (_) {} } // Save sync config (connection + user info) document.getElementById('save-sync-config').addEventListener('click', async () => { const config = getSyncConfigFromForm(); if (!config.baseUrl || !config.owner || !config.repo || !config.token) { showSyncActionStatus('sync-action-status', 'Bitte alle Verbindungsfelder ausfüllen.', 'red'); return; } try { const origin = new URL(config.baseUrl).origin + '/*'; const granted = await browser.permissions.request({ origins: [origin] }); if (!granted) { showSyncActionStatus('sync-action-status', 'Berechtigung für Server abgelehnt.', 'red'); return; } } catch (err) { showSyncActionStatus('sync-action-status', 'Ungültige URL.', 'red'); return; } await browser.storage.local.set({ [SYNC_CONFIG_KEY]: config }); updateSyncStatus('connected', 'Verbunden'); showSyncActionStatus('sync-action-status', 'Gespeichert!', 'green'); appendSyncLog('Verbindung konfiguriert.'); // Load departments after saving loadDepartments(); }); // Save department selection immediately when changed document.getElementById('sync-department').addEventListener('change', async () => { const result = await browser.storage.local.get(SYNC_CONFIG_KEY); const config = result[SYNC_CONFIG_KEY] || {}; config.department = document.getElementById('sync-department').value; await browser.storage.local.set({ [SYNC_CONFIG_KEY]: config }); // Auto-pull templates for the new department if (config.department) { try { const pullResult = await browser.runtime.sendMessage({ action: 'pullTemplates' }); if (pullResult && pullResult.success) { const templates = await getTemplates(); renderTemplates(templates); storeTplHashes(templates); updateTplSyncIndicator(); appendSyncLog(`Abteilung gewechselt: ${config.department} — ${pullResult.updated || 0} Vorlage(n) geladen.`); } } catch (_) {} } }); // Save author name/email immediately when changed for (const id of ['sync-author-name', 'sync-author-email']) { document.getElementById(id).addEventListener('change', async () => { const result = await browser.storage.local.get(SYNC_CONFIG_KEY); const config = result[SYNC_CONFIG_KEY] || {}; config.authorName = document.getElementById('sync-author-name').value.trim(); config.authorEmail = document.getElementById('sync-author-email').value.trim(); await browser.storage.local.set({ [SYNC_CONFIG_KEY]: config }); }); } // Refresh departments button document.getElementById('refresh-departments').addEventListener('click', loadDepartments); // Test connection document.getElementById('test-sync-connection').addEventListener('click', async () => { showSyncActionStatus('sync-action-status', 'Teste...', '#777'); try { const result = await browser.runtime.sendMessage({ action: 'testConnection' }); if (result && result.success) { updateSyncStatus('connected', 'Verbunden'); showSyncActionStatus('sync-action-status', 'Verbindung erfolgreich!', 'green'); appendSyncLog('Verbindungstest erfolgreich.'); loadDepartments(); } else { updateSyncStatus('error', 'Verbindung fehlgeschlagen'); showSyncActionStatus('sync-action-status', result?.error || 'Fehler', 'red'); appendSyncLog('Verbindungstest fehlgeschlagen: ' + (result?.error || 'Unbekannt')); } } catch (err) { updateSyncStatus('error', 'Verbindung fehlgeschlagen'); showSyncActionStatus('sync-action-status', 'Sync nicht verfügbar.', 'red'); } }); // Pull (Vorlagen vom Server laden) document.getElementById('sync-pull-button').addEventListener('click', async () => { showSyncActionStatus('sync-sync-status', 'Lade Vorlagen...', '#777'); appendSyncLog('Vorlagen werden vom Server geladen...'); try { const result = await browser.runtime.sendMessage({ action: 'pullTemplates' }); if (result && result.success) { showSyncActionStatus('sync-sync-status', `${result.updated || 0} Vorlage(n) geladen!`, 'green'); appendSyncLog(`Pull abgeschlossen: ${result.updated || 0} Vorlage(n).`); const templates = await getTemplates(); renderTemplates(templates); storeTplHashes(templates); updateTplSyncIndicator(); } else { showSyncActionStatus('sync-sync-status', result?.error || 'Fehler', 'red'); appendSyncLog('Pull fehlgeschlagen: ' + (result?.error || 'Unbekannt')); } } catch (err) { showSyncActionStatus('sync-sync-status', 'Fehler: ' + err.message, 'red'); appendSyncLog('Fehler: ' + err.message); } }); // Push (Änderungen hochladen — nur per Knopfdruck) document.getElementById('sync-push-button').addEventListener('click', async () => { showSyncActionStatus('sync-sync-status', 'Lade hoch...', '#777'); appendSyncLog('Änderungen werden hochgeladen...'); try { const result = await browser.runtime.sendMessage({ action: 'pushTemplates' }); if (result && result.success) { showSyncActionStatus('sync-sync-status', `${result.pushed || 0} Änderung(en) hochgeladen!`, 'green'); appendSyncLog(`Push abgeschlossen: ${result.pushed || 0} Änderung(en).`); const templates = await getTemplates(); renderTemplates(templates); storeTplHashes(templates); updateTplSyncIndicator(); } else { showSyncActionStatus('sync-sync-status', result?.error || 'Fehler', 'red'); appendSyncLog('Push fehlgeschlagen: ' + (result?.error || 'Unbekannt')); } } catch (err) { showSyncActionStatus('sync-sync-status', 'Fehler: ' + err.message, 'red'); appendSyncLog('Fehler: ' + err.message); } }); // ── Init ── window.addEventListener('load', async () => { await loadSyncHashes(); const templates = await getTemplates(); renderTemplates(templates); updateTplSyncIndicator(); loadSyncConfig(); loadIdentities(); });