Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 01a972d719 |
@@ -44,7 +44,7 @@
|
|||||||
"message": "Neues Dokument anlegen"
|
"message": "Neues Dokument anlegen"
|
||||||
},
|
},
|
||||||
"storeTargetExisting": {
|
"storeTargetExisting": {
|
||||||
"message": "An bestehendes Dokument anhängen"
|
"message": "Als neue Version an ein bestehendes anhängen"
|
||||||
},
|
},
|
||||||
"storeHintTitleMulti": {
|
"storeHintTitleMulti": {
|
||||||
"message": "Bei mehreren Dateien wird der Titel um den Dateinamen ergänzt."
|
"message": "Bei mehreren Dateien wird der Titel um den Dateinamen ergänzt."
|
||||||
@@ -320,5 +320,48 @@
|
|||||||
},
|
},
|
||||||
"artPdfUnavailable": {
|
"artPdfUnavailable": {
|
||||||
"message": "Für dieses Format nicht möglich - nur Textdateien lassen sich aufbereiten."
|
"message": "Für dieses Format nicht möglich - nur Textdateien lassen sich aufbereiten."
|
||||||
|
},
|
||||||
|
"btnSkipAll": {
|
||||||
|
"message": "Alle überspringen"
|
||||||
|
},
|
||||||
|
"classifyProgress": {
|
||||||
|
"message": "$1 von $2",
|
||||||
|
"placeholders": {
|
||||||
|
"1": {
|
||||||
|
"content": "$1"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"content": "$2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"msgUploadingOf": {
|
||||||
|
"message": "Wird abgelegt … $1 von $2",
|
||||||
|
"placeholders": {
|
||||||
|
"1": {
|
||||||
|
"content": "$1"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"content": "$2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stepTitle": {
|
||||||
|
"message": "In Paperless ablegen"
|
||||||
|
},
|
||||||
|
"btnNext": {
|
||||||
|
"message": "Weiter"
|
||||||
|
},
|
||||||
|
"stepWholeMessage": {
|
||||||
|
"message": "Die ganze Nachricht"
|
||||||
|
},
|
||||||
|
"stepHintExisting": {
|
||||||
|
"message": "Die Klassifizierung des Ziels wird übernommen und kann angepasst werden."
|
||||||
|
},
|
||||||
|
"msgLoadingTarget": {
|
||||||
|
"message": "Klassifizierung des Ziels wird geladen …"
|
||||||
|
},
|
||||||
|
"msgOpeningStep": {
|
||||||
|
"message": "Wird vorbereitet …"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
"message": "Create a new document"
|
"message": "Create a new document"
|
||||||
},
|
},
|
||||||
"storeTargetExisting": {
|
"storeTargetExisting": {
|
||||||
"message": "Append to an existing document"
|
"message": "Append as a new version to an existing document"
|
||||||
},
|
},
|
||||||
"storeHintTitleMulti": {
|
"storeHintTitleMulti": {
|
||||||
"message": "With several files the title is extended by the file name."
|
"message": "With several files the title is extended by the file name."
|
||||||
@@ -320,5 +320,48 @@
|
|||||||
},
|
},
|
||||||
"artPdfUnavailable": {
|
"artPdfUnavailable": {
|
||||||
"message": "Not possible for this format - only text files can be prepared."
|
"message": "Not possible for this format - only text files can be prepared."
|
||||||
|
},
|
||||||
|
"btnSkipAll": {
|
||||||
|
"message": "Skip all"
|
||||||
|
},
|
||||||
|
"classifyProgress": {
|
||||||
|
"message": "$1 of $2",
|
||||||
|
"placeholders": {
|
||||||
|
"1": {
|
||||||
|
"content": "$1"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"content": "$2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"msgUploadingOf": {
|
||||||
|
"message": "Storing … $1 of $2",
|
||||||
|
"placeholders": {
|
||||||
|
"1": {
|
||||||
|
"content": "$1"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"content": "$2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stepTitle": {
|
||||||
|
"message": "Store in Paperless"
|
||||||
|
},
|
||||||
|
"btnNext": {
|
||||||
|
"message": "Next"
|
||||||
|
},
|
||||||
|
"stepWholeMessage": {
|
||||||
|
"message": "The whole message"
|
||||||
|
},
|
||||||
|
"stepHintExisting": {
|
||||||
|
"message": "The target's classification is copied and can be adjusted."
|
||||||
|
},
|
||||||
|
"msgLoadingTarget": {
|
||||||
|
"message": "Loading the target's classification …"
|
||||||
|
},
|
||||||
|
"msgOpeningStep": {
|
||||||
|
"message": "Preparing …"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
"message": "Créer un nouveau document"
|
"message": "Créer un nouveau document"
|
||||||
},
|
},
|
||||||
"storeTargetExisting": {
|
"storeTargetExisting": {
|
||||||
"message": "Ajouter à un document existant"
|
"message": "Ajouter comme nouvelle version à un document existant"
|
||||||
},
|
},
|
||||||
"storeHintTitleMulti": {
|
"storeHintTitleMulti": {
|
||||||
"message": "Avec plusieurs fichiers, le titre est complété par le nom du fichier."
|
"message": "Avec plusieurs fichiers, le titre est complété par le nom du fichier."
|
||||||
@@ -320,5 +320,48 @@
|
|||||||
},
|
},
|
||||||
"artPdfUnavailable": {
|
"artPdfUnavailable": {
|
||||||
"message": "Impossible pour ce format - seuls les fichiers texte peuvent être préparés."
|
"message": "Impossible pour ce format - seuls les fichiers texte peuvent être préparés."
|
||||||
|
},
|
||||||
|
"btnSkipAll": {
|
||||||
|
"message": "Tout ignorer"
|
||||||
|
},
|
||||||
|
"classifyProgress": {
|
||||||
|
"message": "$1 sur $2",
|
||||||
|
"placeholders": {
|
||||||
|
"1": {
|
||||||
|
"content": "$1"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"content": "$2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"msgUploadingOf": {
|
||||||
|
"message": "Enregistrement … $1 sur $2",
|
||||||
|
"placeholders": {
|
||||||
|
"1": {
|
||||||
|
"content": "$1"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"content": "$2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stepTitle": {
|
||||||
|
"message": "Enregistrer dans Paperless"
|
||||||
|
},
|
||||||
|
"btnNext": {
|
||||||
|
"message": "Suivant"
|
||||||
|
},
|
||||||
|
"stepWholeMessage": {
|
||||||
|
"message": "Le message entier"
|
||||||
|
},
|
||||||
|
"stepHintExisting": {
|
||||||
|
"message": "Le classement de la cible est repris et peut être ajusté."
|
||||||
|
},
|
||||||
|
"msgLoadingTarget": {
|
||||||
|
"message": "Chargement du classement de la cible …"
|
||||||
|
},
|
||||||
|
"msgOpeningStep": {
|
||||||
|
"message": "Préparation …"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
134
background.js
134
background.js
@@ -256,7 +256,10 @@ async function dialogOeffnen(art, daten) {
|
|||||||
// Absender und Dateinamen; das muss nicht bis zum Neustart im Speicher
|
// Absender und Dateinamen; das muss nicht bis zum Neustart im Speicher
|
||||||
// liegen, nur weil jemand den Dialog offen stehen liess.
|
// liegen, nur weil jemand den Dialog offen stehen liess.
|
||||||
setTimeout(() => auftraege.delete(nummer), 30 * 60 * 1000);
|
setTimeout(() => auftraege.delete(nummer), 30 * 60 * 1000);
|
||||||
const seite = art === "einfuegen" ? "ui/insert.html" : "ui/store.html";
|
const seite = {
|
||||||
|
einfuegen: "ui/insert.html",
|
||||||
|
schritt: "ui/schritt.html"
|
||||||
|
}[art] || "ui/store.html";
|
||||||
|
|
||||||
// Die Adresse MUSS über runtime.getURL aufgelöst werden. Ein relativer
|
// Die Adresse MUSS über runtime.getURL aufgelöst werden. Ein relativer
|
||||||
// Pfad wird in Manifest V3 nicht gegen das Erweiterungsverzeichnis
|
// Pfad wird in Manifest V3 nicht gegen das Erweiterungsverzeichnis
|
||||||
@@ -267,8 +270,12 @@ async function dialogOeffnen(art, daten) {
|
|||||||
await browser.windows.create({
|
await browser.windows.create({
|
||||||
url,
|
url,
|
||||||
type: "popup",
|
type: "popup",
|
||||||
width: art === "einfuegen" ? 760 : 820,
|
// Der Schrittdialog steht zweispaltig. Die Höhe ist auf den Inhalt
|
||||||
height: art === "einfuegen" ? 640 : 720
|
// gerechnet: rund 540 px für die Spalten plus Kopf, Meldung und
|
||||||
|
// Fusszeile, dazu etwas Spielraum - die Systemschrift fällt je
|
||||||
|
// nach Umgebung anders aus.
|
||||||
|
width: { schritt: 980, einfuegen: 760 }[art] || 820,
|
||||||
|
height: { schritt: 760, einfuegen: 640 }[art] || 520
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Manche Stände lehnen type "popup" ab. Dann ein gewöhnliches Fenster.
|
// Manche Stände lehnen type "popup" ab. Dann ein gewöhnliches Fenster.
|
||||||
@@ -297,6 +304,11 @@ const verbindungen = new Set();
|
|||||||
browser.runtime.onConnect.addListener((port) => {
|
browser.runtime.onConnect.addListener((port) => {
|
||||||
verbindungen.add(port);
|
verbindungen.add(port);
|
||||||
console.debug("[paperless] Dialog verbunden, offen:", verbindungen.size);
|
console.debug("[paperless] Dialog verbunden, offen:", verbindungen.size);
|
||||||
|
|
||||||
|
// Die Lebenszeichen werden nicht ausgewertet - allein ihr Eintreffen
|
||||||
|
// hält die Ereignisseite wach.
|
||||||
|
port.onMessage.addListener(() => {});
|
||||||
|
|
||||||
port.onDisconnect.addListener(() => {
|
port.onDisconnect.addListener(() => {
|
||||||
verbindungen.delete(port);
|
verbindungen.delete(port);
|
||||||
console.debug("[paperless] Dialog getrennt, offen:", verbindungen.size);
|
console.debug("[paperless] Dialog getrennt, offen:", verbindungen.size);
|
||||||
@@ -345,8 +357,15 @@ browser.runtime.onMessage.addListener(async (nachricht, absender) => {
|
|||||||
return { ok: true, daten: { tags, korr, typen } };
|
return { ok: true, daten: { tags, korr, typen } };
|
||||||
}
|
}
|
||||||
|
|
||||||
case "ablegen":
|
case "schrittOeffnen":
|
||||||
return { ok: true, daten: await ablegen(nachricht) };
|
await dialogOeffnen("schritt", nachricht.auftrag);
|
||||||
|
return { ok: true };
|
||||||
|
|
||||||
|
case "dateiListe":
|
||||||
|
return { ok: true, daten: dateiListe(nachricht.auftrag) };
|
||||||
|
|
||||||
|
case "ablegenEine":
|
||||||
|
return { ok: true, daten: await ablegenEine(nachricht) };
|
||||||
|
|
||||||
case "klassifizieren": {
|
case "klassifizieren": {
|
||||||
const c = await client();
|
const c = await client();
|
||||||
@@ -754,38 +773,91 @@ async function anhangAlsPdf(datei, beschreibung, s) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ablegen(nachricht) {
|
/**
|
||||||
|
* Die Dateien eines Auftrags aufzählen, ohne sie zu laden.
|
||||||
|
*
|
||||||
|
* Der Dialog fragt damit ab, wie viele Schritte anstehen, und ruft dann
|
||||||
|
* jeden einzeln auf.
|
||||||
|
*/
|
||||||
|
function dateiListe(auftrag) {
|
||||||
|
if (auftrag.quelle === "nachricht") {
|
||||||
|
return [{ art: "nachricht" }];
|
||||||
|
}
|
||||||
|
return (auftrag.dateien || [])
|
||||||
|
.map((d, i) => ({ art: "anhang", index: i, name: d.name }))
|
||||||
|
.filter((_x, i) => auftrag.dateien[i].ausgewaehlt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EINE Datei ablegen.
|
||||||
|
*
|
||||||
|
* Bewusst je Datei ein eigener Aufruf statt einer Schleife über alle:
|
||||||
|
*
|
||||||
|
* * Die Ereignisseite wird in Manifest V3 beendet, sobald sie untätig
|
||||||
|
* scheint. Ein Aufruf über zehn Anhänge lief regelmäßig in
|
||||||
|
* "Receiving end does not exist".
|
||||||
|
* * Der Dialog kann den Fortschritt anzeigen.
|
||||||
|
* * Scheitert eine Datei, sind die übrigen trotzdem abgelegt.
|
||||||
|
*/
|
||||||
|
async function ablegenEine(nachricht) {
|
||||||
const c = await client();
|
const c = await client();
|
||||||
const s = await ladeEinstellungen();
|
const s = await ladeEinstellungen();
|
||||||
const dateien = await datenHolen(nachricht.auftrag, s);
|
const auftrag = nachricht.auftrag;
|
||||||
if (!dateien.length) throw new PaperlessError(t("errNothingSelected"));
|
const eintrag = nachricht.eintrag;
|
||||||
|
|
||||||
const ergebnisse = [];
|
let datei;
|
||||||
for (const datei of dateien) {
|
if (eintrag.art === "nachricht") {
|
||||||
if (nachricht.ziel === "vorhanden") {
|
const alle = await datenHolen(auftrag, s);
|
||||||
const antwort = await c.updateVersion(
|
datei = alle[0];
|
||||||
nachricht.zielId, datei.blob, datei.name,
|
} else {
|
||||||
versionslabel(s.versionLabel));
|
const beschreibung = auftrag.dateien[eintrag.index];
|
||||||
const id = await c.waitForTask(antwort, (sek) => {
|
const roh = await browser.messages.getAttachmentFile(
|
||||||
if (sek % 15 === 0) {
|
beschreibung.messageId, beschreibung.partName);
|
||||||
console.debug("[paperless] warte auf Aufgabe:", sek, "s");
|
|
||||||
}
|
if (beschreibung.alsPdf
|
||||||
});
|
&& umwandelbar(beschreibung.contentType, beschreibung.name)) {
|
||||||
ergebnisse.push({ id, name: datei.name, art: "version",
|
try {
|
||||||
zielId: nachricht.zielId });
|
datei = await anhangAlsPdf(roh, beschreibung, s);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[paperless] Aufbereitung gescheitert",
|
||||||
|
beschreibung.name, err);
|
||||||
|
datei = { blob: roh, name: sichererName(beschreibung.name) };
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const felder = { title: nachricht.titel || datei.name };
|
datei = { blob: roh, name: sichererName(beschreibung.name) };
|
||||||
if (nachricht.erstellt) felder.created = nachricht.erstellt;
|
|
||||||
const antwort = await c.postDocument(datei.blob, datei.name, felder);
|
|
||||||
const id = await c.waitForTask(antwort, (sek) => {
|
|
||||||
if (sek % 15 === 0) {
|
|
||||||
console.debug("[paperless] warte auf Aufgabe:", sek, "s");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ergebnisse.push({ id, name: datei.name, art: "neu" });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ergebnisse;
|
|
||||||
|
if (!datei) throw new PaperlessError(t("errNothingSelected"));
|
||||||
|
|
||||||
|
const melden = (sek) => {
|
||||||
|
if (sek % 15 === 0) {
|
||||||
|
console.debug("[paperless] warte auf Aufgabe:", sek, "s");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (nachricht.ziel === "vorhanden") {
|
||||||
|
const antwort = await c.updateVersion(
|
||||||
|
nachricht.zielId, datei.blob, datei.name,
|
||||||
|
versionslabel(s.versionLabel));
|
||||||
|
const id = await c.waitForTask(antwort, melden);
|
||||||
|
return { id, name: datei.name, art: "version", zielId: nachricht.zielId };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bei mehreren Dateien wird der Titel um den Dateinamen ergänzt -
|
||||||
|
// sonst hiessen alle gleich und wären im Archiv nicht auseinander-
|
||||||
|
// zuhalten.
|
||||||
|
const grundtitel = nachricht.titel || datei.name;
|
||||||
|
const titel = nachricht.mehrere
|
||||||
|
? `${grundtitel} - ${datei.name.replace(/\.[^.]{1,8}$/, "")}`
|
||||||
|
: grundtitel;
|
||||||
|
|
||||||
|
const felder = { title: titel.slice(0, 127) };
|
||||||
|
if (nachricht.erstellt) felder.created = nachricht.erstellt;
|
||||||
|
|
||||||
|
const antwort = await c.postDocument(datei.blob, datei.name, felder);
|
||||||
|
const id = await c.waitForTask(antwort, melden);
|
||||||
|
return { id, name: datei.name, art: "neu" };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function aufloesen(c, pfad, name) {
|
async function aufloesen(c, pfad, name) {
|
||||||
|
|||||||
2
build.sh
2
build.sh
@@ -16,7 +16,7 @@ OUT="${NAME}-${VERSION}.xpi"
|
|||||||
FEHLT=""
|
FEHLT=""
|
||||||
for f in manifest.json background.js \
|
for f in manifest.json background.js \
|
||||||
lib/client.js lib/settings.js lib/pdf.js lib/mailpdf.js \
|
lib/client.js lib/settings.js lib/pdf.js lib/mailpdf.js \
|
||||||
ui/store.html ui/store.js ui/classify.html ui/classify.js \
|
ui/store.html ui/store.js ui/schritt.html ui/schritt.js \
|
||||||
ui/insert.html ui/insert.js ui/options.html ui/options.js \
|
ui/insert.html ui/insert.js ui/options.html ui/options.js \
|
||||||
ui/gemeinsam.js ui/gemeinsam.css \
|
ui/gemeinsam.js ui/gemeinsam.css \
|
||||||
_locales/de/messages.json _locales/en/messages.json \
|
_locales/de/messages.json _locales/en/messages.json \
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "__MSG_extName__",
|
"name": "__MSG_extName__",
|
||||||
"description": "__MSG_extDescription__",
|
"description": "__MSG_extDescription__",
|
||||||
"version": "1.12.0",
|
"version": "1.14.3",
|
||||||
"author": "Name des Herausgebers",
|
"author": "Marco Morath",
|
||||||
"homepage_url": "https://example.org/paperless-thunderbird",
|
"homepage_url": "https://gitea.cloud.mmorath.de/marco.morath/thunderbird-paperless-connector",
|
||||||
"default_locale": "de",
|
"default_locale": "de",
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "paperless@example.org",
|
"id": "webmaster@mmorath.de",
|
||||||
"strict_min_version": "128.0"
|
"strict_min_version": "128.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
"storage",
|
"storage",
|
||||||
"menus",
|
"menus",
|
||||||
"messagesRead",
|
"messagesRead",
|
||||||
"accountsRead",
|
|
||||||
"compose",
|
"compose",
|
||||||
"notifications"
|
"notifications"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
<div id="meldung" class="hinweis"></div>
|
<div id="meldung" class="hinweis"></div>
|
||||||
|
|
||||||
<div class="fuss">
|
<div class="fuss">
|
||||||
|
<button id="abbrechenAlle" data-i18n="btnSkipAll">Alle überspringen</button>
|
||||||
<button id="ueberspringen" data-i18n="btnSkip">Überspringen</button>
|
<button id="ueberspringen" data-i18n="btnSkip">Überspringen</button>
|
||||||
<button id="speichern" class="haupt" data-i18n="btnSave">Speichern</button>
|
<button id="speichern" class="haupt" data-i18n="btnSave">Speichern</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ const $ = (id) => document.getElementById(id);
|
|||||||
let dokumentId = null;
|
let dokumentId = null;
|
||||||
let alleTags = [];
|
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
|
// Gewählte Tags als Menge. Bewusst getrennt vom angezeigten Zustand der
|
||||||
// Kontrollkästchen: Der Filter blendet Zeilen aus, die Auswahl darf davon
|
// Kontrollkästchen: Der Filter blendet Zeilen aus, die Auswahl darf davon
|
||||||
// nicht berührt werden.
|
// nicht berührt werden.
|
||||||
@@ -24,38 +30,82 @@ document.addEventListener("DOMContentLoaded", start);
|
|||||||
async function start() {
|
async function start() {
|
||||||
uebersetzeSeite();
|
uebersetzeSeite();
|
||||||
const p = new URLSearchParams(location.search);
|
const p = new URLSearchParams(location.search);
|
||||||
dokumentId = Number(p.get("id"));
|
|
||||||
const weitere = Number(p.get("weiter") || 1);
|
|
||||||
|
|
||||||
$("ueberspringen").addEventListener("click", () => window.close());
|
// "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);
|
$("speichern").addEventListener("click", speichern);
|
||||||
|
|
||||||
try {
|
if (!liste.length) {
|
||||||
const [doc, stamm] = await Promise.all([
|
fehlerAnzeigen("Kein Dokument angegeben.");
|
||||||
ruf("dokument", { id: dokumentId }),
|
return;
|
||||||
ruf("stammdaten")
|
}
|
||||||
]);
|
|
||||||
alleTags = stamm.tags;
|
|
||||||
|
|
||||||
$("quelle").textContent =
|
try {
|
||||||
`#${dokumentId} · ${kurz(doc.title, 70)}`
|
const stamm = await ruf("stammdaten");
|
||||||
+ (weitere > 1
|
alleTags = stamm.tags;
|
||||||
? ` · ${browser.i18n.getMessage("classifyMoreStored",
|
fuelleListe("korrListe", stamm.korr);
|
||||||
[String(weitere)]) || `${weitere} Dateien abgelegt`}`
|
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 || "";
|
$("titel").value = doc.title || "";
|
||||||
$("erstellt").value = (doc.created || "").slice(0, 10);
|
$("erstellt").value = (doc.created || "").slice(0, 10);
|
||||||
fuelleListe("korrListe", stamm.korr);
|
$("korr").value = nameVon(zustand.korr, doc.correspondent);
|
||||||
fuelleListe("typListe", stamm.typen);
|
$("typ").value = nameVon(zustand.typen, doc.document_type);
|
||||||
$("korr").value = nameVon(stamm.korr, doc.correspondent);
|
|
||||||
$("typ").value = nameVon(stamm.typen, doc.document_type);
|
|
||||||
zeigeTags(doc.tags || []);
|
zeigeTags(doc.tags || []);
|
||||||
|
if ($("tagSuche")) $("tagSuche").value = "";
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
fehlerAnzeigen(err.message);
|
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) {
|
function fuelleListe(id, eintraege) {
|
||||||
const dl = $(id);
|
const dl = $(id);
|
||||||
dl.replaceChildren();
|
dl.replaceChildren();
|
||||||
@@ -204,7 +254,7 @@ async function speichern() {
|
|||||||
typName: $("typ").value.trim()
|
typName: $("typ").value.trim()
|
||||||
});
|
});
|
||||||
meldungAnzeigen(browser.i18n.getMessage("msgSaved") || "Gespeichert.");
|
meldungAnzeigen(browser.i18n.getMessage("msgSaved") || "Gespeichert.");
|
||||||
setTimeout(() => window.close(), 900);
|
setTimeout(weiter, 600);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
fehlerAnzeigen(err.message);
|
fehlerAnzeigen(err.message);
|
||||||
$("speichern").disabled = false;
|
$("speichern").disabled = false;
|
||||||
|
|||||||
116
ui/gemeinsam.css
116
ui/gemeinsam.css
@@ -1,16 +1,21 @@
|
|||||||
/* Gemeinsames Aussehen der Dialoge. Bewusst zurückhaltend, damit sie sich
|
/* Gemeinsames Aussehen der Dialoge.
|
||||||
in die Oberfläche von Thunderbird einfügen statt sich abzuheben. */
|
Bewusst zurückhaltend, damit sie sich in die Oberfläche von
|
||||||
|
Thunderbird einfügen statt sich abzuheben. */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--gruen: #175420;
|
--gruen: #175420;
|
||||||
--rand: #c8ccd0;
|
--rand: #c8ccd0;
|
||||||
--hell: #f6f6f4;
|
--hell: #f6f6f4;
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
|
|
||||||
/* Muss VOR den Anzeigearten stehen und !important tragen: Eine Regel wie
|
/* Muss VOR den Anzeigearten stehen und !important tragen: Eine Regel wie
|
||||||
".wahl { display: flex }" überstimmt sonst das eingebaute
|
".wahl { display: flex }" überstimmt sonst das eingebaute
|
||||||
"[hidden] { display: none }", und ausgeblendete Elemente erscheinen. */
|
"[hidden] { display: none }", und ausgeblendete Elemente erscheinen. */
|
||||||
[hidden] { display: none !important; }
|
[hidden] { display: none !important; }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font: message-box;
|
font: message-box;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -19,9 +24,16 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
|
/* Feste Höhe UND overflow:hidden. Mit min-height wuchs die Seite über
|
||||||
|
das Fenster hinaus, und die Fußzeile lag unerreichbar darunter.
|
||||||
|
Gestaucht wird stattdessen innen: Die Listen blättern selbst. */
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 { font-size: 14px; margin: 0 0 2px; font-weight: 600; }
|
h1 { font-size: 14px; margin: 0 0 2px; font-weight: 600; }
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
border: 1px solid var(--rand);
|
border: 1px solid var(--rand);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -30,7 +42,9 @@ fieldset {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
legend { padding: 0 4px; font-weight: 600; }
|
legend { padding: 0 4px; font-weight: 600; }
|
||||||
|
|
||||||
label { display: block; margin: 4px 0 2px; }
|
label { display: block; margin: 4px 0 2px; }
|
||||||
|
|
||||||
input[type=text], input[type=password], input[type=number], select {
|
input[type=text], input[type=password], input[type=number], select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
@@ -39,14 +53,18 @@ input[type=text], input[type=password], input[type=number], select {
|
|||||||
background: Field;
|
background: Field;
|
||||||
color: FieldText;
|
color: FieldText;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zeile { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
.zeile { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
||||||
|
.zeile > input[type=text] { flex: 1; }
|
||||||
|
|
||||||
/* Kompakte Zahlenfelder mit vorangestellter Beschriftung. */
|
/* Kompakte Zahlenfelder mit vorangestellter Beschriftung. */
|
||||||
.masse { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
|
.masse { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
|
||||||
.mass { display: flex; gap: 5px; align-items: center; }
|
.mass { display: flex; gap: 5px; align-items: center; }
|
||||||
.mass label { margin: 0; white-space: nowrap; }
|
.mass label { margin: 0; white-space: nowrap; }
|
||||||
.mass input { width: 68px; }
|
.mass input { width: 68px; }
|
||||||
.zeile > input[type=text] { flex: 1; }
|
|
||||||
.wahl { display: flex; gap: 6px; align-items: center; margin: 3px 0; }
|
.wahl { display: flex; gap: 6px; align-items: center; margin: 3px 0; }
|
||||||
|
.wahl input { width: auto; margin: 0; }
|
||||||
|
|
||||||
/* Dateiliste mit Wahl der Ablageart je Datei. */
|
/* Dateiliste mit Wahl der Ablageart je Datei. */
|
||||||
.dateizeile {
|
.dateizeile {
|
||||||
@@ -58,7 +76,7 @@ input[type=text], input[type=password], input[type=number], select {
|
|||||||
.dateizeile .art label { margin: 0; white-space: nowrap; font-size: 12px; }
|
.dateizeile .art label { margin: 0; white-space: nowrap; font-size: 12px; }
|
||||||
.dateizeile .art input { width: auto; margin: 0; }
|
.dateizeile .art input { width: auto; margin: 0; }
|
||||||
.dateizeile .art label.aus { color: GrayText; }
|
.dateizeile .art label.aus { color: GrayText; }
|
||||||
.wahl input { width: auto; margin: 0; }
|
|
||||||
button {
|
button {
|
||||||
padding: 5px 14px;
|
padding: 5px 14px;
|
||||||
border: 1px solid var(--rand);
|
border: 1px solid var(--rand);
|
||||||
@@ -67,18 +85,28 @@ button {
|
|||||||
color: ButtonText;
|
color: ButtonText;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
button.haupt { background: var(--gruen); color: #fff; border-color: var(--gruen); }
|
button.haupt {
|
||||||
|
background: var(--gruen); color: #fff; border-color: var(--gruen);
|
||||||
|
}
|
||||||
button:disabled { opacity: .5; cursor: default; }
|
button:disabled { opacity: .5; cursor: default; }
|
||||||
.fuss { display: flex; justify-content: flex-end; gap: 8px; margin-top: auto; }
|
|
||||||
|
.fuss {
|
||||||
|
display: flex; justify-content: flex-end; gap: 8px; margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.liste {
|
.liste {
|
||||||
border: 1px solid var(--rand);
|
border: 1px solid var(--rand);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: Field;
|
background: Field;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 90px;
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%; border-collapse: collapse;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
|
|
||||||
th, td { padding: 3px 8px; text-align: left; white-space: nowrap; }
|
th, td { padding: 3px 8px; text-align: left; white-space: nowrap; }
|
||||||
th {
|
th {
|
||||||
position: sticky; top: 0;
|
position: sticky; top: 0;
|
||||||
@@ -89,18 +117,64 @@ th {
|
|||||||
td.zahl, th.zahl { text-align: right; }
|
td.zahl, th.zahl { text-align: right; }
|
||||||
tr.gewaehlt { background: SelectedItem; color: SelectedItemText; }
|
tr.gewaehlt { background: SelectedItem; color: SelectedItemText; }
|
||||||
tbody tr { cursor: default; }
|
tbody tr { cursor: default; }
|
||||||
.hinweis { color: GrayText; font-size: 12px; }
|
|
||||||
|
|
||||||
/* Gewählte Tags stehen oben und bleiben sichtbar, auch wenn der Filter
|
.hinweis { color: GrayText; font-size: 12px; }
|
||||||
sie ausschliesst. Sonst verliert man beim Suchen den Überblick, was
|
|
||||||
bereits angehakt ist. */
|
|
||||||
.gewaehlteTags {
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-bottom: 1px solid var(--rand);
|
|
||||||
background: color-mix(in srgb, Field 94%, CanvasText);
|
|
||||||
}
|
|
||||||
.gewaehlteTags:empty { display: none; }
|
|
||||||
.gewaehlteTags .wahl label { font-weight: 600; }
|
|
||||||
.trefferHervor { background: color-mix(in srgb, Mark 60%, transparent); }
|
|
||||||
.fehler { color: #b32; }
|
.fehler { color: #b32; }
|
||||||
.wachsen { flex: 1; display: flex; flex-direction: column; gap: 6px; min-height: 0; }
|
.trefferHervor { background: color-mix(in srgb, Mark 60%, transparent); }
|
||||||
|
|
||||||
|
.wachsen {
|
||||||
|
flex: 1; display: flex; flex-direction: column; gap: 6px; min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------- Zweispaltig */
|
||||||
|
|
||||||
|
/* KEIN flex-wrap.
|
||||||
|
Mit Umbruch bemisst sich die Hoehe einer Flexzeile nach ihrem INHALT,
|
||||||
|
nicht nach dem Behaelter - die Spalten wachsen dann, statt sich zu
|
||||||
|
stauchen, und die Liste laeuft wieder aus dem Fenster.
|
||||||
|
Der urspruengliche Dialog hatte genau deshalb keinen Umbruch. Das
|
||||||
|
Fenster ist ohnehin 980 px breit; zwei Spalten zu 340 px passen
|
||||||
|
immer. */
|
||||||
|
.spalten {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 12px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* KEIN fieldset für wachsende Bereiche.
|
||||||
|
Ein fieldset taugt nicht als Flex-Behälter: Browser behandeln seinen
|
||||||
|
inneren anonymen Kasten gesondert, height:100% löst sich darin nicht
|
||||||
|
verlässlich auf, und eine Liste darin wächst über den Rahmen hinaus.
|
||||||
|
Ein gewöhnliches Element verhält sich wie erwartet.
|
||||||
|
Der einzige sichtbare Unterschied: Die Überschrift steht innen statt
|
||||||
|
auf dem Rahmen. */
|
||||||
|
.kasten {
|
||||||
|
border: 1px solid var(--rand);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
/* Grundbreite 0 statt 340 px: Bei nowrap wuerde eine Grundbreite die
|
||||||
|
Spalten ueber das Fenster hinausschieben. Die Aufteilung ergibt
|
||||||
|
sich aus flex-grow zu gleichen Teilen. */
|
||||||
|
flex: 1 1 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.kasten > h2 {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Die Liste füllt, was nach den festen Elementen übrig bleibt, und
|
||||||
|
blättert selbst. Genau das war das Ziel - es scheiterte bisher am
|
||||||
|
fieldset, nicht an der Vorgehensweise. */
|
||||||
|
.kasten .liste {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,13 +13,33 @@
|
|||||||
*/
|
*/
|
||||||
let dauerverbindung = null;
|
let dauerverbindung = null;
|
||||||
|
|
||||||
|
let lebenszeichen = null;
|
||||||
|
|
||||||
export function verbindungHalten() {
|
export function verbindungHalten() {
|
||||||
if (dauerverbindung) return;
|
if (dauerverbindung) return;
|
||||||
try {
|
try {
|
||||||
dauerverbindung = browser.runtime.connect({ name: "dialog" });
|
dauerverbindung = browser.runtime.connect({ name: "dialog" });
|
||||||
dauerverbindung.onDisconnect.addListener(() => {
|
dauerverbindung.onDisconnect.addListener(() => {
|
||||||
dauerverbindung = null;
|
dauerverbindung = null;
|
||||||
|
if (lebenszeichen) {
|
||||||
|
clearInterval(lebenszeichen);
|
||||||
|
lebenszeichen = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Zusätzlich regelmäßig ein Lebenszeichen senden. Die offene
|
||||||
|
// Verbindung allein hat sich als nicht ausreichend erwiesen: Bei
|
||||||
|
// mehreren Dateien hintereinander wurde die Ereignisseite trotzdem
|
||||||
|
// beendet. Ein tatsächliches Ereignis alle 15 Sekunden setzt die
|
||||||
|
// Untätigkeitsuhr zuverlässig zurück.
|
||||||
|
lebenszeichen = setInterval(() => {
|
||||||
|
try {
|
||||||
|
dauerverbindung?.postMessage({ befehl: "lebenszeichen" });
|
||||||
|
} catch {
|
||||||
|
clearInterval(lebenszeichen);
|
||||||
|
lebenszeichen = null;
|
||||||
|
}
|
||||||
|
}, 15000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn("[paperless] Dauerverbindung nicht möglich:", err);
|
console.warn("[paperless] Dauerverbindung nicht möglich:", err);
|
||||||
}
|
}
|
||||||
|
|||||||
98
ui/schritt.html
Normal file
98
ui/schritt.html
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title data-i18n="stepTitle">In Paperless ablegen</title>
|
||||||
|
<link rel="stylesheet" href="gemeinsam.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1 id="kopf">In Paperless ablegen</h1>
|
||||||
|
<div id="quelle" class="hinweis"></div>
|
||||||
|
|
||||||
|
<div class="spalten">
|
||||||
|
|
||||||
|
<section class="kasten">
|
||||||
|
<h2 data-i18n="storeTarget">Ziel</h2>
|
||||||
|
|
||||||
|
<div class="wahl">
|
||||||
|
<input type="radio" name="ziel" id="zielNeu" value="neu" checked>
|
||||||
|
<label for="zielNeu" data-i18n="storeTargetNew">
|
||||||
|
Neues Dokument anlegen
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wahl">
|
||||||
|
<input type="radio" name="ziel" id="zielVorhanden" value="vorhanden">
|
||||||
|
<label for="zielVorhanden" data-i18n="storeTargetExisting">
|
||||||
|
Als neue Version an ein bestehendes anhängen
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="zeile" style="margin-top:4px">
|
||||||
|
<input type="text" id="suche" disabled>
|
||||||
|
<button id="suchen" disabled data-i18n="btnSearch">Suchen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="liste">
|
||||||
|
<table>
|
||||||
|
<thead><tr>
|
||||||
|
<th class="zahl" data-i18n="colAsn">ASN</th>
|
||||||
|
<th data-i18n="colCreated">Erstellt</th>
|
||||||
|
<th data-i18n="colTitle">Titel</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody id="treffer"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hinweis" data-i18n="stepHintExisting">
|
||||||
|
Die Klassifizierung des Ziels wird übernommen und kann angepasst
|
||||||
|
werden.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="kasten">
|
||||||
|
<h2 data-i18n="classifyTitle">Klassifizierung</h2>
|
||||||
|
|
||||||
|
<label for="titel" data-i18n="fieldTitle">Titel</label>
|
||||||
|
<input type="text" id="titel">
|
||||||
|
|
||||||
|
<div class="zeile">
|
||||||
|
<span class="mass">
|
||||||
|
<label for="erstellt" data-i18n="fieldCreated">Erstellt</label>
|
||||||
|
<input type="text" id="erstellt" placeholder="JJJJ-MM-TT"
|
||||||
|
style="width:110px">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label for="korr" data-i18n="fieldCorrespondent">Korrespondent</label>
|
||||||
|
<input type="text" id="korr" list="korrListe" autocomplete="off">
|
||||||
|
<datalist id="korrListe"></datalist>
|
||||||
|
|
||||||
|
<label for="typ" data-i18n="fieldDocType">Dokumenttyp</label>
|
||||||
|
<input type="text" id="typ" list="typListe" autocomplete="off">
|
||||||
|
<datalist id="typListe"></datalist>
|
||||||
|
|
||||||
|
<label for="tagSuche" data-i18n="fieldTags">Tags</label>
|
||||||
|
<input type="text" id="tagSuche"
|
||||||
|
data-i18n-placeholder="tagsFilterPlaceholder"
|
||||||
|
placeholder="Tippen zum Filtern …">
|
||||||
|
|
||||||
|
<div class="liste">
|
||||||
|
<div id="tags" style="padding:4px 8px"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="meldung" class="hinweis"></div>
|
||||||
|
|
||||||
|
<div class="fuss">
|
||||||
|
<button id="abbrechen" data-i18n="btnCancel">Abbrechen</button>
|
||||||
|
<button id="ueberspringen" data-i18n="btnSkip">Überspringen</button>
|
||||||
|
<button id="ablegen" class="haupt" data-i18n="btnStore">Ablegen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module" src="schritt.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
357
ui/schritt.js
Normal file
357
ui/schritt.js
Normal 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();
|
||||||
|
}
|
||||||
@@ -19,65 +19,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset class="wachsen">
|
|
||||||
<legend data-i18n="storeTarget">Ziel</legend>
|
|
||||||
|
|
||||||
<div class="wahl">
|
|
||||||
<input type="radio" name="ziel" id="zielNeu" value="neu" checked>
|
|
||||||
<label for="zielNeu" data-i18n="storeTargetNew">
|
|
||||||
Neues Dokument anlegen
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="bereichNeu">
|
|
||||||
<label for="titel" data-i18n="fieldTitle">Titel</label>
|
|
||||||
<input type="text" id="titel">
|
|
||||||
<div class="hinweis" data-i18n="storeHintTitleMulti">
|
|
||||||
Bei mehreren Dateien wird der Titel um den Dateinamen ergänzt.
|
|
||||||
</div>
|
|
||||||
<label for="erstellt" data-i18n="fieldCreated">Erstellt</label>
|
|
||||||
<input type="text" id="erstellt" placeholder="JJJJ-MM-TT">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="wahl">
|
|
||||||
<input type="radio" name="ziel" id="zielVorhanden" value="vorhanden">
|
|
||||||
<label for="zielVorhanden" data-i18n="storeTargetExisting">
|
|
||||||
An bestehendes Dokument anhängen
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="bereichVorhanden" class="wachsen">
|
|
||||||
<div class="zeile">
|
|
||||||
<input type="text" id="suche" disabled>
|
|
||||||
<button id="suchen" disabled data-i18n="btnSearch">Suchen</button>
|
|
||||||
</div>
|
|
||||||
<div class="liste">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="zahl" data-i18n="colAsn">ASN</th>
|
|
||||||
<th data-i18n="colCreated">Erstellt</th>
|
|
||||||
<th data-i18n="colTitle">Titel</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="treffer"></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<div class="wahl">
|
|
||||||
<input type="checkbox" id="klassifizieren" checked>
|
|
||||||
<label for="klassifizieren" data-i18n="storeClassifyAfter">
|
|
||||||
Nach dem Ablegen die Klassifizierung bearbeiten
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="meldung" class="hinweis"></div>
|
<div id="meldung" class="hinweis"></div>
|
||||||
|
|
||||||
<div class="fuss">
|
<div class="fuss">
|
||||||
<button id="abbrechen" data-i18n="btnCancel">Abbrechen</button>
|
<button id="abbrechen" data-i18n="btnCancel">Abbrechen</button>
|
||||||
<button id="ablegen" class="haupt" data-i18n="btnStore">Ablegen</button>
|
<button id="ablegen" class="haupt" data-i18n="btnNext">Weiter</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="module" src="store.js"></script>
|
<script type="module" src="store.js"></script>
|
||||||
|
|||||||
126
ui/store.js
126
ui/store.js
@@ -8,14 +8,13 @@
|
|||||||
|
|
||||||
import { groesse, kurz, titelAusBetreff } from "../lib/settings.js";
|
import { groesse, kurz, titelAusBetreff } from "../lib/settings.js";
|
||||||
import { umwandelbar } from "../lib/mailpdf.js";
|
import { umwandelbar } from "../lib/mailpdf.js";
|
||||||
import { uebersetzeSeite, ruf, fehlerAnzeigen } from "./gemeinsam.js";
|
import { uebersetzeSeite, ruf, fehlerAnzeigen, meldungAnzeigen }
|
||||||
|
from "./gemeinsam.js";
|
||||||
|
|
||||||
const $ = (id) => document.getElementById(id);
|
const $ = (id) => document.getElementById(id);
|
||||||
|
|
||||||
let auftrag = null;
|
let auftrag = null;
|
||||||
let einstellungen = null;
|
let einstellungen = null;
|
||||||
let treffer = [];
|
|
||||||
let gewaehlterTreffer = null;
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", start);
|
document.addEventListener("DOMContentLoaded", start);
|
||||||
|
|
||||||
@@ -32,7 +31,6 @@ async function start() {
|
|||||||
|
|
||||||
quelleAnzeigen();
|
quelleAnzeigen();
|
||||||
dateienAnzeigen();
|
dateienAnzeigen();
|
||||||
vorbelegen();
|
|
||||||
verdrahten();
|
verdrahten();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,112 +173,40 @@ function dateiZeile(o) {
|
|||||||
return zeile;
|
return zeile;
|
||||||
}
|
}
|
||||||
|
|
||||||
function vorbelegen() {
|
/**
|
||||||
$("titel").value = titelAusBetreff(auftrag.betreff);
|
* Weiter zum Schrittdialog.
|
||||||
if (auftrag.datum) $("erstellt").value = auftrag.datum;
|
*
|
||||||
}
|
* Die Auswahl wird über einen neuen Auftrag übergeben - das Fenster
|
||||||
|
* schliesst sich, und der Schrittdialog geht die Dateien einzeln durch.
|
||||||
function verdrahten() {
|
*/
|
||||||
for (const r of document.querySelectorAll('input[name=ziel]')) {
|
async function weiter() {
|
||||||
r.addEventListener("change", zielGewechselt);
|
|
||||||
}
|
|
||||||
$("suchen").addEventListener("click", suchen);
|
|
||||||
$("suche").addEventListener("keydown", (e) => {
|
|
||||||
if (e.key === "Enter") suchen();
|
|
||||||
});
|
|
||||||
$("abbrechen").addEventListener("click", () => window.close());
|
|
||||||
$("ablegen").addEventListener("click", ablegen);
|
|
||||||
zielGewechselt();
|
|
||||||
}
|
|
||||||
|
|
||||||
function zielGewechselt() {
|
|
||||||
const neu = $("zielNeu").checked;
|
|
||||||
for (const id of ["titel", "erstellt"]) $(id).disabled = !neu;
|
|
||||||
for (const id of ["suche", "suchen"]) $(id).disabled = neu;
|
|
||||||
if (!neu && !treffer.length) suchen();
|
|
||||||
knopfPruefen();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function suchen() {
|
|
||||||
const liste = $("treffer");
|
|
||||||
liste.replaceChildren();
|
|
||||||
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 asn = document.createElement("td");
|
|
||||||
asn.className = "zahl";
|
|
||||||
asn.textContent = r.archive_serial_number ?? "—";
|
|
||||||
const erstellt = document.createElement("td");
|
|
||||||
erstellt.textContent = (r.created || "").slice(0, 10);
|
|
||||||
const titel = document.createElement("td");
|
|
||||||
titel.textContent = r.title || "";
|
|
||||||
titel.title = r.title || "";
|
|
||||||
tr.append(asn, erstellt, titel);
|
|
||||||
tr.addEventListener("click", () => waehle(i));
|
|
||||||
liste.append(tr);
|
|
||||||
}
|
|
||||||
if (treffer.length) waehle(0);
|
|
||||||
else fehlerAnzeigen(browser.i18n.getMessage("msgNoHits") || "Keine Treffer.");
|
|
||||||
}
|
|
||||||
|
|
||||||
function waehle(i) {
|
|
||||||
gewaehlterTreffer = treffer[i] || null;
|
|
||||||
for (const [j, tr] of [...$("treffer").children].entries()) {
|
|
||||||
tr.classList.toggle("gewaehlt", j === i);
|
|
||||||
}
|
|
||||||
knopfPruefen();
|
|
||||||
}
|
|
||||||
|
|
||||||
function knopfPruefen() {
|
|
||||||
const etwasGewaehlt = auftrag.quelle === "nachricht"
|
|
||||||
|| (auftrag.dateien || []).some((d) => d.ausgewaehlt);
|
|
||||||
const zielOk = $("zielNeu").checked || Boolean(gewaehlterTreffer);
|
|
||||||
$("ablegen").disabled = !(etwasGewaehlt && zielOk);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function ablegen() {
|
|
||||||
$("ablegen").disabled = true;
|
$("ablegen").disabled = true;
|
||||||
$("meldung").className = "hinweis";
|
meldungAnzeigen(browser.i18n.getMessage("msgOpeningStep")
|
||||||
$("meldung").textContent =
|
|| "Wird vorbereitet …");
|
||||||
browser.i18n.getMessage("msgUploading") || "Wird abgelegt …";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ergebnisse = await ruf("ablegen", {
|
await ruf("schrittOeffnen", {
|
||||||
auftrag: {
|
auftrag: {
|
||||||
...auftrag,
|
...auftrag,
|
||||||
// Bei einer ganzen Nachricht entscheidet die eine Auswahl, bei
|
titelVorschlag: titelAusBetreff(auftrag.betreff),
|
||||||
// Anhängen steht die Wahl je Datei im Eintrag selbst.
|
|
||||||
alsPdf: auftrag.quelle === "nachricht"
|
alsPdf: auftrag.quelle === "nachricht"
|
||||||
? $("art_ganz_pdf")?.checked ?? true
|
? $("art_ganz_pdf")?.checked ?? true
|
||||||
: undefined
|
: undefined
|
||||||
},
|
}
|
||||||
ziel: $("zielNeu").checked ? "neu" : "vorhanden",
|
|
||||||
zielId: gewaehlterTreffer?.id,
|
|
||||||
titel: $("titel").value.trim(),
|
|
||||||
erstellt: $("erstellt").value.trim()
|
|
||||||
});
|
});
|
||||||
|
window.close();
|
||||||
if ($("klassifizieren").checked && ergebnisse.length) {
|
|
||||||
// Beim Anhängen an ein vorhandenes Dokument gilt dessen
|
|
||||||
// Klassifizierung - dann ist die Wurzel gemeint, nicht die neue
|
|
||||||
// Version.
|
|
||||||
const erstes = ergebnisse[0];
|
|
||||||
const id = erstes.art === "version" ? erstes.zielId : erstes.id;
|
|
||||||
location.href = `classify.html?id=${id}&weiter=${ergebnisse.length}`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$("meldung").textContent =
|
|
||||||
browser.i18n.getMessage("msgStored", [String(ergebnisse.length)])
|
|
||||||
|| `${ergebnisse.length} abgelegt.`;
|
|
||||||
setTimeout(() => window.close(), 1200);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
fehlerAnzeigen(err.message);
|
fehlerAnzeigen(err.message);
|
||||||
$("ablegen").disabled = false;
|
$("ablegen").disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function verdrahten() {
|
||||||
|
$("abbrechen").addEventListener("click", () => window.close());
|
||||||
|
$("ablegen").addEventListener("click", weiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
function knopfPruefen() {
|
||||||
|
const etwasGewaehlt = auftrag.quelle === "nachricht"
|
||||||
|
|| (auftrag.dateien || []).some((d) => d.ausgewaehlt);
|
||||||
|
$("ablegen").disabled = !etwasGewaehlt;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user