Verbesserung Ablagedialog bei mehreren ausgewählten Dateien

This commit is contained in:
2026-09-09 23:15:14 +02:00
parent 3f96e6f49d
commit 01a972d719
14 changed files with 908 additions and 236 deletions

357
ui/schritt.js Normal file
View File

@@ -0,0 +1,357 @@
/**
* schritt.js - Ziel und Klassifizierung, Datei für Datei.
*
* Der Ablauf ist bewusst in zwei Fenster geteilt:
*
* 1. Auswahl der abzulegenden Dateien (store.html)
* 2. Je Datei: Ziel und Klassifizierung (dieses Fenster)
*
* Der Grund: Ziel und Klassifizierung sind je Datei verschieden. Ein
* einzelner Dialog für alle würde die Angaben der ersten Datei auf alle
* übertragen - bei einer Rechnung samt Lastschriftmandat wäre das falsch.
*
* Beim Anhängen an ein bestehendes Dokument wird dessen Klassifizierung
* übernommen und kann angepasst werden. Sie neu einzugeben wäre Arbeit
* für nichts, und ein Tippfehler brächte die Version vom Original weg.
*/
import { kurz, groesse } from "../lib/settings.js";
import { uebersetzeSeite, ruf, fehlerAnzeigen, meldungAnzeigen }
from "./gemeinsam.js";
const $ = (id) => document.getElementById(id);
let auftrag = null;
let liste = []; // die abzulegenden Dateien
let stelle = 0;
let stamm = { tags: [], korr: [], typen: [] };
let treffer = [];
let zielDokument = null;
let gewaehlteTags = new Set();
const abgelegt = []; // für die Abschlussmeldung
document.addEventListener("DOMContentLoaded", start);
async function start() {
uebersetzeSeite();
const nummer = Number(new URLSearchParams(location.search).get("auftrag"));
try {
auftrag = await ruf("auftrag", { nummer });
if (!auftrag) throw new Error("Der Auftrag ist nicht mehr vorhanden.");
liste = await ruf("dateiListe", { auftrag });
stamm = await ruf("stammdaten");
} catch (err) {
fehlerAnzeigen(err.message);
return;
}
if (!liste.length) {
fehlerAnzeigen(browser.i18n.getMessage("errNothingSelected")
|| "Nichts ausgewählt.");
return;
}
fuelleListe("korrListe", stamm.korr);
fuelleListe("typListe", stamm.typen);
verdrahten();
await anzeigen();
}
function verdrahten() {
for (const r of document.querySelectorAll('input[name=ziel]')) {
r.addEventListener("change", zielGewechselt);
}
$("suchen").addEventListener("click", suchen);
$("suche").addEventListener("keydown", (e) => {
if (e.key === "Enter") suchen();
});
$("tagSuche").addEventListener("input", zeichneTags);
$("abbrechen").addEventListener("click", () => window.close());
$("ueberspringen").addEventListener("click", () => weiter());
$("ablegen").addEventListener("click", ablegen);
}
// ------------------------------------------------------ Datei anzeigen
async function anzeigen() {
const eintrag = liste[stelle];
$("kopf").textContent = browser.i18n.getMessage("stepTitle")
|| "In Paperless ablegen";
const zaehler = liste.length > 1
? `${browser.i18n.getMessage("classifyProgress",
[String(stelle + 1), String(liste.length)])
|| `${stelle + 1} von ${liste.length}`} · `
: "";
const name = eintrag.art === "nachricht"
? (browser.i18n.getMessage("stepWholeMessage") || "Die ganze Nachricht")
: `${eintrag.name}${eintrag.groesse ? ` (${groesse(eintrag.groesse)})` : ""}`;
$("quelle").textContent = `${zaehler}${name}`;
// Zurücksetzen für die neue Datei
zielDokument = null;
treffer = [];
$("treffer").replaceChildren();
$("suche").value = "";
$("tagSuche").value = "";
$("zielNeu").checked = true;
gewaehlteTags = new Set();
$("titel").value = vorschlagTitel(eintrag);
$("erstellt").value = auftrag.datum || "";
$("korr").value = "";
$("typ").value = "";
zielGewechselt();
zeichneTags();
meldungAnzeigen("");
$("ablegen").disabled = false;
$("ueberspringen").hidden = liste.length < 2;
}
function vorschlagTitel(eintrag) {
if (eintrag.art === "nachricht") return auftrag.titelVorschlag || "";
// Endung weg: Sie steht schon im Dateinamen des Originals und trägt
// im Titel nichts bei.
return String(eintrag.name || "").replace(/\.[^.]{1,8}$/, "");
}
// ----------------------------------------------------------- Zielwahl
function zielGewechselt() {
const vorhanden = $("zielVorhanden").checked;
$("suche").disabled = !vorhanden;
$("suchen").disabled = !vorhanden;
// Die Trefferliste abblenden statt ausblenden: Ein Feld, das
// verschwindet und wieder auftaucht, lässt die Spalte springen.
const liste = $("treffer").closest(".liste");
if (liste) liste.style.opacity = vorhanden ? "1" : ".45";
if (vorhanden && !treffer.length) suchen();
pruefen();
}
async function suchen() {
$("treffer").replaceChildren();
zielDokument = null;
try {
treffer = await ruf("suchen", { query: $("suche").value.trim() });
} catch (err) {
fehlerAnzeigen(err.message);
return;
}
for (const [i, r] of treffer.entries()) {
const tr = document.createElement("tr");
const a = document.createElement("td");
a.className = "zahl";
a.textContent = r.archive_serial_number ?? "—";
const b = document.createElement("td");
b.textContent = (r.created || "").slice(0, 10);
const c = document.createElement("td");
c.textContent = r.title || "";
c.title = r.title || "";
tr.append(a, b, c);
tr.addEventListener("click", () => waehleZiel(i));
$("treffer").append(tr);
}
if (!treffer.length) {
meldungAnzeigen(browser.i18n.getMessage("msgNoHits") || "Keine Treffer.");
}
pruefen();
}
/**
* Zieldokument wählen und dessen Klassifizierung übernehmen.
*
* Eine neue Version gehört inhaltlich zum selben Vorgang wie das
* Original. Die Angaben neu einzutippen wäre Arbeit für nichts - und ein
* abweichender Korrespondent brächte die Version vom Original weg.
*/
async function waehleZiel(i) {
zielDokument = treffer[i] || null;
for (const [j, tr] of [...$("treffer").children].entries()) {
tr.classList.toggle("gewaehlt", j === i);
}
pruefen();
if (!zielDokument) return;
meldungAnzeigen(browser.i18n.getMessage("msgLoadingTarget")
|| "Klassifizierung des Ziels wird geladen …");
try {
const doc = await ruf("dokument", { id: zielDokument.id });
$("titel").value = doc.title || "";
$("erstellt").value = (doc.created || "").slice(0, 10);
$("korr").value = nameVon(stamm.korr, doc.correspondent);
$("typ").value = nameVon(stamm.typen, doc.document_type);
gewaehlteTags = new Set((doc.tags || []).map(Number));
zeichneTags();
meldungAnzeigen("");
} catch (err) {
fehlerAnzeigen(err.message);
}
}
function pruefen() {
const ok = $("zielNeu").checked || Boolean(zielDokument);
$("ablegen").disabled = !ok;
}
// --------------------------------------------------------------- Tags
function fuelleListe(id, eintraege) {
const dl = $(id);
dl.replaceChildren();
for (const e of eintraege) {
const o = document.createElement("option");
o.value = e.name;
dl.append(o);
}
}
function nameVon(rows, id) {
return rows.find((x) => x.id === id)?.name || "";
}
/**
* Tagliste zeichnen: Gewählte oben, dann die gefilterten übrigen.
*
* Gewählte bleiben sichtbar, auch wenn der Filter sie ausschliessen
* würde - sonst verliert man beim Suchen den Überblick, was schon
* angehakt ist.
*/
function zeichneTags() {
const ziel = $("tags");
ziel.replaceChildren();
const filter = ($("tagSuche").value || "").trim().toLowerCase();
const oben = stamm.tags.filter((t) => gewaehlteTags.has(Number(t.id)));
const unten = stamm.tags.filter(
(t) => !gewaehlteTags.has(Number(t.id))
&& (!filter || t.name.toLowerCase().includes(filter)));
for (const t of oben) ziel.append(tagZeile(t, true, ""));
if (oben.length && unten.length) {
const tr = document.createElement("hr");
tr.style.cssText = "border:0;border-top:1px solid var(--rand);margin:5px 0";
ziel.append(tr);
}
for (const t of unten) ziel.append(tagZeile(t, false, filter));
if (!oben.length && !unten.length) {
const h = document.createElement("div");
h.className = "hinweis";
h.textContent = browser.i18n.getMessage("tagsNoMatch")
|| "Keine Übereinstimmung.";
ziel.append(h);
}
}
function tagZeile(tag, gewaehlt, filter) {
const zeile = document.createElement("div");
zeile.className = "wahl";
const box = document.createElement("input");
box.type = "checkbox";
box.id = `tag_${tag.id}`;
box.checked = gewaehlt;
box.addEventListener("change", () => {
if (box.checked) gewaehlteTags.add(Number(tag.id));
else gewaehlteTags.delete(Number(tag.id));
zeichneTags();
});
const bez = document.createElement("label");
bez.htmlFor = box.id;
if (gewaehlt) bez.style.fontWeight = "600";
beschriften(bez, tag.name, filter);
zeile.append(box, bez);
return zeile;
}
/**
* Beschriftung mit hervorgehobenem Treffer.
*
* Aufbau über createElement und textContent, nicht über innerHTML: Ein
* Tagname stammt aus Paperless und damit mittelbar aus fremden Quellen -
* wer ein Dokument per Mail einliefern kann, bestimmt Tagnamen mit.
*/
function beschriften(el, name, filter) {
el.replaceChildren();
const pos = filter ? name.toLowerCase().indexOf(filter) : -1;
if (pos < 0) {
el.textContent = name;
return;
}
el.append(document.createTextNode(name.slice(0, pos)));
const mark = document.createElement("span");
mark.className = "trefferHervor";
mark.textContent = name.slice(pos, pos + filter.length);
el.append(mark, document.createTextNode(name.slice(pos + filter.length)));
}
// ------------------------------------------------------------- Ablauf
async function ablegen() {
$("ablegen").disabled = true;
$("ueberspringen").disabled = true;
meldungAnzeigen(browser.i18n.getMessage("msgUploading")
|| "Wird abgelegt …");
const eintrag = liste[stelle];
const anVorhandenes = $("zielVorhanden").checked;
try {
const ergebnis = await ruf("ablegenEine", {
auftrag,
eintrag,
ziel: anVorhandenes ? "vorhanden" : "neu",
zielId: zielDokument?.id,
titel: $("titel").value.trim(),
erstellt: $("erstellt").value.trim()
});
// Klassifizierung anwenden. Beim Anhängen gilt sie für die Wurzel,
// nicht für die neue Version.
const zielId = ergebnis.art === "version" ? ergebnis.zielId : ergebnis.id;
if (zielId) {
meldungAnzeigen(browser.i18n.getMessage("msgSaving")
|| "Klassifizierung wird gespeichert …");
await ruf("klassifizieren", {
id: zielId,
payload: {
title: $("titel").value.trim(),
tags: [...gewaehlteTags],
...($("erstellt").value.trim()
? { created: $("erstellt").value.trim() } : {})
},
korrName: $("korr").value.trim(),
typName: $("typ").value.trim()
});
}
abgelegt.push(ergebnis);
await weiter();
} catch (err) {
console.error("[paperless] Ablegen gescheitert", err);
fehlerAnzeigen(err.message);
$("ablegen").disabled = false;
$("ueberspringen").disabled = false;
}
}
async function weiter() {
$("ueberspringen").disabled = false;
stelle += 1;
if (stelle >= liste.length) {
meldungAnzeigen(browser.i18n.getMessage("msgStored",
[String(abgelegt.length)]) || `${abgelegt.length} abgelegt.`);
setTimeout(() => window.close(), 1200);
return;
}
await anzeigen();
}