263 lines
7.6 KiB
JavaScript
263 lines
7.6 KiB
JavaScript
/**
|
|
* classify.js - Klassifizierung eines gerade abgelegten Dokuments.
|
|
*
|
|
* Wird unmittelbar nach dem Ablegen aufgerufen. Ohne diesen Schritt trägt
|
|
* ein neues Dokument nur den Titel, und nachträglich macht man es
|
|
* erfahrungsgemäß nicht mehr.
|
|
*/
|
|
|
|
import { kurz } from "../lib/settings.js";
|
|
import { uebersetzeSeite, ruf, fehlerAnzeigen, meldungAnzeigen }
|
|
from "./gemeinsam.js";
|
|
|
|
const $ = (id) => document.getElementById(id);
|
|
let dokumentId = null;
|
|
let alleTags = [];
|
|
|
|
// Bei mehreren abgelegten Dateien wird jede einzeln klassifiziert.
|
|
// Andernfalls trüge nur die erste eine sinnvolle Zuordnung, und die
|
|
// übrigen fielen später als unklassifiziert auf - wenn überhaupt.
|
|
let liste = [];
|
|
let stelle = 0;
|
|
|
|
// Gewählte Tags als Menge. Bewusst getrennt vom angezeigten Zustand der
|
|
// Kontrollkästchen: Der Filter blendet Zeilen aus, die Auswahl darf davon
|
|
// nicht berührt werden.
|
|
const gewaehlt = new Set();
|
|
|
|
document.addEventListener("DOMContentLoaded", start);
|
|
|
|
async function start() {
|
|
uebersetzeSeite();
|
|
const p = new URLSearchParams(location.search);
|
|
|
|
// "ids" für mehrere, "id" für den Einzelfall - letzteres bleibt für
|
|
// Aufrufe aus anderen Stellen erhalten.
|
|
const roh = p.get("ids") || p.get("id") || "";
|
|
liste = roh.split(",").map((x) => Number(x)).filter(Boolean);
|
|
stelle = 0;
|
|
|
|
$("ueberspringen").addEventListener("click", weiter);
|
|
$("abbrechenAlle").addEventListener("click", () => window.close());
|
|
$("speichern").addEventListener("click", speichern);
|
|
|
|
if (!liste.length) {
|
|
fehlerAnzeigen("Kein Dokument angegeben.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const stamm = await ruf("stammdaten");
|
|
alleTags = stamm.tags;
|
|
fuelleListe("korrListe", stamm.korr);
|
|
fuelleListe("typListe", stamm.typen);
|
|
zustand.korr = stamm.korr;
|
|
zustand.typen = stamm.typen;
|
|
} catch (err) {
|
|
fehlerAnzeigen(err.message);
|
|
return;
|
|
}
|
|
|
|
await anzeigen();
|
|
}
|
|
|
|
const zustand = { korr: [], typen: [] };
|
|
|
|
/** Das Dokument an der aktuellen Stelle laden und darstellen. */
|
|
async function anzeigen() {
|
|
dokumentId = liste[stelle];
|
|
$("speichern").disabled = false;
|
|
$("ueberspringen").disabled = false;
|
|
meldungAnzeigen("");
|
|
|
|
// Der Knopf zum Abbrechen aller weiteren ist nur sinnvoll, wenn noch
|
|
// welche folgen.
|
|
$("abbrechenAlle").hidden = liste.length < 2;
|
|
|
|
try {
|
|
const doc = await ruf("dokument", { id: dokumentId });
|
|
|
|
const zaehler = liste.length > 1
|
|
? ` · ${browser.i18n.getMessage("classifyProgress",
|
|
[String(stelle + 1), String(liste.length)])
|
|
|| `${stelle + 1} von ${liste.length}`}`
|
|
: "";
|
|
$("quelle").textContent = `#${dokumentId} · ${kurz(doc.title, 60)}`
|
|
+ zaehler;
|
|
|
|
$("titel").value = doc.title || "";
|
|
$("erstellt").value = (doc.created || "").slice(0, 10);
|
|
$("korr").value = nameVon(zustand.korr, doc.correspondent);
|
|
$("typ").value = nameVon(zustand.typen, doc.document_type);
|
|
zeigeTags(doc.tags || []);
|
|
if ($("tagSuche")) $("tagSuche").value = "";
|
|
} catch (err) {
|
|
fehlerAnzeigen(err.message);
|
|
}
|
|
}
|
|
|
|
/** Zum nächsten Dokument, oder schliessen. */
|
|
async function weiter() {
|
|
stelle += 1;
|
|
if (stelle >= liste.length) {
|
|
window.close();
|
|
return;
|
|
}
|
|
await anzeigen();
|
|
}
|
|
|
|
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(liste, id) {
|
|
return liste.find((x) => x.id === id)?.name || "";
|
|
}
|
|
|
|
function zeigeTags(gesetzt) {
|
|
for (const id of gesetzt) gewaehlt.add(Number(id));
|
|
const feld = $("tagSuche");
|
|
if (feld) {
|
|
feld.addEventListener("input", zeichneTags);
|
|
// Eingabetaste soll den Dialog nicht abschicken, sondern beim
|
|
// einzigen Treffer diesen umschalten - das ist beim Tippen der
|
|
// erwartete Ablauf.
|
|
feld.addEventListener("keydown", (e) => {
|
|
if (e.key !== "Enter") return;
|
|
e.preventDefault();
|
|
const treffer = gefilterteTags();
|
|
if (treffer.length === 1) {
|
|
umschalten(treffer[0].id);
|
|
feld.value = "";
|
|
zeichneTags();
|
|
}
|
|
});
|
|
}
|
|
zeichneTags();
|
|
}
|
|
|
|
/** Tags, die zum aktuellen Filter passen und noch nicht gewählt sind. */
|
|
function gefilterteTags() {
|
|
const filter = ($("tagSuche")?.value || "").trim().toLowerCase();
|
|
return alleTags.filter((t) => {
|
|
if (gewaehlt.has(Number(t.id))) return false;
|
|
if (!filter) return true;
|
|
return String(t.name).toLowerCase().includes(filter);
|
|
});
|
|
}
|
|
|
|
function umschalten(id) {
|
|
const n = Number(id);
|
|
if (gewaehlt.has(n)) gewaehlt.delete(n);
|
|
else gewaehlt.add(n);
|
|
}
|
|
|
|
/**
|
|
* Tagliste zeichnen.
|
|
*
|
|
* Zwei Bereiche: oben die gewählten, darunter die übrigen. Die gewählten
|
|
* bleiben immer sichtbar, auch wenn der Filter sie ausschliesst - sonst
|
|
* verliert man beim Suchen den Überblick, was bereits angehakt ist.
|
|
*/
|
|
function zeichneTags() {
|
|
const filter = ($("tagSuche")?.value || "").trim().toLowerCase();
|
|
|
|
const oben = $("tagsGewaehlt");
|
|
oben.replaceChildren();
|
|
const gewaehlteListe = alleTags
|
|
.filter((t) => gewaehlt.has(Number(t.id)))
|
|
.sort((a, b) => String(a.name).localeCompare(String(b.name), "de"));
|
|
for (const tag of gewaehlteListe) {
|
|
oben.append(tagZeile(tag, true, ""));
|
|
}
|
|
|
|
const unten = $("tags");
|
|
unten.replaceChildren();
|
|
const rest = gefilterteTags()
|
|
.sort((a, b) => String(a.name).localeCompare(String(b.name), "de"));
|
|
for (const tag of rest) {
|
|
unten.append(tagZeile(tag, false, filter));
|
|
}
|
|
|
|
const leer = $("tagsLeer");
|
|
if (leer) leer.style.display = (rest.length === 0 && filter) ? "" : "none";
|
|
}
|
|
|
|
function tagZeile(tag, istGewaehlt, filter) {
|
|
const zeile = document.createElement("div");
|
|
zeile.className = "wahl";
|
|
|
|
const box = document.createElement("input");
|
|
box.type = "checkbox";
|
|
box.id = `t${tag.id}`;
|
|
box.value = String(tag.id);
|
|
box.checked = istGewaehlt;
|
|
box.addEventListener("change", () => {
|
|
umschalten(tag.id);
|
|
zeichneTags();
|
|
});
|
|
|
|
const bez = document.createElement("label");
|
|
bez.htmlFor = box.id;
|
|
beschriftungSetzen(bez, String(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.
|
|
*/
|
|
function beschriftungSetzen(el, name, filter) {
|
|
el.replaceChildren();
|
|
if (!filter) {
|
|
el.textContent = name;
|
|
return;
|
|
}
|
|
const pos = name.toLowerCase().indexOf(filter);
|
|
if (pos < 0) {
|
|
el.textContent = name;
|
|
return;
|
|
}
|
|
el.append(document.createTextNode(name.slice(0, pos)));
|
|
const treffer = document.createElement("span");
|
|
treffer.className = "trefferHervor";
|
|
treffer.textContent = name.slice(pos, pos + filter.length);
|
|
el.append(treffer);
|
|
el.append(document.createTextNode(name.slice(pos + filter.length)));
|
|
}
|
|
|
|
async function speichern() {
|
|
$("speichern").disabled = true;
|
|
meldungAnzeigen(browser.i18n.getMessage("msgSaving") || "Wird gespeichert …");
|
|
try {
|
|
// Aus der Menge lesen, nicht aus den Kästchen: Gefilterte Zeilen
|
|
// sind gar nicht im Dokument, ihre Auswahl ginge sonst verloren.
|
|
const tags = [...gewaehlt];
|
|
const payload = { title: $("titel").value.trim(), tags };
|
|
const erstellt = $("erstellt").value.trim();
|
|
if (erstellt) payload.created = erstellt;
|
|
|
|
await ruf("klassifizieren", {
|
|
id: dokumentId,
|
|
payload,
|
|
korrName: $("korr").value.trim(),
|
|
typName: $("typ").value.trim()
|
|
});
|
|
meldungAnzeigen(browser.i18n.getMessage("msgSaved") || "Gespeichert.");
|
|
setTimeout(weiter, 600);
|
|
} catch (err) {
|
|
fehlerAnzeigen(err.message);
|
|
$("speichern").disabled = false;
|
|
}
|
|
}
|