Compare commits

..

No commits in common. "c7f205d9bce90ec3c4a5ba0032f15ec634ae99d1" and "f1c6d43613deed66fea7c8a5205898c012128b5c" have entirely different histories.

8 changed files with 80 additions and 215 deletions

View File

@ -51,9 +51,6 @@ textarea { resize: vertical; min-height: 56px; }
.field.prefilled input, .field.prefilled textarea { background: #f3f4f6; color: #4b5563; } .field.prefilled input, .field.prefilled textarea { background: #f3f4f6; color: #4b5563; }
.options { margin: 0; } .options { margin: 0; }
.format { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 5px 0; }
.format-label { font-weight: 500; }
.format .radio { margin: 0; }
.radio, .check { display: flex; align-items: center; gap: 8px; margin: 5px 0; font-weight: 400; } .radio, .check { display: flex; align-items: center; gap: 8px; margin: 5px 0; font-weight: 400; }
.radio input, .check input { accent-color: #2563eb; } .radio input, .check input { accent-color: #2563eb; }

View File

@ -68,12 +68,10 @@
<footer> <footer>
<section class="options"> <section class="options">
<div class="format"> <label class="radio"><input type="radio" name="scope" value="both" checked /> E-Mail und Anhänge</label>
<span class="format-label">E-Mail ablegen als:</span> <label class="radio"><input type="radio" name="scope" value="att" /> Nur Anhänge</label>
<label class="radio"><input type="radio" name="mailFormat" value="eml" checked /> Original (.eml)</label> <label class="radio"><input type="radio" name="scope" value="eml" /> Nur E-Mail</label>
<label class="radio"><input type="radio" name="mailFormat" value="pdf" /> PDF</label> <label class="check"><input type="checkbox" id="optPdf" /> Zusätzlich als PDF ablegen</label>
</div>
<label class="check"><input type="checkbox" id="optAtt" checked /> Anhänge mit ablegen</label>
<ul id="attList" class="att-list"></ul> <ul id="attList" class="att-list"></ul>
</section> </section>
<div id="status" class="muted"></div> <div id="status" class="muted"></div>

View File

@ -17,35 +17,33 @@ let STATE = {
cabinetFieldsByCab: {}, // Cache: cabinetId -> [Feldnamen] (für EML_ID-Erkennung) cabinetFieldsByCab: {}, // Cache: cabinetId -> [Feldnamen] (für EML_ID-Erkennung)
}; };
// Mail-Token -> Funktion, die den Wert aus den Mail-Metadaten liefert. // Mapping DocuWare-Feldname -> Funktion, die einen Vorbefüllwert aus meta liefert.
// Welches DocuWare-Feld welchen Token bekommt, steht im Mapping (s. effectiveFieldMap). const PREFILL = {
const TOKEN_FN = { EML_SENDER: (m) => m.senderEmail,
senderEmail: (m) => m.senderEmail, EML_SENDER_DISPLAYNAME: (m) => m.senderName,
senderName: (m) => m.senderName, EML_SENDER_NAME: (m) => m.senderName,
to: (m) => m.to, EML_RECEIVER: (m) => m.to,
cc: (m) => m.cc, EML_CC: (m) => m.cc,
bcc: (m) => m.bcc, EML_BCC: (m) => m.bcc,
subject: (m) => m.subject, EML_SUBJECT: (m) => m.subject,
body: (m) => m.bodyText, DOC_SUBJECT: (m) => m.subject,
date: (m) => m.date, EML_BODY: (m) => m.bodyText,
direction: (m) => m.direction, EML_SENDINGDATE: (m) => m.date,
size: (m) => m.sizeBytes, EML_DISPLAYDATE: (m) => m.date,
account: (m) => m.account, EML_RECEIVINGDATE: (m) => m.date,
EML_DIRECTION: (m) => m.direction,
EML_SIZE: (m) => m.sizeBytes,
EML_ACCOUNT: (m) => m.account,
DOC_DATE: (m) => m.date,
// Naheliegende generische Feldnamen (Schränke ohne EML_*-Schema)
SUBJECT: (m) => m.subject,
BETREFF: (m) => m.subject,
EMAIL: (m) => m.senderEmail,
E_MAIL: (m) => m.senderEmail,
DATE: (m) => m.date,
DATUM: (m) => m.date,
}; };
// Aktives Feld-Mapping: eigenes (aus den Einstellungen) wenn gesetzt, sonst Default.
function effectiveFieldMap() {
const m = STATE.settings && STATE.settings.fieldMapping;
return m && Object.keys(m).length ? m : DEFAULT_FIELD_MAP;
}
// Vorbefüllwert für ein DocuWare-Feld aus den Mail-Metadaten (oder undefined).
function prefillFor(fieldName, meta) {
const token = effectiveFieldMap()[fieldName];
const fn = token && TOKEN_FN[token];
return fn ? fn(meta) : undefined;
}
function status(msg, kind) { function status(msg, kind) {
const el = $("status"); const el = $("status");
el.textContent = msg || ""; el.textContent = msg || "";
@ -155,24 +153,28 @@ function askPassword(errMsg) {
} }
function applyDefaults() { function applyDefaults() {
// Mail-Format (.eml ODER PDF) aus den Einstellungen. $("optPdf").checked = STATE.settings.storePdf;
setMailFormat(STATE.settings.mailFormat === "pdf" ? "pdf" : "eml");
// Anhänge-Haken nur sinnvoll, wenn die Mail überhaupt Anhänge hat.
const hasAtt = STATE.attachments.length > 0; const hasAtt = STATE.attachments.length > 0;
const att = $("optAtt"); // Standard-Umfang aus den Einstellungen ableiten.
att.checked = hasAtt && STATE.settings.storeAttachments !== false; let scope = "both";
att.disabled = !hasAtt; if (STATE.settings.storeEml && !STATE.settings.storeAttachments) scope = "eml";
$("attList").style.display = hasAtt && att.checked ? "" : "none"; else if (!STATE.settings.storeEml && STATE.settings.storeAttachments) scope = "att";
if (!hasAtt) scope = "eml"; // ohne Anhänge nur E-Mail sinnvoll
setScope(scope);
// Optionen, die Anhänge erfordern, deaktivieren wenn keine vorhanden.
document.querySelectorAll('input[name="scope"]').forEach((r) => {
if ((r.value === "att" || r.value === "both") && !hasAtt) r.disabled = true;
});
} }
function setMailFormat(value) { function setScope(value) {
const el = document.querySelector(`input[name="mailFormat"][value="${value}"]`); const el = document.querySelector(`input[name="scope"][value="${value}"]`);
if (el) el.checked = true; if (el) el.checked = true;
} }
function getMailFormat() { function getScope() {
const el = document.querySelector('input[name="mailFormat"]:checked'); const el = document.querySelector('input[name="scope"]:checked');
return el ? el.value : "eml"; return el ? el.value : "both";
} }
function renderAttachments() { function renderAttachments() {
@ -442,7 +444,9 @@ function applyFieldDefaults() {
function prefillFields() { function prefillFields() {
STATE.fields.forEach((f) => { STATE.fields.forEach((f) => {
const val = prefillFor(f.name, STATE.meta); const fn = PREFILL[f.name];
if (!fn) return;
const val = fn(STATE.meta);
if (val === undefined || val === null || val === "") return; if (val === undefined || val === null || val === "") return;
setFieldValue(f.name, f, val); setFieldValue(f.name, f, val);
}); });
@ -541,11 +545,15 @@ async function submit() {
result = collected.result; result = collected.result;
} }
const fmt = getMailFormat(); // "eml" ODER "pdf" nie beides const scope = getScope();
const wantEml = fmt === "eml"; const wantEml = scope !== "att";
const wantPdf = fmt === "pdf"; const wantPdf = $("optPdf").checked;
const att = $("optAtt"); const wantAttScope = scope !== "eml";
const wantAttScope = att.checked && !att.disabled;
if (!wantEml && !wantAttScope) {
status("Nichts ausgewählt zum Ablegen.", "err");
return;
}
// Dubletten-Warnung (nur Einzelmail): liegt die Mail im Ziel schon, warnen // Dubletten-Warnung (nur Einzelmail): liegt die Mail im Ziel schon, warnen
// „Trotzdem ablegen" bleibt aber möglich. // „Trotzdem ablegen" bleibt aber möglich.
@ -567,7 +575,7 @@ async function submit() {
} }
} }
await saveOptionPrefs(fmt, wantAttScope); // Sticky: Format + Anhänge-Wahl merken await saveOptionPrefs(scope, wantPdf); // Sticky: Umfang merken
$("submit").disabled = true; $("submit").disabled = true;
const multi = STATE.messageIds.length > 1; const multi = STATE.messageIds.length > 1;
@ -642,17 +650,21 @@ function fieldsForMessage(baseResult, meta, multi) {
if (!multi) return baseResult; // Einzelmail: manuelle Edits respektieren if (!multi) return baseResult; // Einzelmail: manuelle Edits respektieren
const result = { ...baseResult }; const result = { ...baseResult };
STATE.fields.forEach((f) => { STATE.fields.forEach((f) => {
const val = prefillFor(f.name, meta); const fn = PREFILL[f.name];
if (!fn) return;
const val = fn(meta);
if (val === undefined || val === null || val === "") return; if (val === undefined || val === null || val === "") return;
result[f.name] = { value: val, type: f.type }; result[f.name] = { value: val, type: f.type };
}); });
return result; return result;
} }
// Sticky: zuletzt genutztes Format + Anhänge-Wahl in den Einstellungen merken. // Sticky: zuletzt genutzten Ablage-Umfang in den Einstellungen merken.
async function saveOptionPrefs(mailFormat, storeAttachments) { async function saveOptionPrefs(scope, wantPdf) {
const storeEml = scope !== "att";
const storeAttachments = scope !== "eml";
try { try {
STATE.settings = await Settings.set({ mailFormat, storeAttachments }); STATE.settings = await Settings.set({ storeEml, storeAttachments, storePdf: wantPdf });
} catch (_) { /* nicht kritisch */ } } catch (_) { /* nicht kritisch */ }
} }
@ -716,13 +728,11 @@ $("reset").addEventListener("click", resetForm);
$("cancel").addEventListener("click", () => window.close()); $("cancel").addEventListener("click", () => window.close());
// Sticky: Optionen sofort bei Änderung merken (nicht erst beim Ablegen). // Sticky: Optionen sofort bei Änderung merken (nicht erst beim Ablegen).
document.querySelectorAll('input[name="mailFormat"]').forEach((r) => document.querySelectorAll('input[name="scope"]').forEach((r) =>
r.addEventListener("change", () => saveOptionPrefs(getMailFormat(), $("optAtt").checked)) r.addEventListener("change", () => saveOptionPrefs(getScope(), $("optPdf").checked))
);
$("optPdf").addEventListener("change", () =>
saveOptionPrefs(getScope(), $("optPdf").checked)
); );
$("optAtt").addEventListener("change", () => {
const show = $("optAtt").checked && STATE.attachments.length > 0;
$("attList").style.display = show ? "" : "none";
saveOptionPrefs(getMailFormat(), $("optAtt").checked);
});
init(); init();

View File

@ -9,65 +9,16 @@ const DEFAULTS = {
username: "", username: "",
defaultCabinetId: "", defaultCabinetId: "",
dialogByCabinet: {}, // gemerkter Ablagedialog je Schrank: { [cabinetId]: dialogId } dialogByCabinet: {}, // gemerkter Ablagedialog je Schrank: { [cabinetId]: dialogId }
mailFormat: "eml", // E-Mail wird als "eml" ODER "pdf" abgelegt (nie beides) storeEml: true,
storePdf: true,
storeAttachments: true, storeAttachments: true,
tagOnSuccess: true, tagOnSuccess: true,
fieldMapping: {}, // DocuWare-Feldname -> Mail-Attribut (Token). Leer = DEFAULT_FIELD_MAP.
};
// Mögliche Mail-Attribute für die Vorbefüllung (Token + Anzeigename).
// Wird von dialog.js (Werte aus meta) und options.js (Mapping-Editor) genutzt.
const MAIL_TOKENS = [
{ key: "senderEmail", label: "Absender E-Mail" },
{ key: "senderName", label: "Absender Name" },
{ key: "to", label: "Empfänger (An)" },
{ key: "cc", label: "CC" },
{ key: "bcc", label: "BCC" },
{ key: "subject", label: "Betreff" },
{ key: "body", label: "Text (Body)" },
{ key: "date", label: "Datum" },
{ key: "direction", label: "Richtung (Ein-/Ausgang)" },
{ key: "size", label: "Größe (Bytes)" },
{ key: "account", label: "Konto" },
];
// Standard-Zuordnung DocuWare-Feldname -> Mail-Token. Greift, solange der Nutzer
// in den Einstellungen kein eigenes fieldMapping gespeichert hat.
const DEFAULT_FIELD_MAP = {
EML_SENDER: "senderEmail",
EML_SENDER_DISPLAYNAME: "senderName",
EML_SENDER_NAME: "senderName",
EML_RECEIVER: "to",
EML_CC: "cc",
EML_BCC: "bcc",
EML_SUBJECT: "subject",
DOC_SUBJECT: "subject",
EML_BODY: "body",
EML_SENDINGDATE: "date",
EML_DISPLAYDATE: "date",
EML_RECEIVINGDATE: "date",
EML_DIRECTION: "direction",
EML_SIZE: "size",
EML_ACCOUNT: "account",
DOC_DATE: "date",
SUBJECT: "subject",
BETREFF: "subject",
EMAIL: "senderEmail",
E_MAIL: "senderEmail",
DATE: "date",
DATUM: "date",
}; };
const Settings = { const Settings = {
async get() { async get() {
const stored = await browser.storage.local.get("settings"); const stored = await browser.storage.local.get("settings");
const raw = stored.settings || {}; return { ...DEFAULTS, ...(stored.settings || {}) };
const s = { ...DEFAULTS, ...raw };
// Migration: altes storeEml/storePdf -> mailFormat (einmalig, nicht persistiert).
if (raw.mailFormat === undefined && raw.storePdf && !raw.storeEml) {
s.mailFormat = "pdf";
}
return s;
}, },
async set(partial) { async set(partial) {
@ -87,5 +38,4 @@ const Settings = {
}, },
}; };
if (typeof module !== "undefined") if (typeof module !== "undefined") module.exports = { Settings, DEFAULTS };
module.exports = { Settings, DEFAULTS, MAIL_TOKENS, DEFAULT_FIELD_MAP };

View File

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

View File

@ -17,13 +17,6 @@
#status { font-size: 13px; min-height: 18px; } #status { font-size: 13px; min-height: 18px; }
.ok { color: #15803d; } .err { color: #b91c1c; } .ok { color: #15803d; } .err { color: #b91c1c; }
.hint { font-size: 12px; color: #666; margin-top: 2px; } .hint { font-size: 12px; color: #666; margin-top: 2px; }
table.map { width: 100%; border-collapse: collapse; margin-top: 8px; }
table.map th { text-align: left; font-size: 12px; color: #666; font-weight: 600; padding: 2px 4px; }
table.map td { padding: 3px 4px; vertical-align: middle; }
table.map input, table.map select { width: 100%; padding: 5px 7px; border: 1px solid #ccc; border-radius: 5px; box-sizing: border-box; }
table.map td.del { width: 36px; }
table.map button { padding: 5px 9px; }
button.secondary { background: #e5e7eb; color: #111; }
</style> </style>
</head> </head>
<body> <body>
@ -55,12 +48,9 @@
</select> </select>
<div class="hint">Liste wird nach erfolgreichem Verbindungstest gefüllt.</div> <div class="hint">Liste wird nach erfolgreichem Verbindungstest gefüllt.</div>
<label>Standard-Format der E-Mail</label> <div class="check"><input type="checkbox" id="storeEml" /> <label for="storeEml" style="margin:0">E-Mail als .eml ablegen</label></div>
<div class="check"><input type="radio" name="mailFormat" id="fmtEml" value="eml" /> <label for="fmtEml" style="margin:0">Original (.eml)</label></div> <div class="check"><input type="checkbox" id="storePdf" /> <label for="storePdf" style="margin:0">E-Mail als PDF ablegen</label></div>
<div class="check"><input type="radio" name="mailFormat" id="fmtPdf" value="pdf" /> <label for="fmtPdf" style="margin:0">PDF</label></div> <div class="check"><input type="checkbox" id="storeAttachments" /> <label for="storeAttachments" style="margin:0">Anhänge separat ablegen</label></div>
<div class="hint">Die E-Mail wird immer in genau einem Format abgelegt (nicht beides). Im Ablage-Fenster umstellbar.</div>
<div class="check" style="margin-top:10px"><input type="checkbox" id="storeAttachments" /> <label for="storeAttachments" style="margin:0">Anhänge mit ablegen</label></div>
<div class="check"><input type="checkbox" id="tagOnSuccess" /> <label for="tagOnSuccess" style="margin:0">Mail nach Ablage als „DocuWare" markieren</label></div> <div class="check"><input type="checkbox" id="tagOnSuccess" /> <label for="tagOnSuccess" style="margin:0">Mail nach Ablage als „DocuWare" markieren</label></div>
</fieldset> </fieldset>
@ -70,21 +60,6 @@
<span id="status"></span> <span id="status"></span>
</div> </div>
<fieldset>
<legend>Feld-Zuordnung (Vorbefüllung)</legend>
<div class="hint">Legt fest, welches DocuWare-Indexfeld aus welchem Mail-Attribut vorbefüllt
wird. <strong>Feldname</strong> = DocuWare-Datenbankname (z. B. <code>EML_SUBJECT</code>).
Gilt für alle Aktenschränke. Leere/entfernte Zeilen befüllen nichts vor.</div>
<table class="map">
<thead><tr><th>DocuWare-Feldname</th><th>Mail-Attribut</th><th></th></tr></thead>
<tbody id="mapBody"></tbody>
</table>
<div class="row">
<button id="mapAdd" class="secondary" type="button">+ Zeile</button>
<button id="mapReset" class="secondary" type="button">Auf Standard zurücksetzen</button>
</div>
</fieldset>
<fieldset> <fieldset>
<legend>Einstellungen sichern</legend> <legend>Einstellungen sichern</legend>
<div class="hint">Exportiert/importiert die Einstellungen (Server, Organisation, <div class="hint">Exportiert/importiert die Einstellungen (Server, Organisation,

View File

@ -4,7 +4,7 @@ const $ = (id) => document.getElementById(id);
// Passwort ist bewusst NICHT dabei es wird nie gespeichert, nur zum Testen/Login // Passwort ist bewusst NICHT dabei es wird nie gespeichert, nur zum Testen/Login
// transient verwendet (siehe "Verbindung testen"). // transient verwendet (siehe "Verbindung testen").
const FIELDS = ["serverUrl", "organization", "username"]; const FIELDS = ["serverUrl", "organization", "username"];
const CHECKS = ["storeAttachments", "tagOnSuccess"]; const CHECKS = ["storeEml", "storePdf", "storeAttachments", "tagOnSuccess"];
function setStatus(msg, kind) { function setStatus(msg, kind) {
const el = $("status"); const el = $("status");
@ -16,67 +16,11 @@ async function load() {
const s = await Settings.get(); const s = await Settings.get();
FIELDS.forEach((f) => ($(f).value = s[f] || "")); FIELDS.forEach((f) => ($(f).value = s[f] || ""));
CHECKS.forEach((c) => ($(c).checked = !!s[c])); CHECKS.forEach((c) => ($(c).checked = !!s[c]));
setMailFormat(s.mailFormat === "pdf" ? "pdf" : "eml");
loadMapping(s.fieldMapping && Object.keys(s.fieldMapping).length ? s.fieldMapping : DEFAULT_FIELD_MAP);
// 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);
} }
function setMailFormat(v) {
const el = document.querySelector(`input[name="mailFormat"][value="${v}"]`);
if (el) el.checked = true;
}
function getMailFormat() {
const el = document.querySelector('input[name="mailFormat"]:checked');
return el ? el.value : "eml";
}
// --- Feld-Mapping-Editor ---------------------------------------------------
function addMapRow(field = "", token = "") {
const tr = document.createElement("tr");
const inp = document.createElement("input");
inp.type = "text"; inp.className = "map-field"; inp.value = field;
inp.placeholder = "EML_SUBJECT";
const tdF = document.createElement("td"); tdF.appendChild(inp);
const sel = document.createElement("select"); sel.className = "map-token";
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;
sel.appendChild(o);
});
const tdT = document.createElement("td"); tdT.appendChild(sel);
const del = document.createElement("button");
del.type = "button"; del.className = "secondary"; del.textContent = "✕";
del.addEventListener("click", () => tr.remove());
const tdX = document.createElement("td"); tdX.className = "del"; tdX.appendChild(del);
tr.append(tdF, tdT, tdX);
$("mapBody").appendChild(tr);
}
function loadMapping(map) {
$("mapBody").innerHTML = "";
Object.entries(map || {}).forEach(([f, t]) => addMapRow(f, t));
}
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
});
return map;
}
function fillCabinets(cabinets, selectedId) { function fillCabinets(cabinets, selectedId) {
const sel = $("defaultCabinet"); const sel = $("defaultCabinet");
sel.innerHTML = '<option value="">— keiner —</option>'; sel.innerHTML = '<option value="">— keiner —</option>';
@ -102,14 +46,9 @@ function collect() {
FIELDS.forEach((f) => (partial[f] = $(f).value.trim())); FIELDS.forEach((f) => (partial[f] = $(f).value.trim()));
CHECKS.forEach((c) => (partial[c] = $(c).checked)); CHECKS.forEach((c) => (partial[c] = $(c).checked));
partial.defaultCabinetId = $("defaultCabinet").value; partial.defaultCabinetId = $("defaultCabinet").value;
partial.mailFormat = getMailFormat();
partial.fieldMapping = collectMapping();
return partial; return partial;
} }
$("mapAdd").addEventListener("click", () => addMapRow());
$("mapReset").addEventListener("click", () => loadMapping(DEFAULT_FIELD_MAP));
$("save").addEventListener("click", async () => { $("save").addEventListener("click", async () => {
await Settings.set(collect()); await Settings.set(collect());
setStatus("Gespeichert.", "ok"); setStatus("Gespeichert.", "ok");

View File

@ -13,10 +13,6 @@
{ {
"version": "0.10.2", "version": "0.10.2",
"update_link": "https://sylyx.xyz/sylyx/thunderbird2docuware/releases/download/v0.10.2/docuware-ablage-0.10.2.xpi" "update_link": "https://sylyx.xyz/sylyx/thunderbird2docuware/releases/download/v0.10.2/docuware-ablage-0.10.2.xpi"
},
{
"version": "0.10.3",
"update_link": "https://sylyx.xyz/sylyx/thunderbird2docuware/releases/download/v0.10.3/docuware-ablage-0.10.3.xpi"
} }
] ]
} }