diff --git a/dialog/dialog.js b/dialog/dialog.js
index 391b37e..f95ec0d 100644
--- a/dialog/dialog.js
+++ b/dialog/dialog.js
@@ -27,6 +27,8 @@ let STATE = {
// Mail-Token -> Funktion, die den Wert aus den Mail-Metadaten liefert.
// Welches DocuWare-Feld welchen Token bekommt, steht im Mapping (s. effectiveFieldMap).
+// Zweites Argument ctx trägt Werte, die nicht an der Mail hängen, sondern am
+// konkreten Upload: { fileName, attachmentCount }.
const TOKEN_FN = {
senderEmail: (m) => m.senderEmail,
senderName: (m) => m.senderName,
@@ -39,8 +41,34 @@ const TOKEN_FN = {
direction: (m) => m.direction,
size: (m) => m.sizeBytes,
account: (m) => m.account,
+
+ // Datum/Zeit. date liefert den Kalendertag, dateTime den echten Zeitpunkt –
+ // in DocuWare braucht letzteres ein Feld vom Typ DateTime, sonst schneidet
+ // der Server die Uhrzeit ab.
+ dateTime: (m) => m.date,
+ time: (m) => fmtTime(m.date),
+ // archiveDate ist bewusst auf Mitternacht gesetzt: so bleibt es „nur Datum",
+ // auch wenn es auf ein DateTime-Feld gemappt wird. archiveDateTime trägt die Uhrzeit.
+ archiveDate: () => { const n = new Date(); return new Date(n.getFullYear(), n.getMonth(), n.getDate()); },
+ archiveDateTime: () => new Date(),
+
+ // Dokumentbezogen
+ fileName: (m, ctx) => (ctx && ctx.fileName) || "",
+ attachmentCount: (m, ctx) => (ctx && ctx.attachmentCount) || 0,
+ rfcMessageId: (m) => DocuWare.normalizeMessageId(m.rfcMessageId),
+
+ // Wer/Woher – aus den Einstellungen bzw. dem Thunderbird-Konto.
+ dwUser: () => (STATE.settings && STATE.settings.username) || "",
+ dwOrganization: () => (STATE.settings && STATE.settings.organization) || "",
+ accountEmail: (m) => m.accountEmail || "",
+ folder: (m) => m.folder || "",
};
+// Tokens, deren Wert vom einzelnen hochgeladenen Dokument abhängt. Felder, die
+// darauf gemappt sind, werden je Upload neu gesetzt – sonst trüge jeder Anhang
+// den Dateinamen der E-Mail.
+const PER_FILE_TOKENS = new Set(["fileName"]);
+
// Aktives Feld-Mapping: eigenes (aus den Einstellungen) wenn gesetzt, sonst Default.
function effectiveFieldMap() {
const m = STATE.settings && STATE.settings.fieldMapping;
@@ -49,16 +77,23 @@ function effectiveFieldMap() {
// 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) {
+function prefillFor(fieldName, meta, ctx) {
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);
+ if (m.token && TOKEN_FN[m.token]) return TOKEN_FN[m.token](meta, ctx);
return undefined;
}
const fn = TOKEN_FN[m];
- return fn ? fn(meta) : undefined;
+ return fn ? fn(meta, ctx) : undefined;
+}
+
+// Auf welchen Token ist ein Feld gemappt? (String-Form oder { token }.)
+function tokenOf(fieldName) {
+ const m = effectiveFieldMap()[fieldName];
+ if (typeof m === "string") return m;
+ return m && typeof m === "object" ? m.token : undefined;
}
function status(msg, kind) {
@@ -461,6 +496,10 @@ function buildInput(f) {
// Nur Listenwerte erlaubt -> starres Dropdown.
el = document.createElement("select");
el.innerHTML = '';
+ } else if (t.includes("datetime")) {
+ // DateTime-Feld: Tag UND Uhrzeit erfassbar (vor dem reinen date-Zweig prüfen).
+ el = document.createElement("input");
+ el.type = "datetime-local";
} else if (t.includes("date")) {
el = document.createElement("input");
el.type = "date";
@@ -493,10 +532,18 @@ function applyFieldDefaults() {
}
function prefillFields() {
+ const ctx = formCtx();
STATE.fields.forEach((f) => {
- const val = prefillFor(f.name, STATE.meta);
+ const val = prefillFor(f.name, STATE.meta, ctx);
if (val === undefined || val === null || val === "") return;
setFieldValue(f.name, f, val);
+ // Dateiname-Felder sind nur eine Vorschau: beim Ablegen trägt jeder Anhang
+ // seinen eigenen Namen. Im Tooltip erklären, statt still zu überschreiben.
+ if (PER_FILE_TOKENS.has(tokenOf(f.name))) {
+ const wrap = document.querySelector(`.field[data-name="${cssEsc(f.name)}"]`);
+ const el = wrap && wrap.querySelector("[data-role='value']");
+ if (el) el.title = "Wird beim Ablegen je Dokument gesetzt (Anhänge tragen ihren eigenen Dateinamen).";
+ }
});
}
@@ -505,9 +552,9 @@ function setFieldValue(name, f, val) {
const wrap = document.querySelector(`.field[data-name="${cssEsc(name)}"]`);
const el = wrap && wrap.querySelector("[data-role='value']");
if (!el) return;
- if (el.type === "date") {
+ if (el.type === "date" || el.type === "datetime-local") {
const d = val instanceof Date ? val : new Date(val);
- if (!isNaN(d)) el.value = toDateInput(d);
+ if (!isNaN(d)) el.value = el.type === "date" ? toDateInput(d) : toDateTimeInput(d);
} else if (el.tagName === "SELECT") {
let opt = Array.from(el.options).find((o) => o.value === String(val));
if (!opt) {
@@ -576,8 +623,12 @@ function collectFieldValues() {
}
if (String(raw).trim() !== "") {
// Date-Input als LOKALE Mitternacht parsen (raw+"T00:00:00"), sonst
- // interpretiert new Date("YYYY-MM-DD") als UTC.
- const value = el.type === "date" ? new Date(`${raw}T00:00:00`) : raw;
+ // interpretiert new Date("YYYY-MM-DD") als UTC. datetime-local ist bereits
+ // lokal und wird direkt geparst.
+ const value =
+ el.type === "date" ? new Date(`${raw}T00:00:00`)
+ : el.type === "datetime-local" ? new Date(raw)
+ : raw;
result[name] = { value, type };
}
});
@@ -643,7 +694,8 @@ async function submit() {
mid === STATE.messageId ? STATE.attachments : await Mail.listAttachments(mid);
// Indexfelder: manuelle Werte für alle gleich; gemappte Felder je Mail neu.
- const fields = STATE.isBasket ? {} : fieldsForMessage(result, meta, multi);
+ const msgCtx = { attachmentCount: atts.length };
+ const fields = STATE.isBasket ? {} : fieldsForMessage(result, meta, multi, msgCtx);
// Message-ID für Dedup/„bereits abgelegt" mitschreiben (wenn Schrank EML_ID hat).
if (!STATE.isBasket && meta.rfcMessageId && fieldNames.includes("EML_ID")) {
fields.EML_ID = { value: DocuWare.normalizeMessageId(meta.rfcMessageId), type: "Text" };
@@ -654,14 +706,16 @@ async function submit() {
await uploadOnce(`${mid}|eml`, async () => {
status(`${prefix}E-Mail …`);
const eml = await Mail.getEmlFile(mid, meta.subject);
- await DocuWare.uploadDocument(STATE.settings, STATE.cabinetId, eml, eml.name, fields);
+ const ef = applyPerFileFields(fields, meta, { ...msgCtx, fileName: eml.name });
+ await DocuWare.uploadDocument(STATE.settings, STATE.cabinetId, eml, eml.name, ef);
});
}
if (wantPdf) {
await uploadOnce(`${mid}|pdf`, async () => {
status(`${prefix}PDF …`);
const pdf = Pdf.fromMail(meta);
- await DocuWare.uploadDocument(STATE.settings, STATE.cabinetId, pdf, pdf.name, fields);
+ const pf = applyPerFileFields(fields, meta, { ...msgCtx, fileName: pdf.name });
+ await DocuWare.uploadDocument(STATE.settings, STATE.cabinetId, pdf, pdf.name, pf);
});
}
if (wantAtt) {
@@ -673,7 +727,14 @@ async function submit() {
await uploadOnce(`${mid}|att:${a.partName}`, async () => {
status(`${prefix}Anhang ${a.name} …`);
const file = await Mail.getAttachmentFile(mid, a.partName);
- const af = STATE.isBasket ? {} : { ...fields, DOC_FILE_NAME: { value: a.name, type: "Text" } };
+ // Anhang trägt seinen EIGENEN Dateinamen – sowohl in gemappten
+ // fileName-Feldern als auch im festen DOC_FILE_NAME.
+ const af = STATE.isBasket
+ ? {}
+ : {
+ ...applyPerFileFields(fields, meta, { ...msgCtx, fileName: a.name }),
+ DOC_FILE_NAME: { value: a.name, type: "Text" },
+ };
await DocuWare.uploadDocument(STATE.settings, STATE.cabinetId, file, a.name, af);
});
}
@@ -720,17 +781,32 @@ async function uploadOnce(part, doUpload) {
// Indexfelder für eine konkrete Nachricht: gemappte Felder (EML_*/generisch)
// werden je Mail aus deren Metadaten neu berechnet, manuelle Werte bleiben.
-function fieldsForMessage(baseResult, meta, multi) {
+function fieldsForMessage(baseResult, meta, multi, ctx) {
if (!multi) return baseResult; // Einzelmail: manuelle Edits respektieren
const result = { ...baseResult };
STATE.fields.forEach((f) => {
- const val = prefillFor(f.name, meta);
+ const val = prefillFor(f.name, meta, ctx);
if (val === undefined || val === null || val === "") return;
result[f.name] = { value: val, type: f.type };
});
return result;
}
+// Felder, die auf ein dokumentabhängiges Token (fileName) gemappt sind, für
+// GENAU dieses Dokument setzen – auch bei Einzelmail, weil ein Formularwert
+// nicht gleichzeitig für .eml und jeden Anhang stimmen kann.
+function applyPerFileFields(fields, meta, ctx) {
+ if (STATE.isBasket) return fields; // Briefkorb: keine Indexfelder
+ const out = { ...fields };
+ STATE.fields.forEach((f) => {
+ if (!PER_FILE_TOKENS.has(tokenOf(f.name))) return;
+ const val = prefillFor(f.name, meta, ctx);
+ if (val === undefined || val === null || val === "") return;
+ out[f.name] = { value: val, type: f.type };
+ });
+ return out;
+}
+
// Sticky: zuletzt genutztes Format + Anhänge-Wahl in den Einstellungen merken.
async function saveOptionPrefs(mailFormat, storeAttachments) {
try {
@@ -780,6 +856,27 @@ const toDateInput = (d) => {
const day = String(d.getDate()).padStart(2, "0");
return `${y}-${m}-${day}`;
};
+// Lokal als YYYY-MM-DDTHH:MM für .
+const toDateTimeInput = (d) => `${toDateInput(d)}T${fmtTime(d)}`;
+// Uhrzeit HH:MM lokal (auch als eigenständiger Text-Token nutzbar).
+function fmtTime(d) {
+ const dt = d instanceof Date ? d : new Date(d);
+ if (isNaN(dt)) return "";
+ return `${String(dt.getHours()).padStart(2, "0")}:${String(dt.getMinutes()).padStart(2, "0")}`;
+}
+
+// Dateiname, den die E-Mail selbst beim Upload bekäme – für die Vorschau im
+// Formular. Beim tatsächlichen Upload gilt der echte Name (siehe submit()).
+function previewFileName() {
+ const subj = (STATE.meta && STATE.meta.subject) || "";
+ const base = subj.replace(/[\\/:*?"<>|]+/g, "_").slice(0, 80).trim() || "email";
+ return `${base}.${getMailFormat() === "pdf" ? "pdf" : "eml"}`;
+}
+
+// Kontext für die Token-Auflösung im Formular (Vorschauwerte).
+function formCtx() {
+ return { fileName: previewFileName(), attachmentCount: STATE.attachments.length };
+}
// Sicheres CSS-Escaping für Attribut-Selektoren (Feldnamen aus DocuWare).
const cssEsc = (s) =>
window.CSS && CSS.escape ? CSS.escape(String(s)) : String(s).replace(/["\\\]]/g, "\\$&");
@@ -811,8 +908,21 @@ $("cancel").addEventListener("click", () => window.close());
// Sticky: Optionen sofort bei Änderung merken (nicht erst beim Ablegen).
document.querySelectorAll('input[name="mailFormat"]').forEach((r) =>
- r.addEventListener("change", () => saveOptionPrefs(getMailFormat(), $("optAtt").checked))
+ r.addEventListener("change", () => {
+ refreshPerFilePreview(); // .eml ↔ .pdf: Dateiname-Vorschau nachziehen
+ saveOptionPrefs(getMailFormat(), $("optAtt").checked);
+ })
);
+
+// Aktualisiert die Vorschauwerte dokumentabhängiger Felder (Dateiname).
+function refreshPerFilePreview() {
+ const ctx = formCtx();
+ STATE.fields.forEach((f) => {
+ if (!PER_FILE_TOKENS.has(tokenOf(f.name))) return;
+ const val = prefillFor(f.name, STATE.meta, ctx);
+ if (val !== undefined && val !== null && val !== "") setFieldValue(f.name, f, val);
+ });
+}
$("optAtt").addEventListener("change", () => {
const show = !isMulti() && $("optAtt").checked && STATE.attachments.length > 0;
$("attList").style.display = show ? "" : "none";
diff --git a/lib/docuware.js b/lib/docuware.js
index 53aff50..da87194 100644
--- a/lib/docuware.js
+++ b/lib/docuware.js
@@ -429,6 +429,21 @@ const DocuWare = {
_field(name, value, type) {
const t = (type || "Text").toLowerCase();
+ // WICHTIG: "datetime" zuerst prüfen – "DateTime".includes("date") ist true und
+ // würde sonst die Uhrzeit auf Mitternacht normalisieren (Datenverlust).
+ if (t.includes("datetime")) {
+ const d = value instanceof Date ? value : new Date(value);
+ if (isNaN(d)) return { FieldName: name, Item: String(value), ItemElementName: "DateTime" };
+ // Mit Uhrzeit: echter Zeitpunkt, daher unverändert als ISO (UTC) senden.
+ //
+ // UNGEPRÜFT gegen den Server: ob DocuWare das "Z" auswertet (dann stimmt die
+ // Uhrzeit) oder die ISO-Zeichenkette als lokale Wanduhrzeit übernimmt (dann
+ // erscheint sie um den UTC-Versatz verschoben, in Wien -2 h). Reine Date-Felder
+ // sind davon nicht betroffen, die werden auf UTC-Mitternacht normalisiert.
+ // Falls im Testarchiv verschoben: hier stattdessen die lokale Wanduhrzeit ohne
+ // Zeitzone senden, also `${toLocalIso(d)}` statt d.toISOString().
+ return { FieldName: name, Item: d.toISOString(), ItemElementName: "DateTime" };
+ }
if (t.includes("date")) {
// DocuWare erwartet /Date(ms)/ oder ISO; ISO funktioniert bei Platform.
const d = value instanceof Date ? value : new Date(value);
diff --git a/lib/mail.js b/lib/mail.js
index 29a4833..15d5b61 100644
--- a/lib/mail.js
+++ b/lib/mail.js
@@ -16,9 +16,11 @@ const Mail = {
const bodyText = this._extractText(full);
const sender = this._parseAddress(header.author);
- const account = await this._accountForMessage(messageId);
+ const acc = await this._accountInfo(messageId);
return {
+ folder: (header.folder && (header.folder.name || header.folder.path)) || "",
+ accountEmail: acc.email,
rfcMessageId: this._header(full, "message-id"), // RFC822 Message-ID (Dedup-Schlüssel)
subject: header.subject || "",
senderEmail: sender.email,
@@ -28,7 +30,7 @@ const Mail = {
bcc: (header.bccList || []).join(", "),
date: header.date instanceof Date ? header.date : new Date(header.date),
bodyText,
- account,
+ account: acc.name,
direction: this._direction(header),
sizeBytes: header.size || 0,
};
@@ -128,16 +130,23 @@ const Mail = {
return { name: "", email: str.trim() };
},
- async _accountForMessage(messageId) {
+ /**
+ * Konto-Name und eigene E-Mail-Adresse (erste Identität) zur Nachricht.
+ * @returns {Promise<{name:string, email:string}>}
+ */
+ async _accountInfo(messageId) {
try {
const msg = await browser.messages.get(messageId);
const folder = msg.folder;
if (folder && folder.accountId) {
const acc = await browser.accounts.get(folder.accountId);
- return acc ? acc.name : "";
+ if (acc) {
+ const ident = (acc.identities || [])[0];
+ return { name: acc.name || "", email: (ident && ident.email) || "" };
+ }
}
} catch (_) {}
- return "";
+ return { name: "", email: "" };
},
_direction(header) {
diff --git a/lib/store.js b/lib/store.js
index 27f1532..ca81792 100644
--- a/lib/store.js
+++ b/lib/store.js
@@ -32,6 +32,27 @@ const MAIL_TOKENS = [
{ key: "direction", label: "Richtung (Ein-/Ausgang)" },
{ key: "size", label: "Größe (Bytes)" },
{ key: "account", label: "Konto" },
+
+ // --- Datum/Zeit ---------------------------------------------------------
+ // "date" liefert nur den Kalendertag. Für DocuWare-Felder vom Typ DateTime
+ // gibt es "dateTime" (Tag + Uhrzeit); "time" nur die Uhrzeit als Text.
+ { key: "dateTime", label: "Datum + Uhrzeit der E-Mail" },
+ { key: "time", label: "Uhrzeit der E-Mail" },
+ { key: "archiveDate", label: "Ablagedatum (heute)" },
+ { key: "archiveDateTime", label: "Ablagezeitpunkt (jetzt)" },
+
+ // --- Dokument -----------------------------------------------------------
+ // fileName wird JE HOCHGELADENEM DOKUMENT gesetzt: .eml/PDF tragen den
+ // Mailnamen, jeder Anhang seinen eigenen Dateinamen.
+ { key: "fileName", label: "Dateiname (je Dokument)" },
+ { key: "attachmentCount", label: "Anzahl Anhänge" },
+ { key: "rfcMessageId", label: "Message-ID (RFC822)" },
+
+ // --- Wer/Woher ----------------------------------------------------------
+ { key: "dwUser", label: "DocuWare-Benutzer (Mitarbeiter)" },
+ { key: "dwOrganization", label: "DocuWare-Organisation" },
+ { key: "accountEmail", label: "Eigene E-Mail-Adresse" },
+ { key: "folder", label: "Ordner" },
];
// Standard-Zuordnung DocuWare-Feldname -> Mail-Token. Greift, solange der Nutzer
@@ -53,6 +74,7 @@ const DEFAULT_FIELD_MAP = {
EML_SIZE: "size",
EML_ACCOUNT: "account",
DOC_DATE: "date",
+ DOC_FILE_NAME: "fileName", // .eml/PDF: Mailname, Anhang: eigener Dateiname
SUBJECT: "subject",
BETREFF: "subject",
EMAIL: "senderEmail",