/** * 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; } }