// Logik der Einstellungsseite. const $ = (id) => document.getElementById(id); // Passwort ist bewusst NICHT dabei – es wird nie gespeichert, nur zum Testen/Login // transient verwendet (siehe "Verbindung testen"). const FIELDS = ["serverUrl", "organization", "username"]; const CHECKS = ["storeAttachments", "tagOnSuccess"]; function setStatus(msg, kind) { const el = $("status"); el.textContent = msg; el.className = kind || ""; } // Warnt, wenn der Server über unverschlüsseltes http:// läuft – dann ginge das // Passwort im Klartext über die Leitung. Liefert einen anzuhängenden Hinweis. function insecureWarning(url) { return /^http:\/\//i.test(String(url || "").trim()) ? " ⚠ Achtung: http:// ist unverschlüsselt – bitte https:// verwenden." : ""; } async function load() { const s = await Settings.get(); FIELDS.forEach((f) => ($(f).value = s[f] || "")); CHECKS.forEach((c) => ($(c).checked = !!s[c])); setMailFormat(s.mailFormat === "pdf" ? "pdf" : "eml"); $("mappingUrl").value = s.mappingUrl || ""; loadMapping(s.fieldMapping && Object.keys(s.fieldMapping).length ? s.fieldMapping : DEFAULT_FIELD_MAP); // Schrankliste, falls vorher schon getestet (im Storage gecached) const cached = (await browser.storage.local.get("cabinets")).cabinets || []; fillCabinets(cached, s.defaultCabinetId); // Best-effort: Felder des Standard-Archivs gleich laden – nur wenn schon ein // Sitzungs-Token da ist (sonst wählt der Nutzer das Archiv manuell). if (s.defaultCabinetId && [...$("mapCabinet").options].some((o) => o.value === s.defaultCabinetId)) { $("mapCabinet").value = s.defaultCabinetId; try { const stt = await browser.runtime.sendMessage({ type: "auth:status" }); if (stt && stt.hasToken && stt.token) { Auth.setToken(stt.token); availableFields = (await DocuWare.cabinetFieldNames(s, s.defaultCabinetId)) .filter((n) => n && !/^DW[A-Z]/.test(n)).sort(); refreshFieldSelects(); $("mapFieldStatus").textContent = `${availableFields.length} Felder zur Auswahl geladen.`; } } catch (_) { /* egal – Archiv kann manuell gewählt werden */ } } } function setMailFormat(v) { const el = document.querySelector(`input[name="mailFormat"][value="${v}"]`); if (el) el.checked = true; } function getMailFormat() { const el = document.querySelector('input[name="mailFormat"]:checked'); return el ? el.value : "eml"; } function fillCabinets(cabinets, selectedId) { // Standard-Ziel (Archive + Briefkörbe) const sel = $("defaultCabinet"); sel.innerHTML = ''; const addGroup = (label, list) => { if (!list.length) return; const g = document.createElement("optgroup"); g.label = label; list.forEach((c) => { const o = document.createElement("option"); o.value = c.id; o.textContent = c.name; if (String(c.id) === String(selectedId)) o.selected = true; g.appendChild(o); }); sel.appendChild(g); }; addGroup("Archive", cabinets.filter((c) => !c.isBasket)); addGroup("Briefkörbe", cabinets.filter((c) => c.isBasket)); // Mapping-Helfer: nur Archive (Briefkörbe haben keine Indexfelder). const mc = $("mapCabinet"); const keep = mc.value; mc.innerHTML = ''; cabinets.filter((c) => !c.isBasket).forEach((c) => { const o = document.createElement("option"); o.value = c.id; o.textContent = c.name; mc.appendChild(o); }); if (keep) mc.value = keep; } // Besorgt einen Sitzungs-Token: vorhandenen vom Broker nutzen, sonst mit dem // (transienten) Passwortfeld anmelden. Liefert false, wenn kein Passwort da ist. async function ensureToken() { const st = await browser.runtime.sendMessage({ type: "auth:status" }); if (st && st.hasToken && st.token) { Auth.setToken(st.token); return true; } const pw = $("password").value; if (!pw) return false; const res = await browser.runtime.sendMessage({ type: "auth:logon", password: pw }); if (!res || !res.ok) throw new Error(res && res.error ? res.error : "Anmeldung fehlgeschlagen."); Auth.setToken(res.token); $("password").value = ""; // Passwort nicht behalten return true; } // --- Feld-Mapping-Editor --------------------------------------------------- const CONST_OPT = "__const__"; // Sentinel im Quelle-Dropdown für „Fester Wert" let availableFields = []; // Feldnamen aus dem zuletzt gewählten Archiv // Zerlegt einen Mapping-Wert (Token-String ODER { const } / { token }) in den UI-Zustand. function mapValueState(value) { if (value && typeof value === "object") { if (value.const != null) return { sel: CONST_OPT, konst: String(value.const) }; if (value.token) return { sel: value.token, konst: "" }; } if (typeof value === "string" && value) return { sel: value, konst: "" }; return { sel: "", konst: "" }; } // Befüllt ein Feldname-Dropdown mit den Archiv-Feldern + dem aktuellen Wert (bleibt erhalten). function fillFieldSelect(sel, current) { sel.innerHTML = ""; const empty = document.createElement("option"); empty.value = ""; empty.textContent = "— Feld wählen —"; sel.appendChild(empty); [...new Set([...(availableFields || []), current].filter(Boolean))].sort().forEach((n) => { const o = document.createElement("option"); o.value = n; o.textContent = n; sel.appendChild(o); }); sel.value = current || ""; } // Aktualisiert alle Feldname-Dropdowns auf die aktuelle Archiv-Feldliste (Auswahl bleibt). function refreshFieldSelects() { document.querySelectorAll("#mapBody .map-field").forEach((sel) => fillFieldSelect(sel, sel.value)); } function addMapRow(field = "", value = "") { const st = mapValueState(value); const row = document.createElement("div"); row.className = "map-row" + (st.sel === CONST_OPT ? " has-const" : ""); // Links gestapelt: Feldname (Dropdown) über Mail-Attribut. const left = document.createElement("div"); left.className = "map-left"; const fieldSel = document.createElement("select"); fieldSel.className = "map-field"; fillFieldSelect(fieldSel, field); const src = document.createElement("select"); src.className = "map-source"; const none = document.createElement("option"); none.value = ""; none.textContent = "— nichts —"; src.appendChild(none); MAIL_TOKENS.forEach((t) => { const o = document.createElement("option"); o.value = t.key; o.textContent = t.label; if (t.key === st.sel) o.selected = true; src.appendChild(o); }); const co = document.createElement("option"); co.value = CONST_OPT; co.textContent = "★ Fester Wert"; if (st.sel === CONST_OPT) co.selected = true; src.appendChild(co); left.append(fieldSel, src); // Rechts (breit): fester Wert – nur sichtbar bei „Fester Wert". const right = document.createElement("div"); right.className = "map-right"; const konst = document.createElement("input"); konst.type = "text"; konst.className = "map-const"; konst.placeholder = "Fester Wert, z. B. NEU"; konst.value = st.konst; right.appendChild(konst); src.addEventListener("change", () => { const isConst = src.value === CONST_OPT; row.classList.toggle("has-const", isConst); if (isConst) konst.focus(); }); const del = document.createElement("button"); del.type = "button"; del.className = "icon-btn"; del.textContent = "✕"; del.title = "Zeile entfernen"; del.addEventListener("click", () => row.remove()); row.append(left, right, del); $("mapBody").appendChild(row); } function loadMapping(map) { $("mapBody").innerHTML = ""; Object.entries(map || {}).forEach(([f, v]) => addMapRow(f, v)); } function collectMapping() { const map = {}; document.querySelectorAll("#mapBody .map-row").forEach((row) => { const f = row.querySelector(".map-field").value.trim(); if (!f) return; // ohne Feldname ignorieren const sel = row.querySelector(".map-source").value; if (sel === CONST_OPT) { const c = row.querySelector(".map-const").value; if (c !== "") map[f] = { const: c }; // fester Wert } else if (sel) { map[f] = sel; // Mail-Token (als String, abwärtskompatibel) } }); return map; } function collect() { const partial = {}; FIELDS.forEach((f) => (partial[f] = $(f).value.trim())); CHECKS.forEach((c) => (partial[c] = $(c).checked)); partial.defaultCabinetId = $("defaultCabinet").value; partial.mailFormat = getMailFormat(); partial.fieldMapping = collectMapping(); partial.mappingUrl = $("mappingUrl").value.trim(); return partial; } // --- Events ---------------------------------------------------------------- $("save").addEventListener("click", async () => { const data = collect(); await Settings.set(data); const warn = insecureWarning(data.serverUrl); setStatus("Gespeichert." + warn, warn ? "err" : "ok"); }); $("test").addEventListener("click", async () => { setStatus("Verbinde …"); try { const s = await Settings.set(collect()); // erst speichern (ohne Passwort), dann testen const ok = await ensureToken(); if (!ok) { setStatus("Bitte Passwort eingeben.", "err"); return; } const cabinets = await DocuWare.listCabinets(s); await browser.storage.local.set({ cabinets }); fillCabinets(cabinets, s.defaultCabinetId); const arch = cabinets.filter((c) => !c.isBasket).length; const bk = cabinets.filter((c) => c.isBasket).length; const warn = insecureWarning(s.serverUrl); setStatus(`Verbindung ok – ${arch} Archive, ${bk} Briefkörbe gefunden.` + warn, warn ? "err" : "ok"); } catch (e) { setStatus(`Fehler: ${e.message}`, "err"); } }); $("mapAdd").addEventListener("click", () => addMapRow()); $("mapReset").addEventListener("click", () => loadMapping(DEFAULT_FIELD_MAP)); // --- Mapping teilen: JSON anzeigen / kopieren / anwenden / von URL laden ---- $("mappingExport").addEventListener("click", () => { $("mappingJson").value = JSON.stringify(collectMapping(), null, 2); setStatus("Aktuelles Mapping als JSON angezeigt.", "ok"); }); $("mappingCopy").addEventListener("click", async () => { let txt = $("mappingJson").value.trim(); if (!txt) { txt = JSON.stringify(collectMapping(), null, 2); $("mappingJson").value = txt; } try { await navigator.clipboard.writeText(txt); setStatus("In Zwischenablage kopiert.", "ok"); } catch (_) { $("mappingJson").select(); document.execCommand("copy"); setStatus("In Zwischenablage kopiert.", "ok"); } }); $("mappingApply").addEventListener("click", () => { try { const obj = JSON.parse($("mappingJson").value); if (!obj || typeof obj !== "object" || Array.isArray(obj)) throw new Error("kein JSON-Objekt"); loadMapping(obj); setStatus("JSON ins Mapping übernommen – mit „Speichern“ sichern.", "ok"); } catch (e) { setStatus(`Ungültiges JSON: ${e.message}`, "err"); } }); $("mappingPull").addEventListener("click", async () => { const url = $("mappingUrl").value.trim(); if (!url) { setStatus("Bitte eine URL eingeben.", "err"); return; } setStatus("Lade Mapping …"); try { const res = await fetch(url, { cache: "no-store" }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const obj = await res.json(); if (!obj || typeof obj !== "object" || Array.isArray(obj)) throw new Error("kein JSON-Objekt"); loadMapping(obj); await Settings.set({ mappingUrl: url }); // URL gleich merken setStatus("Mapping von URL geladen – mit „Speichern“ übernehmen.", "ok"); } catch (e) { setStatus(`Laden fehlgeschlagen: ${e.message}`, "err"); } }); // Archiv wählen -> dessen Indexfelder als Vorschläge (datalist) für alle Feldnamen. $("mapCabinet").addEventListener("change", async () => { const id = $("mapCabinet").value; const st = $("mapFieldStatus"); if (!id) { availableFields = []; refreshFieldSelects(); st.textContent = ""; return; } st.textContent = "Lade Felder …"; try { const s = await Settings.get(); const ok = await ensureToken(); if (!ok) { st.textContent = "Erst „Verbindung testen“ (Passwort nötig)."; return; } availableFields = (await DocuWare.cabinetFieldNames(s, id)) .filter((n) => n && !/^DW[A-Z]/.test(n)) // DW*-Systemfelder ausblenden .sort(); refreshFieldSelects(); st.textContent = `${availableFields.length} Felder zur Auswahl geladen.`; } catch (e) { st.textContent = `Fehler: ${e.message}`; } }); load();