// renderer/settings-extra.js // Ergänzungen der Einstellungen: App-Einstellungen (Identität, Uploads, Design, System), // ein gemeinsames "Speichern" für alle Bereiche, Warnung bei ungespeicherten Änderungen, // mehrere Gitea-Server, Token anzeigen/entfernen und Diagnose-Hilfen. // Wird nach renderer.js und features.js geladen und nutzt deren globale Helfer. (function () { 'use strict'; let savedPrefs = null; /* ---------- Design ---------- */ const systemDark = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null; function applyTheme(theme) { const effective = theme === 'system' ? (systemDark && !systemDark.matches ? 'light' : 'dark') : theme; document.documentElement.dataset.theme = effective === 'light' ? 'light' : 'dark'; } if (systemDark) { systemDark.addEventListener('change', () => { if (savedPrefs && savedPrefs.theme === 'system') applyTheme('system'); }); } /* ---------- Laden ---------- */ async function loadPrefs() { const res = await window.electronAPI.prefsGet(); if (!res || !res.ok) return null; savedPrefs = res.prefs; applyTheme(savedPrefs.theme); return savedPrefs; } function fillPrefFields(p) { if (!p) return; const set = (id, v) => { const el = $(id); if (!el) return; if (el.type === 'checkbox') el.checked = !!v; else el.value = v; }; set('prefGitName', p.gitName); set('prefGitEmail', p.gitEmail); set('prefTheme', p.theme); set('prefZoom', String(p.zoom)); if ($('prefZoom') && !$('prefZoom').value) $('prefZoom').value = '100'; set('prefAutoUpdate', p.autoUpdateCheck); set('prefShowSplash', p.showSplash); set('prefCloseAction', p.closeAction); set('prefCloneDir', p.defaultCloneDir); set('prefSecretScan', p.uploadSecretScan); set('prefBlockSecrets', p.uploadBlockSecrets); set('prefUploadPreview', p.uploadPreview); set('prefNotifyTransfers', p.notifyTransfers); set('prefUploadExcludes', p.uploadExtraExcludes); } function collectPrefs() { const val = (id) => ($(id) ? $(id).value : undefined); const chk = (id) => ($(id) ? $(id).checked : undefined); return { gitName: val('prefGitName'), gitEmail: val('prefGitEmail'), theme: val('prefTheme'), zoom: Number(val('prefZoom')), autoUpdateCheck: chk('prefAutoUpdate'), showSplash: chk('prefShowSplash'), closeAction: val('prefCloseAction'), defaultCloneDir: val('prefCloneDir'), uploadSecretScan: chk('prefSecretScan'), uploadBlockSecrets: chk('prefBlockSecrets'), uploadPreview: val('prefUploadPreview'), notifyTransfers: val('prefNotifyTransfers'), uploadExtraExcludes: val('prefUploadExcludes') }; } /* ---------- Ungespeicherte Änderungen ---------- */ let snapshot = null; function readFormState() { const state = {}; document.querySelectorAll('#settingsModal input, #settingsModal select, #settingsModal textarea').forEach(el => { if (!el.id || el.type === 'file') return; state[el.id] = el.type === 'checkbox' ? el.checked : el.value; }); return JSON.stringify(state); } async function takeSnapshot() { // Erst nachdem alle Felder befüllt sind (Zugangsdaten, App-Einstellungen, Backup) fillPrefFields(await loadPrefs()); if (window.gmBackupSettings) await window.gmBackupSettings.load(); await loadProfiles(); await refreshTransportState(); snapshot = readFormState(); } // Wird vom "Abbrechen"-Knopf in renderer.js gefragt window.settingsCanClose = async function () { if (!snapshot || readFormState() === snapshot) return true; const discard = await showActionConfirmModal({ title: 'Ungespeicherte Änderungen', message: 'Du hast Einstellungen geändert, aber nicht gespeichert. Änderungen verwerfen?', confirmText: 'Verwerfen', danger: true }); if (discard && savedPrefs) applyTheme(savedPrefs.theme); // Vorschau zurücknehmen return discard; }; /* ---------- Gemeinsames Speichern ---------- */ async function saveExtras() { const problems = []; const res = await window.electronAPI.prefsSet(collectPrefs()); if (res && res.prefs) { savedPrefs = res.prefs; applyTheme(savedPrefs.theme); fillPrefFields(savedPrefs); } if (!res || !res.ok) problems.push(res && res.error ? res.error : 'App-Einstellungen nicht gespeichert'); if (window.gmBackupSettings) { const b = await window.gmBackupSettings.save(true); if (!b || !b.ok) problems.push('Backup-Einstellung nicht gespeichert' + (b && b.error ? ': ' + b.error : '')); } snapshot = null; if (problems.length) showWarning(problems.join(' · ')); } /* ---------- Mehrere Gitea-Server ---------- */ async function loadProfiles() { const sel = $('giteaProfileSelect'); if (!sel) return; const creds = (await window.electronAPI.loadCredentials()) || {}; const profiles = Array.isArray(creds.giteaProfiles) ? creds.giteaProfiles : []; sel.textContent = ''; if (!profiles.length) { sel.append(new Option('Noch keine Profile gespeichert', '')); sel.disabled = true; } else { sel.disabled = false; for (const p of profiles) { const active = p.url === creds.giteaURL && p.token === creds.giteaToken; sel.append(new Option(`${p.name} – ${p.url}${active ? ' (aktiv)' : ''}`, p.name)); } } $('btnGiteaProfileUse').disabled = $('btnGiteaProfileDelete').disabled = !profiles.length; } async function saveProfile() { const name = $('giteaProfileName').value.trim(); const url = $('giteaURL').value.trim(); const token = $('giteaToken').value.trim(); if (!name) { showWarning('Bitte einen Namen für das Profil eingeben.'); $('giteaProfileName').focus(); return; } if (!url || !token) { showWarning('Bitte zuerst Gitea-URL und Token unter „Zugangsdaten“ eintragen.'); return; } const creds = (await window.electronAPI.loadCredentials()) || {}; const profiles = (Array.isArray(creds.giteaProfiles) ? creds.giteaProfiles : []).filter(p => p.name !== name); profiles.push({ name, url, token }); profiles.sort((a, b) => a.name.localeCompare(b.name, 'de')); await window.electronAPI.saveCredentials({ ...creds, giteaProfiles: profiles }); $('giteaProfileName').value = ''; await loadProfiles(); snapshot = readFormState(); showSuccess(`Profil „${name}“ gespeichert`); } async function useProfile() { const name = $('giteaProfileSelect').value; const creds = (await window.electronAPI.loadCredentials()) || {}; const p = (creds.giteaProfiles || []).find(x => x.name === name); if (!p) return; $('giteaURL').value = p.url; $('giteaToken').value = p.token; // Wie ein normales "Speichern": übernimmt den Server und lädt danach die Repos neu $('btnSaveSettings').click(); setTimeout(() => { if (typeof loadRepos === 'function') loadRepos(); }, 600); showInfo(`Wechsle zu „${p.name}“ …`); } async function deleteProfile() { const name = $('giteaProfileSelect').value; if (!name) return; const ok = await showActionConfirmModal({ title: 'Profil entfernen', message: `Das gespeicherte Profil „${name}“ wird entfernt. Der aktuell eingetragene Server bleibt unverändert.`, confirmText: 'Entfernen', danger: true }); if (!ok) return; const creds = (await window.electronAPI.loadCredentials()) || {}; await window.electronAPI.saveCredentials({ ...creds, giteaProfiles: (creds.giteaProfiles || []).filter(p => p.name !== name) }); await loadProfiles(); snapshot = readFormState(); } /* ---------- Token anzeigen / entfernen ---------- */ function addTokenTools(inputId) { const input = $(inputId); if (!input || input.dataset.tools) return; input.dataset.tools = '1'; const wrap = document.createElement('div'); wrap.className = 'token-field'; input.parentNode.insertBefore(wrap, input); wrap.appendChild(input); const eye = document.createElement('button'); eye.type = 'button'; eye.className = 'token-tool'; eye.title = 'Token anzeigen'; eye.textContent = '👁'; eye.onclick = () => { const show = input.type === 'password'; input.type = show ? 'text' : 'password'; eye.title = show ? 'Token verbergen' : 'Token anzeigen'; eye.classList.toggle('is-on', show); }; const clear = document.createElement('button'); clear.type = 'button'; clear.className = 'token-tool'; clear.title = 'Token entfernen (wirksam nach „Speichern“)'; clear.textContent = '✕'; clear.onclick = () => { input.value = ''; input.focus(); }; wrap.append(eye, clear); } /* ---------- Diagnose ---------- */ async function refreshTransportState() { const el = $('gitTransportState'); const btn = $('btnGitTransportClear'); if (!el) return; const res = await window.electronAPI.gitTransportState(); const blocked = (res && res.blocked) || []; if (!blocked.length) { el.textContent = '✅ Nicht gesperrt: Uploads, Löschen und Backup nutzen den schnellen Git-Weg, sobald der Server ihn anbietet.'; if (btn) btn.disabled = true; return; } el.textContent = blocked.map(b => `⛔ ${b.host} gesperrt seit ${new Date(b.since).toLocaleString('de-DE')} – die App nutzt dort die langsamere API.`).join('\n'); if (btn) { btn.disabled = false; btn.dataset.host = blocked[0].host; } } async function openFolder(which) { const r = await window.electronAPI.openKnownFolder(which); if (!r || !r.ok) showWarning((r && r.error) || 'Ordner konnte nicht geöffnet werden'); } /* ---------- Verdrahtung ---------- */ function wire() { addTokenTools('githubToken'); addTokenTools('giteaToken'); const on = (id, fn) => { const el = $(id); if (el) el.addEventListener('click', fn); }; on('btnGiteaProfileSave', saveProfile); on('btnGiteaProfileUse', useProfile); on('btnGiteaProfileDelete', deleteProfile); on('btnPrefCloneDir', async () => { const d = await window.electronAPI.selectFolder(); if (d) $('prefCloneDir').value = d; }); on('btnRepoBackupOpen', () => openFolder('backup')); on('btnOpenAppData', () => openFolder('appData')); on('btnOpenLogs', () => openFolder('logs')); on('btnGitTransportClear', async (e) => { const r = await window.electronAPI.gitTransportClear(e.currentTarget.dataset.host || ''); if (r && r.ok) showSuccess('Sperre aufgehoben. Der nächste Upload probiert wieder den schnellen Git-Weg.'); refreshTransportState(); }); // Design als Vorschau sofort zeigen; "Abbrechen" nimmt sie zurück const theme = $('prefTheme'); if (theme) theme.addEventListener('change', () => applyTheme(theme.value)); // Gemeinsames Speichern: läuft zusätzlich zum bisherigen Speichern der Zugangsdaten on('btnSaveSettings', () => { saveExtras(); }); // Beim Öffnen der Einstellungen Stand merken (für die Warnung beim Abbrechen) on('btnSettings', () => setTimeout(takeSnapshot, 80)); // Standardordner beim Klonen mehrerer Repos vorschlagen on('btnBatchActions', () => setTimeout(() => { const field = $('batchCloneTarget'); if (field && !field.value && savedPrefs && savedPrefs.defaultCloneDir) field.value = savedPrefs.defaultCloneDir; }, 50)); // Diagnose-Bereich: Sperrstatus aktualisieren, wenn er geöffnet wird const diagTab = document.querySelector('#settingsModal [data-settings-tab="diagnose"]'); if (diagTab) diagTab.addEventListener('click', refreshTransportState); } // Gemeinsamer Zugriff für features.js / renderer.js window.gmPrefs = { get: () => savedPrefs, load: loadPrefs, takeSnapshot }; loadPrefs().catch(() => {}); if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', wire); else wire(); })();