diff --git a/dialog/dialog.js b/dialog/dialog.js
index f831aba..2c1470e 100644
--- a/dialog/dialog.js
+++ b/dialog/dialog.js
@@ -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;
}
diff --git a/lib/store.js b/lib/store.js
index 6c6cfab..b175436 100644
--- a/lib/store.js
+++ b/lib/store.js
@@ -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).
diff --git a/options/options.html b/options/options.html
index d0ea903..1509bdc 100644
--- a/options/options.html
+++ b/options/options.html
@@ -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 @@
+ Zeile
Auf Standard zurücksetzen
+
Als Mail-Attribut „Fester Wert "
+ wählen, um einen festen Text (z. B. NEU) ins Feld zu schreiben.
+
+
+ Mapping teilen / übernehmen
+
+ Aus Repo/URL laden
+
+
+ Von URL laden
+
+ 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.)
+
+ JSON (kopieren / einfügen)
+
+
+ Aktuelles als JSON
+ Kopieren
+ JSON anwenden
+
+
diff --git a/options/options.js b/options/options.js
index 25e4f8e..1215d1c 100644
--- a/options/options.js
+++ b/options/options.js
@@ -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;