358 lines
11 KiB
JavaScript
358 lines
11 KiB
JavaScript
/**
|
|
* 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();
|
|
}
|