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 <noreply@anthropic.com>
This commit is contained in:
parent
1a358a96ef
commit
51f9362c9c
@ -97,12 +97,20 @@
|
|||||||
/* Mapping-Tabelle */
|
/* Mapping-Tabelle */
|
||||||
.map-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 4px 0 12px; }
|
.map-toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 4px 0 12px; }
|
||||||
.map-toolbar .grow { flex: 1; min-width: 180px; }
|
.map-toolbar .grow { flex: 1; min-width: 180px; }
|
||||||
table.map { width: 100%; border-collapse: separate; border-spacing: 0 6px; }
|
/* Mapping-Zeilen: Feldname + Attribut links gestapelt, fester Wert breit daneben. */
|
||||||
table.map th { text-align: left; font-size: 11px; letter-spacing: .4px; text-transform: uppercase;
|
.map { margin-top: 4px; }
|
||||||
color: var(--muted); font-weight: 600; padding: 0 6px 2px; }
|
.map-row {
|
||||||
table.map td { padding: 0 6px; vertical-align: middle; }
|
display: flex; align-items: center; gap: 10px;
|
||||||
table.map input, table.map select { padding: 7px 9px; }
|
background: var(--surface-2); border: 1px solid var(--border);
|
||||||
table.map td.del { width: 40px; }
|
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 {
|
.icon-btn {
|
||||||
width: 30px; height: 30px; padding: 0; border-radius: 7px; cursor: pointer;
|
width: 30px; height: 30px; padding: 0; border-radius: 7px; cursor: pointer;
|
||||||
background: var(--surface-2); border: 1px solid var(--border-strong); color: var(--muted);
|
background: var(--surface-2); border: 1px solid var(--border-strong); color: var(--muted);
|
||||||
@ -212,11 +220,8 @@
|
|||||||
<span id="mapFieldStatus" class="hint" style="margin:0"></span>
|
<span id="mapFieldStatus" class="hint" style="margin:0"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="map">
|
<div class="map-hint">DocuWare-Feld → Mail-Attribut · fester Wert</div>
|
||||||
<thead><tr><th>DocuWare-Feldname</th><th>Mail-Attribut</th><th></th></tr></thead>
|
<div id="mapBody" class="map"></div>
|
||||||
<tbody id="mapBody"></tbody>
|
|
||||||
</table>
|
|
||||||
<datalist id="fieldNames"></datalist>
|
|
||||||
|
|
||||||
<div class="map-toolbar" style="margin-top:10px">
|
<div class="map-toolbar" style="margin-top:10px">
|
||||||
<button id="mapAdd" class="ghost" type="button">+ Zeile</button>
|
<button id="mapAdd" class="ghost" type="button">+ Zeile</button>
|
||||||
|
|||||||
@ -22,6 +22,22 @@ async function load() {
|
|||||||
// Schrankliste, falls vorher schon getestet (im Storage gecached)
|
// Schrankliste, falls vorher schon getestet (im Storage gecached)
|
||||||
const cached = (await browser.storage.local.get("cabinets")).cabinets || [];
|
const cached = (await browser.storage.local.get("cabinets")).cabinets || [];
|
||||||
fillCabinets(cached, s.defaultCabinetId);
|
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) {
|
function setMailFormat(v) {
|
||||||
@ -83,6 +99,7 @@ async function ensureToken() {
|
|||||||
// --- Feld-Mapping-Editor ---------------------------------------------------
|
// --- Feld-Mapping-Editor ---------------------------------------------------
|
||||||
|
|
||||||
const CONST_OPT = "__const__"; // Sentinel im Quelle-Dropdown für „Fester Wert"
|
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.
|
// Zerlegt einen Mapping-Wert (Token-String ODER { const } / { token }) in den UI-Zustand.
|
||||||
function mapValueState(value) {
|
function mapValueState(value) {
|
||||||
@ -94,49 +111,71 @@ function mapValueState(value) {
|
|||||||
return { sel: "", 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 = "") {
|
function addMapRow(field = "", value = "") {
|
||||||
const st = mapValueState(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");
|
// Links gestapelt: Feldname (Dropdown) über Mail-Attribut.
|
||||||
inp.type = "text"; inp.className = "map-field"; inp.value = field;
|
const left = document.createElement("div"); left.className = "map-left";
|
||||||
inp.placeholder = "EML_SUBJECT";
|
|
||||||
inp.setAttribute("list", "fieldNames"); // Vorschläge aus gewähltem Archiv
|
|
||||||
const tdF = document.createElement("td"); tdF.appendChild(inp);
|
|
||||||
|
|
||||||
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 —";
|
const none = document.createElement("option"); none.value = ""; none.textContent = "— nichts —";
|
||||||
sel.appendChild(none);
|
src.appendChild(none);
|
||||||
MAIL_TOKENS.forEach((t) => {
|
MAIL_TOKENS.forEach((t) => {
|
||||||
const o = document.createElement("option");
|
const o = document.createElement("option");
|
||||||
o.value = t.key; o.textContent = t.label;
|
o.value = t.key; o.textContent = t.label;
|
||||||
if (t.key === st.sel) o.selected = true;
|
if (t.key === st.sel) o.selected = true;
|
||||||
sel.appendChild(o);
|
src.appendChild(o);
|
||||||
});
|
});
|
||||||
const co = document.createElement("option");
|
const co = document.createElement("option");
|
||||||
co.value = CONST_OPT; co.textContent = "★ Fester Wert";
|
co.value = CONST_OPT; co.textContent = "★ Fester Wert";
|
||||||
if (st.sel === CONST_OPT) co.selected = true;
|
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");
|
const konst = document.createElement("input");
|
||||||
konst.type = "text"; konst.className = "map-const"; konst.placeholder = "z. B. NEU";
|
konst.type = "text"; konst.className = "map-const"; konst.placeholder = "Fester Wert, z. B. NEU";
|
||||||
konst.value = st.konst; konst.style.marginTop = "6px";
|
konst.value = st.konst;
|
||||||
konst.style.display = st.sel === CONST_OPT ? "" : "none";
|
right.appendChild(konst);
|
||||||
|
|
||||||
sel.addEventListener("change", () => {
|
src.addEventListener("change", () => {
|
||||||
konst.style.display = sel.value === CONST_OPT ? "" : "none";
|
const isConst = src.value === CONST_OPT;
|
||||||
if (sel.value === CONST_OPT) konst.focus();
|
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");
|
const del = document.createElement("button");
|
||||||
del.type = "button"; del.className = "icon-btn"; del.textContent = "✕"; del.title = "Zeile entfernen";
|
del.type = "button"; del.className = "icon-btn"; del.textContent = "✕"; del.title = "Zeile entfernen";
|
||||||
del.addEventListener("click", () => tr.remove());
|
del.addEventListener("click", () => row.remove());
|
||||||
const tdX = document.createElement("td"); tdX.className = "del"; tdX.appendChild(del);
|
|
||||||
|
|
||||||
tr.append(tdF, tdT, tdX);
|
row.append(left, right, del);
|
||||||
$("mapBody").appendChild(tr);
|
$("mapBody").appendChild(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadMapping(map) {
|
function loadMapping(map) {
|
||||||
@ -146,12 +185,12 @@ function loadMapping(map) {
|
|||||||
|
|
||||||
function collectMapping() {
|
function collectMapping() {
|
||||||
const map = {};
|
const map = {};
|
||||||
document.querySelectorAll("#mapBody tr").forEach((tr) => {
|
document.querySelectorAll("#mapBody .map-row").forEach((row) => {
|
||||||
const f = tr.querySelector(".map-field").value.trim();
|
const f = row.querySelector(".map-field").value.trim();
|
||||||
if (!f) return; // ohne Feldname ignorieren
|
if (!f) return; // ohne Feldname ignorieren
|
||||||
const sel = tr.querySelector(".map-source").value;
|
const sel = row.querySelector(".map-source").value;
|
||||||
if (sel === CONST_OPT) {
|
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
|
if (c !== "") map[f] = { const: c }; // fester Wert
|
||||||
} else if (sel) {
|
} else if (sel) {
|
||||||
map[f] = sel; // Mail-Token (als String, abwärtskompatibel)
|
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.
|
// Archiv wählen -> dessen Indexfelder als Vorschläge (datalist) für alle Feldnamen.
|
||||||
$("mapCabinet").addEventListener("change", async () => {
|
$("mapCabinet").addEventListener("change", async () => {
|
||||||
const id = $("mapCabinet").value;
|
const id = $("mapCabinet").value;
|
||||||
const dl = $("fieldNames");
|
|
||||||
const st = $("mapFieldStatus");
|
const st = $("mapFieldStatus");
|
||||||
if (!id) { dl.innerHTML = ""; st.textContent = ""; return; }
|
if (!id) { availableFields = []; refreshFieldSelects(); st.textContent = ""; return; }
|
||||||
st.textContent = "Lade Felder …";
|
st.textContent = "Lade Felder …";
|
||||||
try {
|
try {
|
||||||
const s = await Settings.get();
|
const s = await Settings.get();
|
||||||
const ok = await ensureToken();
|
const ok = await ensureToken();
|
||||||
if (!ok) { st.textContent = "Erst „Verbindung testen“ (Passwort nötig)."; return; }
|
if (!ok) { st.textContent = "Erst „Verbindung testen“ (Passwort nötig)."; return; }
|
||||||
const names = (await DocuWare.cabinetFieldNames(s, id))
|
availableFields = (await DocuWare.cabinetFieldNames(s, id))
|
||||||
.filter((n) => n && !/^DW[A-Z]/.test(n)); // DW*-Systemfelder ausblenden
|
.filter((n) => n && !/^DW[A-Z]/.test(n)) // DW*-Systemfelder ausblenden
|
||||||
dl.innerHTML = "";
|
.sort();
|
||||||
names.sort().forEach((n) => {
|
refreshFieldSelects();
|
||||||
const o = document.createElement("option");
|
st.textContent = `${availableFields.length} Felder zur Auswahl geladen.`;
|
||||||
o.value = n;
|
|
||||||
dl.appendChild(o);
|
|
||||||
});
|
|
||||||
st.textContent = `${names.length} Felder als Vorschlag geladen.`;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
st.textContent = `Fehler: ${e.message}`;
|
st.textContent = `Fehler: ${e.message}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user