Files
thunderbird-paperless-conne…/ui/classify.js

213 lines
6.3 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 = [];
// 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);
dokumentId = Number(p.get("id"));
const weitere = Number(p.get("weiter") || 1);
$("ueberspringen").addEventListener("click", () => window.close());
$("speichern").addEventListener("click", speichern);
try {
const [doc, stamm] = await Promise.all([
ruf("dokument", { id: dokumentId }),
ruf("stammdaten")
]);
alleTags = stamm.tags;
$("quelle").textContent =
`#${dokumentId} · ${kurz(doc.title, 70)}`
+ (weitere > 1
? ` · ${browser.i18n.getMessage("classifyMoreStored",
[String(weitere)]) || `${weitere} Dateien abgelegt`}`
: "");
$("titel").value = doc.title || "";
$("erstellt").value = (doc.created || "").slice(0, 10);
fuelleListe("korrListe", stamm.korr);
fuelleListe("typListe", stamm.typen);
$("korr").value = nameVon(stamm.korr, doc.correspondent);
$("typ").value = nameVon(stamm.typen, doc.document_type);
zeigeTags(doc.tags || []);
} catch (err) {
fehlerAnzeigen(err.message);
}
}
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(() => window.close(), 900);
} catch (err) {
fehlerAnzeigen(err.message);
$("speichern").disabled = false;
}
}