Compare commits

...

2 Commits

Author SHA1 Message Date
1a358a96ef Release 0.10.5 2026-06-16 15:02:41 +02:00
81964c76d3 Mapping: feste Werte ({const}) + Teilen via JSON-Copy/Paste und Pull von raw-JSON-URL (mappingUrl)
- prefillFor löst Token-String ODER {const:...} auf; Options-Editor mit
  'Fester Wert'-Option + Konstanten-Eingabe.
- Feld-Zuordnung: JSON exportieren/anwenden/kopieren, von URL laden (Gitea/GitHub raw).
- Fix: zwei Statustexte mit ASCII-Quote in " gehärtet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:01:37 +02:00
6 changed files with 147 additions and 12 deletions

View File

@ -39,10 +39,17 @@ function effectiveFieldMap() {
return m && Object.keys(m).length ? m : DEFAULT_FIELD_MAP;
}
// Vorbefüllwert für ein DocuWare-Feld aus den Mail-Metadaten (oder undefined).
// Vorbefüllwert für ein DocuWare-Feld (oder undefined). Das Mapping kann je Feld
// entweder einen Mail-Token (String) ODER einen festen Wert ({ const: "NEU" }) tragen.
function prefillFor(fieldName, meta) {
const token = effectiveFieldMap()[fieldName];
const fn = token && TOKEN_FN[token];
const m = effectiveFieldMap()[fieldName];
if (!m) return undefined;
if (typeof m === "object") {
if (m.const !== undefined && m.const !== null && m.const !== "") return m.const;
if (m.token && TOKEN_FN[m.token]) return TOKEN_FN[m.token](meta);
return undefined;
}
const fn = TOKEN_FN[m];
return fn ? fn(meta) : undefined;
}

View File

