Compare commits
No commits in common. "main" and "v0.10.7" have entirely different histories.
@ -15,7 +15,6 @@ let STATE = {
|
|||||||
fields: [], // Felddefinitionen des Store-Dialogs
|
fields: [], // Felddefinitionen des Store-Dialogs
|
||||||
existingDoc: null, // Treffer der Dedup-Suche im gewählten Schrank (oder null)
|
existingDoc: null, // Treffer der Dedup-Suche im gewählten Schrank (oder null)
|
||||||
cabinetFieldsByCab: {}, // Cache: cabinetId -> [Feldnamen] (für EML_ID-Erkennung)
|
cabinetFieldsByCab: {}, // Cache: cabinetId -> [Feldnamen] (für EML_ID-Erkennung)
|
||||||
uploadedIds: new Set(), // bereits erfolgreich abgelegte Nachrichten (für Retry-Schutz)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mail-Token -> Funktion, die den Wert aus den Mail-Metadaten liefert.
|
// Mail-Token -> Funktion, die den Wert aus den Mail-Metadaten liefert.
|
||||||
@ -187,27 +186,11 @@ function renderAttachments() {
|
|||||||
const ul = $("attList");
|
const ul = $("attList");
|
||||||
ul.innerHTML = "";
|
ul.innerHTML = "";
|
||||||
STATE.attachments.forEach((a, i) => {
|
STATE.attachments.forEach((a, i) => {
|
||||||
// Bewusst per DOM-API (nicht innerHTML): der Anhang-Name stammt aus der Mail
|
|
||||||
// und ist angreiferkontrolliert – als Text einsetzen, nie als HTML.
|
|
||||||
const li = document.createElement("li");
|
const li = document.createElement("li");
|
||||||
|
li.innerHTML =
|
||||||
const cb = document.createElement("input");
|
`<input type="checkbox" id="att-${i}" checked /> ` +
|
||||||
cb.type = "checkbox";
|
`<label for="att-${i}" style="margin:0;font-weight:400">${a.name} ` +
|
||||||
cb.id = `att-${i}`;
|
`<span class="muted">(${formatSize(a.size)})</span></label>`;
|
||||||
cb.checked = true;
|
|
||||||
|
|
||||||
const label = document.createElement("label");
|
|
||||||
label.setAttribute("for", `att-${i}`);
|
|
||||||
label.style.margin = "0";
|
|
||||||
label.style.fontWeight = "400";
|
|
||||||
label.textContent = `${a.name} `;
|
|
||||||
|
|
||||||
const size = document.createElement("span");
|
|
||||||
size.className = "muted";
|
|
||||||
size.textContent = `(${formatSize(a.size)})`;
|
|
||||||
label.appendChild(size);
|
|
||||||
|
|
||||||
li.append(cb, document.createTextNode(" "), label);
|
|
||||||
ul.appendChild(li);
|
ul.appendChild(li);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -446,8 +429,7 @@ function buildInput(f) {
|
|||||||
if (f.isSelectList) {
|
if (f.isSelectList) {
|
||||||
// Combobox: frei tippbar mit Vorschlagsliste (wie in Connect-to-Outlook).
|
// Combobox: frei tippbar mit Vorschlagsliste (wie in Connect-to-Outlook).
|
||||||
const dl = document.createElement("datalist");
|
const dl = document.createElement("datalist");
|
||||||
// Feldnamen können Leer-/Sonderzeichen enthalten -> auf gültige id-Zeichen reduzieren.
|
dl.id = "dl-" + f.name;
|
||||||
dl.id = "dl-" + String(f.name).replace(/[^A-Za-z0-9_-]/g, "_");
|
|
||||||
el.setAttribute("list", dl.id);
|
el.setAttribute("list", dl.id);
|
||||||
el._datalist = dl;
|
el._datalist = dl;
|
||||||
}
|
}
|
||||||
@ -475,7 +457,7 @@ function prefillFields() {
|
|||||||
|
|
||||||
// Setzt einen Wert auf das Control eines Feldes (kümmert sich um Datum/Select).
|
// Setzt einen Wert auf das Control eines Feldes (kümmert sich um Datum/Select).
|
||||||
function setFieldValue(name, f, val) {
|
function setFieldValue(name, f, val) {
|
||||||
const wrap = document.querySelector(`.field[data-name="${cssEsc(name)}"]`);
|
const wrap = document.querySelector(`.field[data-name="${name}"]`);
|
||||||
const el = wrap && wrap.querySelector("[data-role='value']");
|
const el = wrap && wrap.querySelector("[data-role='value']");
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
if (el.type === "date") {
|
if (el.type === "date") {
|
||||||
@ -506,7 +488,7 @@ async function populateSelectLists() {
|
|||||||
);
|
);
|
||||||
} catch (_) { /* leere Liste ist ok */ }
|
} catch (_) { /* leere Liste ist ok */ }
|
||||||
if (!vals.length) continue;
|
if (!vals.length) continue;
|
||||||
const wrap = document.querySelector(`.field[data-name="${cssEsc(f.name)}"]`);
|
const wrap = document.querySelector(`.field[data-name="${f.name}"]`);
|
||||||
const el = wrap && wrap.querySelector("[data-role='value']");
|
const el = wrap && wrap.querySelector("[data-role='value']");
|
||||||
if (!el) continue;
|
if (!el) continue;
|
||||||
if (el.tagName === "SELECT") {
|
if (el.tagName === "SELECT") {
|
||||||
@ -548,10 +530,7 @@ function collectFieldValues() {
|
|||||||
if (!firstInvalid) firstInvalid = name;
|
if (!firstInvalid) firstInvalid = name;
|
||||||
}
|
}
|
||||||
if (String(raw).trim() !== "") {
|
if (String(raw).trim() !== "") {
|
||||||
// Date-Input als LOKALE Mitternacht parsen (raw+"T00:00:00"), sonst
|
result[name] = { value: el.type === "date" ? new Date(raw) : raw, type };
|
||||||
// interpretiert new Date("YYYY-MM-DD") als UTC.
|
|
||||||
const value = el.type === "date" ? new Date(`${raw}T00:00:00`) : raw;
|
|
||||||
result[name] = { value, type };
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return { result, firstInvalid };
|
return { result, firstInvalid };
|
||||||
@ -605,8 +584,6 @@ async function submit() {
|
|||||||
let mi = 0;
|
let mi = 0;
|
||||||
for (const mid of STATE.messageIds) {
|
for (const mid of STATE.messageIds) {
|
||||||
mi++;
|
mi++;
|
||||||
// Retry-Schutz: nach einem Teilfehler nicht erneut hochladen (sonst Dubletten).
|
|
||||||
if (STATE.uploadedIds.has(mid)) continue;
|
|
||||||
const prefix = multi ? `Mail ${mi}/${STATE.messageIds.length}: ` : "";
|
const prefix = multi ? `Mail ${mi}/${STATE.messageIds.length}: ` : "";
|
||||||
// Meta/Anhänge der jeweiligen Nachricht.
|
// Meta/Anhänge der jeweiligen Nachricht.
|
||||||
const meta = mid === STATE.messageId ? STATE.meta : await Mail.getMeta(mid);
|
const meta = mid === STATE.messageId ? STATE.meta : await Mail.getMeta(mid);
|
||||||
@ -655,9 +632,6 @@ async function submit() {
|
|||||||
.sendMessage({ type: "archive:done", rfcMessageId: meta.rfcMessageId, cabinetId: STATE.cabinetId })
|
.sendMessage({ type: "archive:done", rfcMessageId: meta.rfcMessageId, cabinetId: STATE.cabinetId })
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diese Nachricht ist vollständig abgelegt -> bei einem späteren Retry überspringen.
|
|
||||||
STATE.uploadedIds.add(mid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const base = multi ? `${STATE.messageIds.length} E-Mails abgelegt.` : "Erfolgreich abgelegt.";
|
const base = multi ? `${STATE.messageIds.length} E-Mails abgelegt.` : "Erfolgreich abgelegt.";
|
||||||
@ -721,17 +695,7 @@ async function tagMessage(messageId) {
|
|||||||
|
|
||||||
// --- Helfer ---
|
// --- Helfer ---
|
||||||
const isMemo = (f) => /memo/i.test(f.type) || f.name === "EML_BODY";
|
const isMemo = (f) => /memo/i.test(f.type) || f.name === "EML_BODY";
|
||||||
// Lokales Datum als YYYY-MM-DD (NICHT toISOString -> das rechnet auf UTC um und
|
const toDateInput = (d) => d.toISOString().slice(0, 10);
|
||||||
// kippt eine Uhrzeit nahe Mitternacht auf den Vortag).
|
|
||||||
const toDateInput = (d) => {
|
|
||||||
const y = d.getFullYear();
|
|
||||||
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
||||||
const day = String(d.getDate()).padStart(2, "0");
|
|
||||||
return `${y}-${m}-${day}`;
|
|
||||||
};
|
|
||||||
// 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, "\\$&");
|
|
||||||
function formatSize(b) {
|
function formatSize(b) {
|
||||||
if (!b) return "–";
|
if (!b) return "–";
|
||||||
if (b < 1024) return `${b} B`;
|
if (b < 1024) return `${b} B`;
|
||||||
|
|||||||
@ -414,12 +414,7 @@ const DocuWare = {
|
|||||||
if (t.includes("date")) {
|
if (t.includes("date")) {
|
||||||
// DocuWare erwartet /Date(ms)/ oder ISO; ISO funktioniert bei Platform.
|
// DocuWare erwartet /Date(ms)/ oder ISO; ISO funktioniert bei Platform.
|
||||||
const d = value instanceof Date ? value : new Date(value);
|
const d = value instanceof Date ? value : new Date(value);
|
||||||
if (isNaN(d)) return { FieldName: name, Item: String(value), ItemElementName: "Date" };
|
return { FieldName: name, Item: d.toISOString(), ItemElementName: "Date" };
|
||||||
// Auf den LOKALEN Kalendertag als UTC-Mitternacht normalisieren. Sonst würde
|
|
||||||
// toISOString eine Uhrzeit nahe Mitternacht in eine andere Zeitzone und damit
|
|
||||||
// auf den Vor-/Folgetag kippen (Datumsfelder tragen keine Uhrzeit).
|
|
||||||
const utcDay = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
||||||
return { FieldName: name, Item: utcDay.toISOString(), ItemElementName: "Date" };
|
|
||||||
}
|
}
|
||||||
if (t.includes("decimal") || t.includes("currency")) {
|
if (t.includes("decimal") || t.includes("currency")) {
|
||||||
return { FieldName: name, Item: Number(value), ItemElementName: "Decimal" };
|
return { FieldName: name, Item: Number(value), ItemElementName: "Decimal" };
|
||||||
|
|||||||
@ -29,7 +29,7 @@ const Mail = {
|
|||||||
date: header.date instanceof Date ? header.date : new Date(header.date),
|
date: header.date instanceof Date ? header.date : new Date(header.date),
|
||||||
bodyText,
|
bodyText,
|
||||||
account,
|
account,
|
||||||
direction: this._direction(header),
|
direction: this._direction(header, account),
|
||||||
sizeBytes: header.size || 0,
|
sizeBytes: header.size || 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -140,8 +140,9 @@ const Mail = {
|
|||||||
return "";
|
return "";
|
||||||
},
|
},
|
||||||
|
|
||||||
_direction(header) {
|
_direction(header, accountName) {
|
||||||
// Heuristik über den Ordnertyp; robustere Erkennung via Identitäten käme später.
|
// Heuristik: ist der Account-Name/eine Identität Absender -> Ausgang.
|
||||||
|
// Robustere Erkennung erfolgt später über Identitäten; v1 simpel:
|
||||||
const folderType = header.folder && header.folder.type;
|
const folderType = header.folder && header.folder.type;
|
||||||
if (folderType === "sent" || folderType === "outbox") return "Ausgang";
|
if (folderType === "sent" || folderType === "outbox") return "Ausgang";
|
||||||
return "Eingang";
|
return "Eingang";
|
||||||
|
|||||||
22
lib/store.js
22
lib/store.js
@ -73,23 +73,13 @@ const Settings = {
|
|||||||
return s;
|
return s;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Laufende Schreibkette: serialisiert parallele set()-Aufrufe, damit zwei fast
|
|
||||||
// gleichzeitige Writes (z.B. Schrankwahl + Format-Umschalten) sich nicht beim
|
|
||||||
// read-modify-write gegenseitig überschreiben.
|
|
||||||
_writeQueue: Promise.resolve(),
|
|
||||||
|
|
||||||
async set(partial) {
|
async set(partial) {
|
||||||
const run = async () => {
|
const current = await this.get();
|
||||||
const current = await this.get();
|
const next = { ...current, ...partial };
|
||||||
const next = { ...current, ...partial };
|
// Sicherheitsnetz: Passwort darf NIE persistiert werden (z.B. aus Alt-Importen).
|
||||||
// Sicherheitsnetz: Passwort darf NIE persistiert werden (z.B. aus Alt-Importen).
|
delete next.password;
|
||||||
delete next.password;
|
await browser.storage.local.set({ settings: next });
|
||||||
await browser.storage.local.set({ settings: next });
|
return next;
|
||||||
return next;
|
|
||||||
};
|
|
||||||
const p = this._writeQueue.then(run, run);
|
|
||||||
this._writeQueue = p.catch(() => {}); // Fehler bricht die Kette nicht ab
|
|
||||||
return p;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Basis-URL der Platform-API ohne abschließenden Slash. */
|
/** Basis-URL der Platform-API ohne abschließenden Slash. */
|
||||||
|
|||||||
@ -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.8",
|
"version": "0.10.7",
|
||||||
"author": "l.lingler",
|
"author": "l.lingler",
|
||||||
"applications": {
|
"applications": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
|
|||||||
@ -12,14 +12,6 @@ function setStatus(msg, kind) {
|
|||||||
el.className = kind || "";
|
el.className = kind || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warnt, wenn der Server über unverschlüsseltes http:// läuft – dann ginge das
|
|
||||||
// Passwort im Klartext über die Leitung. Liefert einen anzuhängenden Hinweis.
|
|
||||||
function insecureWarning(url) {
|
|
||||||
return /^http:\/\//i.test(String(url || "").trim())
|
|
||||||
? " ⚠ Achtung: http:// ist unverschlüsselt – bitte https:// verwenden."
|
|
||||||
: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
async function load() {
|
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] || ""));
|
||||||
@ -221,10 +213,8 @@ function collect() {
|
|||||||
// --- Events ----------------------------------------------------------------
|
// --- Events ----------------------------------------------------------------
|
||||||
|
|
||||||
$("save").addEventListener("click", async () => {
|
$("save").addEventListener("click", async () => {
|
||||||
const data = collect();
|
await Settings.set(collect());
|
||||||
await Settings.set(data);
|
setStatus("Gespeichert.", "ok");
|
||||||
const warn = insecureWarning(data.serverUrl);
|
|
||||||
setStatus("Gespeichert." + warn, warn ? "err" : "ok");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("test").addEventListener("click", async () => {
|
$("test").addEventListener("click", async () => {
|
||||||
@ -238,8 +228,7 @@ $("test").addEventListener("click", async () => {
|
|||||||
fillCabinets(cabinets, s.defaultCabinetId);
|
fillCabinets(cabinets, s.defaultCabinetId);
|
||||||
const arch = cabinets.filter((c) => !c.isBasket).length;
|
const arch = cabinets.filter((c) => !c.isBasket).length;
|
||||||
const bk = cabinets.filter((c) => c.isBasket).length;
|
const bk = cabinets.filter((c) => c.isBasket).length;
|
||||||
const warn = insecureWarning(s.serverUrl);
|
setStatus(`Verbindung ok – ${arch} Archive, ${bk} Briefkörbe gefunden.`, "ok");
|
||||||
setStatus(`Verbindung ok – ${arch} Archive, ${bk} Briefkörbe gefunden.` + warn, warn ? "err" : "ok");
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setStatus(`Fehler: ${e.message}`, "err");
|
setStatus(`Fehler: ${e.message}`, "err");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,10 +33,6 @@
|
|||||||
{
|
{
|
||||||
"version": "0.10.7",
|
"version": "0.10.7",
|
||||||
"update_link": "https://sylyx.xyz/sylyx/thunderbird2docuware/releases/download/v0.10.7/docuware-ablage-0.10.7.xpi"
|
"update_link": "https://sylyx.xyz/sylyx/thunderbird2docuware/releases/download/v0.10.7/docuware-ablage-0.10.7.xpi"
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.10.8",
|
|
||||||
"update_link": "https://sylyx.xyz/sylyx/thunderbird2docuware/releases/download/v0.10.8/docuware-ablage-0.10.8.xpi"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user