Compare commits

...

2 Commits

Author SHA1 Message Date
d9ec2537e2 Release 0.12.2 2026-08-21 10:23:56 +02:00
f6c7761182 Feld-Zuordnung: Feldname wieder als Auswahl - und die Liste bleibt gefuellt
0.12.1 hatte den Feldnamen auf eine freie Texteingabe umgestellt. Das behob zwar
die Blockade, nahm aber die Auswahl weg - eigentlich war nur die Liste leer.

Jetzt wieder ein <select>, dessen Liste sich aus drei Quellen speist und damit
nie leer ist: Felder des gewaehlten Archivs, die Namen aus DEFAULT_FIELD_MAP und
alles im Formular bereits Vergebene. Ein Eintrag "Anderer Feldname" blendet eine
freie Eingabe ein, fuer Felder die in keiner Liste stehen. Ein gespeicherter Name,
den kein Archiv kennt, wird ins Dropdown aufgenommen statt verloren zu gehen.

Kern des Problems war die Herkunft der Liste: sie kam ausschliesslich vom Server
und setzte damit gecachte Schrankliste + Passwort dieser Sitzung + Archivwahl
voraus. Die Feldnamen werden nun je Archiv in storage.local unter
"fieldsByCabinet" gemerkt und beim Oeffnen VOR dem Aufbau der Zeilen gelesen -
die Auswahl steht also sofort und auch ohne Anmeldung. Ist ein Token da, wird im
Hintergrund aufgefrischt; schlaegt das fehl, bleibt die gemerkte Liste nutzbar.

Bei Server-Wechsel wird fieldsByCabinet mitgeloescht - die Feldlisten haengen an
Schrank-GUIDs des alten Servers (gleiche Logik wie fuer cabinets/defaultCabinetId).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 10:23:36 +02:00
4 changed files with 126 additions and 40 deletions

View File