@ -12,7 +12,10 @@ const DEFAULTS = {
mailFormat: "eml", // E-Mail wird als "eml" ODER "pdf" abgelegt (nie beides)
storeAttachments: true,
tagOnSuccess: true,
fieldMapping: {}, // DocuWare-Feldname -> Mail-Attribut (Token). Leer = DEFAULT_FIELD_MAP.
// DocuWare-Feldname -> Wert: entweder ein Mail-Token ("subject") ODER ein fester
// Wert ({ const: "NEU" }). Leer = DEFAULT_FIELD_MAP.
fieldMapping: {},
mappingUrl: "", // optional: raw-JSON-URL (Gitea/GitHub) zum Nachladen des Mappings.
};
// Mögliche Mail-Attribute für die Vorbefüllung (Token + Anzeigename).

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.10.4",
"version": "0.10.5",
"author": "l.lingler",
"applications": {
"gecko": {

View File

@ -110,6 +110,22 @@
}
.icon-btn:hover { color: var(--err); border-color: var(--err); }
/* Teilen / JSON */
details.share { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 10px; }
details.share summary {
cursor: pointer; font-size: 13px; font-weight: 600; color: var(--accent);
list-style: none; user-select: none; padding: 2px 0;
}
details.share summary::-webkit-details-marker { display: none; }
details.share summary::before { content: "▸ "; color: var(--muted); }
details.share[open] summary::before { content: "▾ "; }
textarea {
width: 100%; padding: 10px 11px; color: var(--text); background: var(--surface-2);
border: 1px solid var(--border-strong); border-radius: 8px; box-sizing: border-box;
font: 12px/1.45 ui-monospace, "Cascadia Code", Consolas, monospace; resize: vertical; outline: none;
}
textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-weak); }
/* Buttons */
button { font: inherit; cursor: pointer; border-radius: 8px; }
button.primary {
@ -206,6 +222,28 @@
<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">Als Mail-Attribut „<strong>Fester Wert</strong>"
wählen, um einen festen Text (z. B. <code>NEU</code>) ins Feld zu schreiben.</div>
<details class="share">
<summary>Mapping teilen / übernehmen</summary>
<label for="mappingUrl">Aus Repo/URL laden</label>
<div class="map-toolbar">
<input type="url" id="mappingUrl" class="grow" placeholder="https://gitea.example/…/mapping.json" />
<button id="mappingPull" class="ghost" type="button">Von URL laden</button>
</div>
<div class="hint">Eine raw-JSON-Datei aus einem Gitea/GitHub-Repo. Lädt das Mapping in
den Editor mit „Speichern" übernehmen. (Lesen/Pull; Zurückschreiben ins Repo nicht.)</div>
<label for="mappingJson">JSON (kopieren / einfügen)</label>
<textarea id="mappingJson" rows="6" placeholder='{ "EML_SUBJECT": "subject", "STATUS": { "const": "NEU" } }'></textarea>
<div class="map-toolbar">
<button id="mappingExport" class="ghost" type="button">Aktuelles als JSON</button>
<button id="mappingCopy" class="ghost" type="button">Kopieren</button>
<button id="mappingApply" class="ghost" type="button">JSON anwenden</button>
</div>
</details>
</section>
</div>

View File

@ -17,6 +17,7 @@ async function load() {
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 || [];
@ -81,7 +82,20 @@ async function ensureToken() {
// --- Feld-Mapping-Editor ---------------------------------------------------
function addMapRow(field = "", token = "") {
const CONST_OPT = "__const__"; // Sentinel im Quelle-Dropdown für „Fester Wert"
// 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: "" };
}
function addMapRow(field = "", value = "") {
const st = mapValueState(value);
const tr = document.createElement("tr");
const inp = document.createElement("input");
@ -90,16 +104,31 @@ function addMapRow(field = "", token = "") {
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-token";
const sel = document.createElement("select"); sel.className = "map-source";
const none = document.createElement("option"); none.value = ""; none.textContent = "— nichts —";
sel.appendChild(none);
MAIL_TOKENS.forEach((t) => {
const o = document.createElement("option");
o.value = t.key; o.textContent = t.label;
if (t.key === token) o.selected = true;
if (t.key === st.sel) o.selected = true;
sel.appendChild(o);
});
const tdT = document.createElement("td"); tdT.appendChild(sel);
const co = document.createElement("option");
co.value = CONST_OPT; co.textContent = "★ Fester Wert";
if (st.sel === CONST_OPT) co.selected = true;
sel.appendChild(co);
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";
sel.addEventListener("change", () => {
konst.style.display = sel.value === CONST_OPT ? "" : "none";
if (sel.value === CONST_OPT) 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";
@ -112,15 +141,21 @@ function addMapRow(field = "", token = "") {
function loadMapping(map) {
$("mapBody").innerHTML = "";
Object.entries(map || {}).forEach(([f, t]) => addMapRow(f, t));
Object.entries(map || {}).forEach(([f, v]) => addMapRow(f, v));
}
function collectMapping() {
const map = {};
document.querySelectorAll("#mapBody tr").forEach((tr) => {
const f = tr.querySelector(".map-field").value.trim();
const t = tr.querySelector(".map-token").value;
if (f && t) map[f] = t; // leere Feldnamen/Token ignorieren
if (!f) return; // ohne Feldname ignorieren
const sel = tr.querySelector(".map-source").value;
if (sel === CONST_OPT) {
const c = tr.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;
}
@ -132,6 +167,7 @@ function collect() {
partial.defaultCabinetId = $("defaultCabinet").value;
partial.mailFormat = getMailFormat();
partial.fieldMapping = collectMapping();
partial.mappingUrl = $("mappingUrl").value.trim();
return partial;
}
@ -162,6 +198,53 @@ $("test").addEventListener("click", async () => {
$("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;

View File

@ -21,6 +21,10 @@
{
"version": "0.10.4",
"update_link": "https://sylyx.xyz/sylyx/thunderbird2docuware/releases/download/v0.10.4/docuware-ablage-0.10.4.xpi"
},
{
"version": "0.10.5",
"update_link": "https://sylyx.xyz/sylyx/thunderbird2docuware/releases/download/v0.10.5/docuware-ablage-0.10.5.xpi"
}
]
}