From 51f9362c9c1adbc103e764901a8fc5cf5413001b Mon Sep 17 00:00:00 2001 From: sylyx Date: Tue, 16 Jun 2026 16:09:04 +0200 Subject: [PATCH] Mapping-Editor: DocuWare-Feldname als Dropdown (aus Archiv), Zeilen als Karten (Feld+Attribut gestapelt, fester Wert breit daneben), Auto-Load der Standard-Archiv-Felder Co-Authored-By: Claude Opus 4.8 --- options/options.html | 27 ++++++----- options/options.js | 108 ++++++++++++++++++++++++++++--------------- 2 files changed, 87 insertions(+), 48 deletions(-) diff --git a/options/options.html b/options/options.html index 1509bdc..4b2694a 100644 --- a/options/options.html +++ b/options/options.html @@ -97,12 +97,20 @@ /* Mapping-Tabelle */ .map-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 4px 0 12px; } .map-toolbar .grow { flex: 1; min-width: 180px; } - table.map { width: 100%; border-collapse: separate; border-spacing: 0 6px; } - table.map th { text-align: left; font-size: 11px; letter-spacing: .4px; text-transform: uppercase; - color: var(--muted); font-weight: 600; padding: 0 6px 2px; } - table.map td { padding: 0 6px; vertical-align: middle; } - table.map input, table.map select { padding: 7px 9px; } - table.map td.del { width: 40px; } + /* Mapping-Zeilen: Feldname + Attribut links gestapelt, fester Wert breit daneben. */ + .map { margin-top: 4px; } + .map-row { + display: flex; align-items: center; gap: 10px; + background: var(--surface-2); border: 1px solid var(--border); + border-radius: 9px; padding: 8px 8px 8px 10px; margin-bottom: 8px; + } + .map-left { display: flex; flex-direction: column; gap: 6px; flex: 1 1 auto; min-width: 0; } + .map-row.has-const .map-left { flex: 0 0 250px; } + .map-right { display: none; flex: 1 1 auto; min-width: 0; } + .map-row.has-const .map-right { display: flex; align-items: center; } + .map-left select, .map-right input { width: 100%; padding: 7px 9px; } + .map-hint { font-size: 11px; letter-spacing: .4px; text-transform: uppercase; + color: var(--muted); font-weight: 600; margin: 2px 2px 8px; } .icon-btn { width: 30px; height: 30px; padding: 0; border-radius: 7px; cursor: pointer; background: var(--surface-2); border: 1px solid var(--border-strong); color: var(--muted); @@ -212,11 +220,8 @@ - - - -
DocuWare-FeldnameMail-Attribut
- +
DocuWare-Feld → Mail-Attribut · fester Wert
+
diff --git a/options/options.js b/options/options.js index 1215d1c..6b2c0aa 100644 --- a/options/options.js +++ b/options/options.js @@ -22,6 +22,22 @@ async function load() { // 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) { @@ -83,6 +99,7 @@ async function ensureToken() { // --- 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) { @@ -94,49 +111,71 @@ function mapValueState(value) { 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 tr = document.createElement("tr"); + const row = document.createElement("div"); + row.className = "map-row" + (st.sel === CONST_OPT ? " has-const" : ""); - const inp = document.createElement("input"); - inp.type = "text"; inp.className = "map-field"; inp.value = field; - inp.placeholder = "EML_SUBJECT"; - inp.setAttribute("list", "fieldNames"); // Vorschläge aus gewähltem Archiv - const tdF = document.createElement("td"); tdF.appendChild(inp); + // Links gestapelt: Feldname (Dropdown) über Mail-Attribut. + const left = document.createElement("div"); left.className = "map-left"; - const sel = document.createElement("select"); sel.className = "map-source"; + 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 —"; - sel.appendChild(none); + 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; - sel.appendChild(o); + src.appendChild(o); }); const co = document.createElement("option"); co.value = CONST_OPT; co.textContent = "★ Fester Wert"; if (st.sel === CONST_OPT) co.selected = true; - sel.appendChild(co); + 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 = "z. B. NEU"; - konst.value = st.konst; konst.style.marginTop = "6px"; - konst.style.display = st.sel === CONST_OPT ? "" : "none"; + konst.type = "text"; konst.className = "map-const"; konst.placeholder = "Fester Wert, z. B. NEU"; + konst.value = st.konst; + right.appendChild(konst); - sel.addEventListener("change", () => { - konst.style.display = sel.value === CONST_OPT ? "" : "none"; - if (sel.value === CONST_OPT) konst.focus(); + src.addEventListener("change", () => { + const isConst = src.value === CONST_OPT; + row.classList.toggle("has-const", isConst); + if (isConst) konst.focus(); }); - const tdT = document.createElement("td"); tdT.appendChild(sel); tdT.appendChild(konst); - const del = document.createElement("button"); del.type = "button"; del.className = "icon-btn"; del.textContent = "✕"; del.title = "Zeile entfernen"; - del.addEventListener("click", () => tr.remove()); - const tdX = document.createElement("td"); tdX.className = "del"; tdX.appendChild(del); + del.addEventListener("click", () => row.remove()); - tr.append(tdF, tdT, tdX); - $("mapBody").appendChild(tr); + row.append(left, right, del); + $("mapBody").appendChild(row); } function loadMapping(map) { @@ -146,12 +185,12 @@ function loadMapping(map) { function collectMapping() { const map = {}; - document.querySelectorAll("#mapBody tr").forEach((tr) => { - const f = tr.querySelector(".map-field").value.trim(); + document.querySelectorAll("#mapBody .map-row").forEach((row) => { + const f = row.querySelector(".map-field").value.trim(); if (!f) return; // ohne Feldname ignorieren - const sel = tr.querySelector(".map-source").value; + const sel = row.querySelector(".map-source").value; if (sel === CONST_OPT) { - const c = tr.querySelector(".map-const").value; + 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) @@ -248,23 +287,18 @@ $("mappingPull").addEventListener("click", async () => { // Archiv wählen -> dessen Indexfelder als Vorschläge (datalist) für alle Feldnamen. $("mapCabinet").addEventListener("change", async () => { const id = $("mapCabinet").value; - const dl = $("fieldNames"); const st = $("mapFieldStatus"); - if (!id) { dl.innerHTML = ""; st.textContent = ""; return; } + 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; } - const names = (await DocuWare.cabinetFieldNames(s, id)) - .filter((n) => n && !/^DW[A-Z]/.test(n)); // DW*-Systemfelder ausblenden - dl.innerHTML = ""; - names.sort().forEach((n) => { - const o = document.createElement("option"); - o.value = n; - dl.appendChild(o); - }); - st.textContent = `${names.length} Felder als Vorschlag geladen.`; + 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}`; }