@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "DocuWare Ablage",
"description": "Legt markierte E-Mails (als .eml, PDF und mit Anhängen) in DocuWare ab.",
"version": "0.12.1",
"version": "0.12.2",
"author": "l.lingler",
"applications": {
"gecko": {

View File

@ -109,6 +109,7 @@
.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-left input, .map-right input { width: 100%; padding: 7px 9px; }
.map-left input[hidden] { display: none; }
.map-hint { font-size: 11px; letter-spacing: .4px; text-transform: uppercase;
color: var(--muted); font-weight: 600; margin: 2px 2px 8px; }
.icon-btn {
@ -222,13 +223,13 @@
<div class="map-hint">DocuWare-Feld&nbsp;→ Mail-Attribut · fester Wert</div>
<div id="mapBody" class="map"></div>
<!-- Vorschlagsliste fuer alle Feldname-Eingaben (siehe refreshFieldSelects). -->
<datalist id="fieldNameList"></datalist>
<div class="map-toolbar" style="margin-top:10px">
<button id="mapAdd" class="ghost" type="button">+ Zeile</button>
<button id="mapReset" class="ghost" type="button">Auf Standard zurücksetzen</button>
</div>
<div class="hint" style="margin-top:8px">Steht ein Feld nicht in der Liste,
„<strong>✎ Anderer Feldname</strong>" wählen und den Namen eintippen.</div>
<div class="hint" style="margin-top:8px">Als Mail-Attribut „<strong>Fester Wert</strong>"
wählen, um einen festen Text (z. B. <code>NEU</code>) ins Feld zu schreiben.</div>

View File

@ -58,7 +58,12 @@ async function invalidateSession(s) {
let next = s;
if (server) {
next = await Settings.set({ defaultCabinetId: "", dialogByCabinet: {} });
// Gemerkte Feldlisten hängen an Schrank-GUIDs des alten Servers -> weg damit.
fieldsByCabinet = {};
await browser.storage.local.remove("fieldsByCabinet").catch(() => {});
availableFields = [];
fillCabinets([], ""); // Dropdowns leeren – die alte Liste gilt nicht mehr
refreshFieldSelects();
}
snapshotAuth(next);
return next;
@ -71,13 +76,23 @@ async function load() {
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).
// Gemerkte Schrank- und Feldlisten VOR dem Aufbau der Zeilen lesen, damit die
// Feldname-Dropdowns schon beim ersten Rendern gefüllt sind.
const local = await browser.storage.local.get(["cabinets", "fieldsByCabinet"]);
fieldsByCabinet = local.fieldsByCabinet || {};
if (s.defaultCabinetId && Array.isArray(fieldsByCabinet[s.defaultCabinetId])) {
availableFields = fieldsByCabinet[s.defaultCabinetId];
}
loadMapping(s.fieldMapping && Object.keys(s.fieldMapping).length ? s.fieldMapping : DEFAULT_FIELD_MAP);
fillCabinets(local.cabinets || [], s.defaultCabinetId);
if (availableFields.length) {
$("mapFieldStatus").textContent = `${availableFields.length} Felder zur Auswahl (gemerkt).`;
}
// Best-effort auffrischen – nur wenn schon ein Sitzungs-Token da ist. Ohne
// Token bleibt es bei der gemerkten Liste, die Auswahl funktioniert trotzdem.
if (s.defaultCabinetId && [...$("mapCabinet").options].some((o) => o.value === s.defaultCabinetId)) {
$("mapCabinet").value = s.defaultCabinetId;
try {
@ -86,10 +101,11 @@ async function load() {
Auth.setToken(stt.token);
availableFields = (await DocuWare.cabinetFieldNames(s, s.defaultCabinetId))
.filter((n) => n && !/^DW[A-Z]/.test(n)).sort();
await rememberFields(s.defaultCabinetId, availableFields);
refreshFieldSelects();
$("mapFieldStatus").textContent = `${availableFields.length} Felder zur Auswahl geladen.`;
}
} catch (_) { /* egal – Archiv kann manuell gewählt werden */ }
} catch (_) { /* egal – gemerkte Liste bleibt nutzbar */ }
}
}
@ -160,7 +176,18 @@ 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
const CUSTOM_OPT = "__custom__"; // Sentinel im Feldname-Dropdown für „eigener Name"
let availableFields = []; // Feldnamen des zuletzt gewählten Archivs
// Gemerkte Feldnamen je Archiv (storage.local, Schlüssel "fieldsByCabinet").
// Ohne diesen Cache war die Auswahlliste bei jedem Thunderbird-Start wieder leer:
// sie kam nur vom Server und setzte damit Passworteingabe + Archivwahl voraus.
let fieldsByCabinet = {};
async function rememberFields(cabinetId, names) {
fieldsByCabinet = { ...fieldsByCabinet, [cabinetId]: names };
await browser.storage.local.set({ fieldsByCabinet }).catch(() => {});
}
// Zerlegt einen Mapping-Wert (Token-String ODER { const } / { token }) in den UI-Zustand.
function mapValueState(value) {
@ -172,25 +199,52 @@ function mapValueState(value) {
return { sel: "", konst: "" };
}
// Vorschläge für die Feldname-Eingaben: Felder des gewählten Archivs, dazu die
// Standard-Feldnamen und alles schon Eingetragene. Der Archiv-Teil ist erst nach
// Anmeldung + Archivwahl da – ohne die beiden anderen Quellen stünde man vorher
// vor einer leeren Liste.
// Feldname einer Zeile – aus dem Dropdown oder, bei „Anderer Feldname", aus der
// daneben eingeblendeten Eingabe.
function rowFieldName(row) {
const sel = row.querySelector(".map-field-sel");
if (!sel) return "";
if (sel.value === CUSTOM_OPT) {
const inp = row.querySelector(".map-field-custom");
return inp ? inp.value.trim() : "";
}
return sel.value.trim();
}
// Auswahlliste für die Feldname-Dropdowns. Drei Quellen, damit sie NIE leer ist:
// Felder des gewählten Archivs (auch aus dem Cache, also ohne Anmeldung), die
// Standard-Feldnamen und alles im Formular bereits Vergebene.
function fieldSuggestions() {
const used = [...document.querySelectorAll("#mapBody .map-field")].map((i) => i.value);
const used = [...document.querySelectorAll("#mapBody .map-row")].map(rowFieldName);
const all = [...(availableFields || []), ...Object.keys(DEFAULT_FIELD_MAP), ...used];
return [...new Set(all.filter(Boolean))].sort();
}
// Baut die gemeinsame <datalist> neu auf (alle Zeilen hängen daran).
function refreshFieldSelects() {
const dl = $("fieldNameList");
if (!dl) return;
dl.innerHTML = "";
fieldSuggestions().forEach((n) => {
// Ein Feldname-Dropdown neu aufbauen. Der aktuelle Wert steht immer mit drin,
// auch wenn das Archiv ihn nicht (mehr) kennt – sonst ginge er beim Neuaufbau
// still verloren.
function fillFieldSelect(sel, current) {
sel.innerHTML = "";
const empty = document.createElement("option");
empty.value = ""; empty.textContent = "— Feld wählen —";
sel.appendChild(empty);
[...new Set([...fieldSuggestions(), current].filter(Boolean))].sort().forEach((n) => {
const o = document.createElement("option");
o.value = n;
dl.appendChild(o);
o.value = n; o.textContent = n;
sel.appendChild(o);
});
const own = document.createElement("option");
own.value = CUSTOM_OPT; own.textContent = "✎ Anderer Feldname …";
sel.appendChild(own);
sel.value = current || "";
}
// Alle Dropdowns auf die aktuelle Liste bringen (Auswahl bleibt). Zeilen mit
// freier Eingabe bleiben unangetastet.
function refreshFieldSelects() {
document.querySelectorAll("#mapBody .map-field-sel").forEach((sel) => {
if (sel.value === CUSTOM_OPT) return;
fillFieldSelect(sel, sel.value);
});
}
@ -199,19 +253,33 @@ function addMapRow(field = "", value = "") {
const row = document.createElement("div");
row.className = "map-row" + (st.sel === CONST_OPT ? " has-const" : "");
// Links gestapelt: Feldname (Eingabe mit Vorschlägen) über Mail-Attribut.
// Links gestapelt: Feldname (Dropdown, bei Bedarf freie Eingabe) über Mail-Attribut.
const left = document.createElement("div"); left.className = "map-left";
// Freie Eingabe mit Vorschlagsliste – bewusst KEIN <select>: die Archiv-Feldliste
// gibt es erst nach Anmeldung, ein starres Dropdown wäre bis dahin leer und die
// Zeile unbenutzbar (ohne Feldnamen wirft collectMapping sie beim Speichern weg).
const fieldSel = document.createElement("input");
fieldSel.type = "text";
fieldSel.className = "map-field";
fieldSel.setAttribute("list", "fieldNameList");
fieldSel.placeholder = "DocuWare-Feldname, z. B. EML_SUBJECT";
fieldSel.value = field || "";
fieldSel.addEventListener("change", refreshFieldSelects);
// Feldname wird ausgewählt. Die Liste ist immer gefüllt (Archiv-Cache +
// Standardnamen), und „✎ Anderer Feldname" blendet eine freie Eingabe ein –
// für Felder, die in keiner Liste stehen.
const fieldSel = document.createElement("select");
fieldSel.className = "map-field-sel";
const own = document.createElement("input");
own.type = "text"; own.className = "map-field-custom";
own.placeholder = "Eigener Feldname, z. B. EML_SUBJECT";
own.hidden = true;
fillFieldSelect(fieldSel, field);
if (field && fieldSel.value !== field) {
// Sicherheitsnetz: Name nicht im Dropdown gelandet -> als freie Eingabe zeigen,
// statt ihn stillschweigend zu verlieren.
fieldSel.value = CUSTOM_OPT; own.value = field; own.hidden = false;
}
fieldSel.addEventListener("change", () => {
const isOwn = fieldSel.value === CUSTOM_OPT;
own.hidden = !isOwn;
if (isOwn) own.focus();
});
own.addEventListener("change", refreshFieldSelects);
const src = document.createElement("select"); src.className = "map-source";
const none = document.createElement("option"); none.value = ""; none.textContent = "— nichts —";
@ -227,7 +295,7 @@ function addMapRow(field = "", value = "") {
if (st.sel === CONST_OPT) co.selected = true;
src.appendChild(co);
left.append(fieldSel, src);
left.append(fieldSel, own, src);
// Rechts (breit): fester Wert – nur sichtbar bei „Fester Wert".
const right = document.createElement("div"); right.className = "map-right";
@ -259,7 +327,7 @@ function loadMapping(map) {
function collectMapping() {
const map = {};
document.querySelectorAll("#mapBody .map-row").forEach((row) => {
const f = row.querySelector(".map-field").value.trim();
const f = rowFieldName(row);
if (!f) return; // ohne Feldname ignorieren
const sel = row.querySelector(".map-source").value;
if (sel === CONST_OPT) {
@ -372,14 +440,27 @@ $("mapCabinet").addEventListener("change", async () => {
const id = $("mapCabinet").value;
const st = $("mapFieldStatus");
if (!id) { availableFields = []; refreshFieldSelects(); st.textContent = ""; return; }
// Gemerkte Liste sofort anzeigen – dann ist die Auswahl auch ohne Anmeldung da.
if (Array.isArray(fieldsByCabinet[id])) {
availableFields = fieldsByCabinet[id];
refreshFieldSelects();
st.textContent = `${availableFields.length} Felder zur Auswahl (gemerkt).`;
} else {
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; }
if (!ok) {
if (!availableFields.length) st.textContent = "Erst „Verbindung testen“ (Passwort nötig).";
return; // gemerkte Liste bleibt stehen
}
availableFields = (await DocuWare.cabinetFieldNames(s, id))
.filter((n) => n && !/^DW[A-Z]/.test(n)) // DW*-Systemfelder ausblenden
.sort();
await rememberFields(id, availableFields);
refreshFieldSelects();
st.textContent = `${availableFields.length} Felder zur Auswahl geladen.`;
} catch (e) {

View File

@ -49,6 +49,10 @@
{
"version": "0.12.1",
"update_link": "https://sylyx.xyz/sylyx/thunderbird2docuware/releases/download/v0.12.1/docuware-ablage-0.12.1.xpi"
},
{
"version": "0.12.2",
"update_link": "https://sylyx.xyz/sylyx/thunderbird2docuware/releases/download/v0.12.2/docuware-ablage-0.12.2.xpi"
}
]
}