Initial commit
This commit is contained in:
44
ui/classify.html
Normal file
44
ui/classify.html
Normal file
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title data-i18n="classifyTitle">Klassifizierung bearbeiten</title>
|
||||
<link rel="stylesheet" href="gemeinsam.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1 data-i18n="classifyTitle">Klassifizierung bearbeiten</h1>
|
||||
<div id="quelle" class="hinweis"></div>
|
||||
|
||||
<label for="titel" data-i18n="fieldTitle">Titel</label>
|
||||
<input type="text" id="titel">
|
||||
|
||||
<label for="erstellt" data-i18n="fieldCreated">Erstellt</label>
|
||||
<input type="text" id="erstellt" placeholder="JJJJ-MM-TT">
|
||||
|
||||
<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>
|
||||
|
||||
<div class="wachsen">
|
||||
<label data-i18n="fieldTags">Tags</label>
|
||||
<div class="liste"><div id="tags" style="padding:4px 8px"></div></div>
|
||||
</div>
|
||||
|
||||
<div class="hinweis" data-i18n="classifyHintNew">
|
||||
Unbekannte Korrespondenten und Dokumenttypen werden angelegt.
|
||||
</div>
|
||||
|
||||
<div id="meldung" class="hinweis"></div>
|
||||
|
||||
<div class="fuss">
|
||||
<button id="ueberspringen" data-i18n="btnSkip">Überspringen</button>
|
||||
<button id="speichern" class="haupt" data-i18n="btnSave">Speichern</button>
|
||||
</div>
|
||||
|
||||
<script type="module" src="classify.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
109
ui/classify.js
Normal file
109
ui/classify.js
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 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 = [];
|
||||
|
||||
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) {
|
||||
const ziel = $("tags");
|
||||
ziel.replaceChildren();
|
||||
for (const tag of alleTags) {
|
||||
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 = gesetzt.includes(tag.id);
|
||||
const bez = document.createElement("label");
|
||||
bez.htmlFor = box.id;
|
||||
bez.textContent = tag.name;
|
||||
zeile.append(box, bez);
|
||||
ziel.append(zeile);
|
||||
}
|
||||
}
|
||||
|
||||
async function speichern() {
|
||||
$("speichern").disabled = true;
|
||||
meldungAnzeigen(browser.i18n.getMessage("msgSaving") || "Wird gespeichert …");
|
||||
try {
|
||||
const tags = [...$("tags").querySelectorAll("input:checked")]
|
||||
.map((b) => Number(b.value));
|
||||
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;
|
||||
}
|
||||
}
|
||||
74
ui/gemeinsam.css
Normal file
74
ui/gemeinsam.css
Normal file
@@ -0,0 +1,74 @@
|
||||
/* Gemeinsames Aussehen der Dialoge. Bewusst zurückhaltend, damit sie sich
|
||||
in die Oberfläche von Thunderbird einfügen statt sich abzuheben. */
|
||||
:root {
|
||||
--gruen: #175420;
|
||||
--rand: #c8ccd0;
|
||||
--hell: #f6f6f4;
|
||||
color-scheme: light dark;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
font: message-box;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
padding: 12px 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
height: 100vh;
|
||||
}
|
||||
h1 { font-size: 14px; margin: 0 0 2px; font-weight: 600; }
|
||||
fieldset {
|
||||
border: 1px solid var(--rand);
|
||||
border-radius: 4px;
|
||||
margin: 0;
|
||||
padding: 8px 10px 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
legend { padding: 0 4px; font-weight: 600; }
|
||||
label { display: block; margin: 4px 0 2px; }
|
||||
input[type=text], input[type=password], input[type=number], select {
|
||||
width: 100%;
|
||||
padding: 4px 6px;
|
||||
border: 1px solid var(--rand);
|
||||
border-radius: 3px;
|
||||
background: Field;
|
||||
color: FieldText;
|
||||
}
|
||||
.zeile { display: flex; gap: 8px; align-items: center; }
|
||||
.zeile > input[type=text] { flex: 1; }
|
||||
.wahl { display: flex; gap: 6px; align-items: center; margin: 3px 0; }
|
||||
.wahl input { width: auto; margin: 0; }
|
||||
button {
|
||||
padding: 5px 14px;
|
||||
border: 1px solid var(--rand);
|
||||
border-radius: 3px;
|
||||
background: ButtonFace;
|
||||
color: ButtonText;
|
||||
cursor: pointer;
|
||||
}
|
||||
button.haupt { background: var(--gruen); color: #fff; border-color: var(--gruen); }
|
||||
button:disabled { opacity: .5; cursor: default; }
|
||||
.fuss { display: flex; justify-content: flex-end; gap: 8px; margin-top: auto; }
|
||||
.liste {
|
||||
border: 1px solid var(--rand);
|
||||
border-radius: 3px;
|
||||
overflow: auto;
|
||||
background: Field;
|
||||
flex: 1;
|
||||
min-height: 90px;
|
||||
}
|
||||
table { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
|
||||
th, td { padding: 3px 8px; text-align: left; white-space: nowrap; }
|
||||
th {
|
||||
position: sticky; top: 0;
|
||||
background: var(--hell);
|
||||
border-bottom: 1px solid var(--rand);
|
||||
font-weight: 600;
|
||||
}
|
||||
td.zahl, th.zahl { text-align: right; }
|
||||
tr.gewaehlt { background: SelectedItem; color: SelectedItemText; }
|
||||
tbody tr { cursor: default; }
|
||||
.hinweis { color: GrayText; font-size: 12px; }
|
||||
.fehler { color: #b32; }
|
||||
.wachsen { flex: 1; display: flex; flex-direction: column; gap: 6px; min-height: 0; }
|
||||
48
ui/gemeinsam.js
Normal file
48
ui/gemeinsam.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* gemeinsam.js - Hilfsmittel für alle Dialogseiten.
|
||||
*/
|
||||
|
||||
/** Ruft die Ereignisseite und wirft bei Fehlern eine Ausnahme. */
|
||||
export async function ruf(befehl, daten = {}) {
|
||||
const antwort = await browser.runtime.sendMessage({ befehl, ...daten });
|
||||
if (!antwort?.ok) {
|
||||
throw new Error(antwort?.fehler || `Aufruf ${befehl} fehlgeschlagen`);
|
||||
}
|
||||
return antwort.daten;
|
||||
}
|
||||
|
||||
/**
|
||||
* Beschriftungen aus _locales einsetzen.
|
||||
*
|
||||
* Elemente mit data-i18n bekommen ihren Text, solche mit
|
||||
* data-i18n-placeholder ihren Platzhalter. Fehlt ein Eintrag, bleibt der
|
||||
* im HTML hinterlegte deutsche Text stehen.
|
||||
*/
|
||||
export function uebersetzeSeite() {
|
||||
for (const el of document.querySelectorAll("[data-i18n]")) {
|
||||
const s = browser.i18n.getMessage(el.dataset.i18n);
|
||||
if (s) el.textContent = s;
|
||||
}
|
||||
for (const el of document.querySelectorAll("[data-i18n-placeholder]")) {
|
||||
const s = browser.i18n.getMessage(el.dataset.i18nPlaceholder);
|
||||
if (s) el.placeholder = s;
|
||||
}
|
||||
for (const el of document.querySelectorAll("[data-i18n-title]")) {
|
||||
const s = browser.i18n.getMessage(el.dataset.i18nTitle);
|
||||
if (s) el.title = s;
|
||||
}
|
||||
}
|
||||
|
||||
export function fehlerAnzeigen(text) {
|
||||
const ziel = document.getElementById("meldung");
|
||||
if (!ziel) return;
|
||||
ziel.className = "fehler";
|
||||
ziel.textContent = String(text || "").slice(0, 300);
|
||||
}
|
||||
|
||||
export function meldungAnzeigen(text) {
|
||||
const ziel = document.getElementById("meldung");
|
||||
if (!ziel) return;
|
||||
ziel.className = "hinweis";
|
||||
ziel.textContent = String(text || "");
|
||||
}
|
||||
83
ui/insert.html
Normal file
83
ui/insert.html
Normal file
@@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title data-i18n="insertTitle">Aus Paperless einfügen</title>
|
||||
<link rel="stylesheet" href="gemeinsam.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1 data-i18n="insertTitle">Aus Paperless einfügen</h1>
|
||||
|
||||
<div class="zeile">
|
||||
<input type="text" id="suche">
|
||||
<button id="suchen" data-i18n="btnSearch">Suchen</button>
|
||||
</div>
|
||||
|
||||
<div class="wachsen">
|
||||
<label data-i18n="colDocuments">Dokumente</label>
|
||||
<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>
|
||||
|
||||
<div class="wachsen">
|
||||
<label data-i18n="colVersions">Versionen</label>
|
||||
<div class="liste" style="min-height:70px">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th class="zahl" data-i18n="colNo">Nr</th>
|
||||
<th data-i18n="colCreated">Erstellt</th>
|
||||
<th data-i18n="colFormat">Format</th>
|
||||
<th data-i18n="colLabel">Bezeichnung</th>
|
||||
</tr></thead>
|
||||
<tbody id="versionen"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend data-i18n="insertHow">Einfügen als</legend>
|
||||
<div class="wahl">
|
||||
<input type="radio" name="art" id="artAnhang" value="anhang" checked>
|
||||
<label for="artAnhang" data-i18n="insertAsAttachment">
|
||||
Anhang der Nachricht
|
||||
</label>
|
||||
</div>
|
||||
<div class="wahl">
|
||||
<input type="radio" name="art" id="artLink" value="link">
|
||||
<label for="artLink" data-i18n="insertAsLink">
|
||||
Freigabelink im Nachrichtentext
|
||||
</label>
|
||||
</div>
|
||||
<div id="linkOptionen" class="zeile" style="margin-top:6px">
|
||||
<label for="tage" style="margin:0" data-i18n="fieldShareDays">
|
||||
Gültigkeit in Tagen
|
||||
</label>
|
||||
<input type="number" id="tage" min="0" max="3650" style="width:80px">
|
||||
<label for="tagName" style="margin:0 0 0 10px" data-i18n="fieldShareTag">
|
||||
Tag
|
||||
</label>
|
||||
<input type="text" id="tagName">
|
||||
</div>
|
||||
<div class="hinweis" data-i18n="insertHintLink">
|
||||
Der Link ist ohne Anmeldung abrufbar. 0 Tage bedeutet unbegrenzt.
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div id="meldung" class="hinweis"></div>
|
||||
|
||||
<div class="fuss">
|
||||
<button id="abbrechen" data-i18n="btnCancel">Abbrechen</button>
|
||||
<button id="einfuegen" class="haupt" data-i18n="btnInsert">Einfügen</button>
|
||||
</div>
|
||||
|
||||
<script type="module" src="insert.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
211
ui/insert.js
Normal file
211
ui/insert.js
Normal file
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* insert.js - Dokument aus Paperless in einen Entwurf einfügen.
|
||||
*
|
||||
* Zwei Wege: als Anhang oder als Freigabelink. Der Link ist die bessere
|
||||
* Wahl für grosse Dateien und für Empfänger, die ohnehin Zugriff auf das
|
||||
* Archiv haben sollen - der Anhang für alles, was auch in fünf Jahren noch
|
||||
* lesbar sein muss, wenn der Link längst abgelaufen ist.
|
||||
*/
|
||||
|
||||
import { uebersetzeSeite, ruf, fehlerAnzeigen, meldungAnzeigen }
|
||||
from "./gemeinsam.js";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
let auftrag = null;
|
||||
let einstellungen = null;
|
||||
let treffer = [];
|
||||
let versionen = [];
|
||||
let gewaehltesDokument = null;
|
||||
let gewaehlteVersion = null;
|
||||
|
||||
// Formate, die LibreOffice und Thunderbird sinnvoll darstellen. Nur für
|
||||
// die Anzeige - eingefügt werden kann jedes.
|
||||
const FORMATE = {
|
||||
"application/pdf": "PDF",
|
||||
"application/vnd.oasis.opendocument.text": "ODF Text",
|
||||
"application/vnd.oasis.opendocument.spreadsheet": "ODF Tabelle",
|
||||
"application/vnd.oasis.opendocument.presentation": "ODF Präsentation",
|
||||
"application/msword": "Word 97",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "Word",
|
||||
"application/vnd.ms-excel": "Excel 97",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "Excel",
|
||||
"message/rfc822": "E-Mail",
|
||||
"image/png": "Bild",
|
||||
"image/jpeg": "Bild",
|
||||
"text/plain": "Text"
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", start);
|
||||
|
||||
async function start() {
|
||||
uebersetzeSeite();
|
||||
const nummer = Number(new URLSearchParams(location.search).get("auftrag"));
|
||||
einstellungen = await ruf("einstellungen");
|
||||
auftrag = await ruf("auftrag", { nummer });
|
||||
if (!auftrag) {
|
||||
fehlerAnzeigen("Der Auftrag ist nicht mehr vorhanden.");
|
||||
return;
|
||||
}
|
||||
|
||||
$("tage").value = String(einstellungen.shareDays ?? 7);
|
||||
// ?? statt ||: Ein bewusst geleertes Feld in den Einstellungen soll
|
||||
// leer bleiben. Mit || käme die Vorgabe zurück, und die Kennzeichnung
|
||||
// liesse sich nicht dauerhaft abschalten.
|
||||
$("tagName").value = einstellungen.shareTag ?? "Sharelink";
|
||||
|
||||
$("suchen").addEventListener("click", suchen);
|
||||
$("suche").addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") suchen();
|
||||
});
|
||||
$("abbrechen").addEventListener("click", () => window.close());
|
||||
$("einfuegen").addEventListener("click", einfuegen);
|
||||
for (const r of document.querySelectorAll('input[name=art]')) {
|
||||
r.addEventListener("change", artGewechselt);
|
||||
}
|
||||
artGewechselt();
|
||||
suchen();
|
||||
}
|
||||
|
||||
function artGewechselt() {
|
||||
const link = $("artLink").checked;
|
||||
for (const id of ["tage", "tagName"]) $(id).disabled = !link;
|
||||
// Bei einem Freigabelink zählt das Dokument, nicht die einzelne Version -
|
||||
// der Link zeigt immer auf die Kopfversion.
|
||||
$("versionen").closest(".wachsen").style.opacity = link ? ".5" : "1";
|
||||
knopfPruefen();
|
||||
}
|
||||
|
||||
async function suchen() {
|
||||
$("treffer").replaceChildren();
|
||||
$("versionen").replaceChildren();
|
||||
gewaehltesDokument = null;
|
||||
gewaehlteVersion = 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", () => waehleDokument(i));
|
||||
$("treffer").append(tr);
|
||||
}
|
||||
if (treffer.length) waehleDokument(0);
|
||||
else meldungAnzeigen(browser.i18n.getMessage("msgNoHits") || "Keine Treffer.");
|
||||
}
|
||||
|
||||
async function waehleDokument(i) {
|
||||
gewaehltesDokument = treffer[i] || null;
|
||||
for (const [j, tr] of [...$("treffer").children].entries()) {
|
||||
tr.classList.toggle("gewaehlt", j === i);
|
||||
}
|
||||
$("versionen").replaceChildren();
|
||||
gewaehlteVersion = null;
|
||||
knopfPruefen();
|
||||
if (!gewaehltesDokument) return;
|
||||
|
||||
meldungAnzeigen(browser.i18n.getMessage("msgLoadingVersions")
|
||||
|| "Versionen werden geladen …");
|
||||
try {
|
||||
versionen = await ruf("versionen", { id: gewaehltesDokument.id });
|
||||
} catch (err) {
|
||||
fehlerAnzeigen(err.message);
|
||||
return;
|
||||
}
|
||||
meldungAnzeigen("");
|
||||
|
||||
for (const [k, v] of versionen.entries()) {
|
||||
const tr = document.createElement("tr");
|
||||
const a = document.createElement("td");
|
||||
a.className = "zahl";
|
||||
a.textContent = v.no;
|
||||
const b = document.createElement("td");
|
||||
b.textContent = (v.added || "").slice(0, 10);
|
||||
const c = document.createElement("td");
|
||||
c.textContent = FORMATE[v.mime_type] || (v.mime_type || "?");
|
||||
const d = document.createElement("td");
|
||||
d.textContent = v.version_label || "";
|
||||
tr.append(a, b, c, d);
|
||||
tr.addEventListener("click", () => waehleVersion(k));
|
||||
$("versionen").append(tr);
|
||||
}
|
||||
if (versionen.length) waehleVersion(0);
|
||||
}
|
||||
|
||||
function waehleVersion(k) {
|
||||
gewaehlteVersion = versionen[k] || null;
|
||||
for (const [j, tr] of [...$("versionen").children].entries()) {
|
||||
tr.classList.toggle("gewaehlt", j === k);
|
||||
}
|
||||
knopfPruefen();
|
||||
}
|
||||
|
||||
function knopfPruefen() {
|
||||
const ok = $("artLink").checked
|
||||
? Boolean(gewaehltesDokument)
|
||||
: Boolean(gewaehlteVersion);
|
||||
$("einfuegen").disabled = !ok;
|
||||
}
|
||||
|
||||
async function einfuegen() {
|
||||
$("einfuegen").disabled = true;
|
||||
try {
|
||||
if ($("artLink").checked) {
|
||||
meldungAnzeigen(browser.i18n.getMessage("msgCreatingLink")
|
||||
|| "Link wird erzeugt …");
|
||||
// ACHTUNG: linktext darf NICHT auf url zurückgreifen - die Variable
|
||||
// entsteht erst durch dieselbe Zuweisung. Bei einem Dokument ohne
|
||||
// Titel gäbe das eine Ausnahme.
|
||||
const { url, tagStatus, gueltigBis } = await ruf("freigabelink", {
|
||||
id: gewaehltesDokument.id,
|
||||
tage: Number($("tage").value || 0),
|
||||
variante: "original",
|
||||
tagName: $("tagName").value.trim() || null,
|
||||
tabId: auftrag.tabId,
|
||||
einfuegen: true,
|
||||
linktext: gewaehltesDokument.title
|
||||
|| `Dokument ${gewaehltesDokument.id}`
|
||||
});
|
||||
// Ein fehlgeschlagener Tag darf nicht untergehen. Er ist der Grund,
|
||||
// warum sich geteilte Dokumente später wiederfinden lassen.
|
||||
if (tagStatus && !tagStatus.ok) {
|
||||
fehlerAnzeigen(`Link erzeugt, aber der Tag konnte nicht gesetzt `
|
||||
+ `werden:\n${tagStatus.fehler}`);
|
||||
return;
|
||||
}
|
||||
const teile = [url];
|
||||
if (gueltigBis) {
|
||||
const d = new Date(gueltigBis);
|
||||
const z = (n) => String(n).padStart(2, "0");
|
||||
teile.push(`gültig bis ${z(d.getDate())}.${z(d.getMonth() + 1)}.`
|
||||
+ `${d.getFullYear()}`);
|
||||
} else {
|
||||
teile.push("ohne zeitliche Begrenzung");
|
||||
}
|
||||
if (tagStatus?.ok) teile.push(`Tag „${tagStatus.name}" gesetzt`);
|
||||
meldungAnzeigen(teile.join(" · "));
|
||||
} else {
|
||||
meldungAnzeigen(browser.i18n.getMessage("msgAttaching")
|
||||
|| "Wird angehängt …");
|
||||
await ruf("anhaengen", {
|
||||
versionId: gewaehlteVersion.id,
|
||||
tabId: auftrag.tabId
|
||||
});
|
||||
}
|
||||
setTimeout(() => window.close(), 1000);
|
||||
} catch (err) {
|
||||
fehlerAnzeigen(err.message);
|
||||
$("einfuegen").disabled = false;
|
||||
}
|
||||
}
|
||||
76
ui/options.html
Normal file
76
ui/options.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title data-i18n="optionsTitle">Paperless — Verbindung</title>
|
||||
<link rel="stylesheet" href="gemeinsam.css">
|
||||
<style>body { max-width: 620px; height: auto; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 data-i18n="optionsTitle">Paperless — Verbindung</h1>
|
||||
|
||||
<fieldset>
|
||||
<legend data-i18n="optionsConnection">Zugang</legend>
|
||||
|
||||
<label for="baseUrl" data-i18n="fieldBaseUrl">Basisadresse</label>
|
||||
<input type="text" id="baseUrl" placeholder="https://dms.example.org">
|
||||
<div class="hinweis" data-i18n="optionsHintUrl">
|
||||
Ohne abschließenden Schrägstrich. Nur http und https.
|
||||
</div>
|
||||
|
||||
<label for="token" data-i18n="fieldToken">API-Token</label>
|
||||
<input type="password" id="token">
|
||||
<div class="hinweis" data-i18n="optionsHintToken">
|
||||
Im Benutzerprofil von Paperless erzeugen. Ein eigener Token für diese
|
||||
Erweiterung lässt sich einzeln widerrufen.
|
||||
</div>
|
||||
|
||||
<div style="margin-top:10px" class="zeile">
|
||||
<button id="pruefen" data-i18n="btnTest">Verbindung prüfen</button>
|
||||
<span id="status" class="hinweis"></span>
|
||||
</div>
|
||||
<div id="recht" class="hinweis" style="margin-top:6px"></div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend data-i18n="optionsBehaviour">Verhalten</legend>
|
||||
|
||||
<label for="versionLabel" data-i18n="fieldVersionLabel">
|
||||
Bezeichnung neuer Versionen
|
||||
</label>
|
||||
<input type="text" id="versionLabel">
|
||||
<div class="hinweis" data-i18n="optionsHintLabel">
|
||||
{datum} wird durch das heutige Datum ersetzt.
|
||||
</div>
|
||||
|
||||
<div class="zeile" style="margin-top:8px">
|
||||
<label for="shareDays" style="margin:0" data-i18n="fieldShareDays">
|
||||
Gültigkeit in Tagen
|
||||
</label>
|
||||
<input type="number" id="shareDays" min="0" max="3650" style="width:90px">
|
||||
<label for="shareTag" style="margin:0 0 0 12px" data-i18n="fieldShareTag">
|
||||
Tag beim Teilen
|
||||
</label>
|
||||
<input type="text" id="shareTag" style="width:160px">
|
||||
</div>
|
||||
|
||||
<label for="ignorierteKleiner" style="margin-top:8px"
|
||||
data-i18n="fieldIgnoreSmaller">
|
||||
Anhänge vorab abwählen unter … Bytes
|
||||
</label>
|
||||
<input type="number" id="ignorierteKleiner" min="0" style="width:120px">
|
||||
<div class="hinweis" data-i18n="optionsHintIgnore">
|
||||
Signaturbilder und Logos sind meist kleiner als 8 KB. Sie lassen sich
|
||||
im Dialog jederzeit anhaken.
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div id="meldung" class="hinweis"></div>
|
||||
|
||||
<div class="fuss">
|
||||
<button id="speichern" class="haupt" data-i18n="btnSave">Speichern</button>
|
||||
</div>
|
||||
|
||||
<script type="module" src="options.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
125
ui/options.js
Normal file
125
ui/options.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* options.js - Einstellungsseite.
|
||||
*
|
||||
* Beim Speichern wird zusätzlich das Zugriffsrecht für genau die
|
||||
* eingetragene Adresse angefordert. Es steht bewusst nicht pauschal im
|
||||
* Manifest: Eine Erweiterung, die von Haus aus jede Adresse erreichen
|
||||
* darf, ist eine unnötig grosse Angriffsfläche.
|
||||
*/
|
||||
|
||||
import { ladeEinstellungen, speichereEinstellungen } from "../lib/settings.js";
|
||||
import { uebersetzeSeite, ruf, fehlerAnzeigen, meldungAnzeigen }
|
||||
from "./gemeinsam.js";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const FELDER = ["baseUrl", "token", "versionLabel", "shareDays", "shareTag",
|
||||
"ignorierteKleiner"];
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
uebersetzeSeite();
|
||||
const s = await ladeEinstellungen();
|
||||
for (const f of FELDER) if ($(f)) $(f).value = s[f] ?? "";
|
||||
$("speichern").addEventListener("click", speichern);
|
||||
$("pruefen").addEventListener("click", pruefen);
|
||||
await rechtAnzeigen();
|
||||
});
|
||||
|
||||
/**
|
||||
* Zustand des Zugriffsrechts anzeigen.
|
||||
*
|
||||
* Es ist die häufigste Ursache für ein scheiterndes fetch, und ohne
|
||||
* Anzeige merkt man erst beim ersten Ablegen, dass es fehlt.
|
||||
*/
|
||||
async function rechtAnzeigen() {
|
||||
// Diese Anzeige ist Beiwerk. Fehlt das Element - etwa weil eine ältere
|
||||
// options.html geladen ist -, darf das nicht das Speichern verhindern.
|
||||
const ziel = $("recht");
|
||||
if (!ziel) return;
|
||||
const url = $("baseUrl").value.trim();
|
||||
if (!url) {
|
||||
ziel.textContent = "";
|
||||
return;
|
||||
}
|
||||
let origin;
|
||||
try {
|
||||
origin = new URL(url).origin + "/*";
|
||||
} catch {
|
||||
ziel.className = "fehler";
|
||||
ziel.textContent = browser.i18n.getMessage("errBadUrl") || "Ungültige Adresse.";
|
||||
return;
|
||||
}
|
||||
const erteilt = await browser.permissions.contains({ origins: [origin] });
|
||||
ziel.className = erteilt ? "hinweis" : "fehler";
|
||||
ziel.textContent = erteilt
|
||||
? `Zugriffsrecht erteilt für ${origin}`
|
||||
: `Zugriffsrecht fehlt für ${origin} — auf Speichern klicken.`;
|
||||
}
|
||||
|
||||
function gelesen() {
|
||||
return {
|
||||
baseUrl: $("baseUrl").value.trim().replace(/\/+$/, ""),
|
||||
token: $("token").value.trim(),
|
||||
versionLabel: $("versionLabel").value.trim() || "Thunderbird {datum}",
|
||||
shareDays: Number($("shareDays").value || 0),
|
||||
shareTag: $("shareTag").value.trim(),
|
||||
ignorierteKleiner: Number($("ignorierteKleiner").value || 0)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Einstellungen speichern und das Zugriffsrecht anfordern.
|
||||
*
|
||||
* WICHTIG: permissions.request MUSS als erstes stehen, noch vor jedem
|
||||
* await. Der Aufruf ist nur aus einer Benutzeraktion heraus zulässig, und
|
||||
* diese Eigenschaft geht beim ersten await verloren - die Folge wäre
|
||||
* "permissions.request may only be called from a user input handler".
|
||||
*
|
||||
* Ein vorgeschaltetes permissions.contains() ist deshalb ausgeschlossen.
|
||||
* Das ist auch nicht nötig: request() löst keine Abfrage aus, wenn das
|
||||
* Recht bereits erteilt ist, sondern liefert sofort true.
|
||||
*/
|
||||
function speichern() {
|
||||
const werte = gelesen();
|
||||
|
||||
let origin;
|
||||
try {
|
||||
origin = new URL(werte.baseUrl).origin + "/*";
|
||||
} catch {
|
||||
fehlerAnzeigen(browser.i18n.getMessage("errBadUrl")
|
||||
|| "Ungültige Adresse. Erwartet wird https://rechner oder http://rechner.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Kein await davor. Die Auswertung läuft in der Zusage.
|
||||
browser.permissions.request({ origins: [origin] })
|
||||
.then(async (erteilt) => {
|
||||
if (!erteilt) {
|
||||
fehlerAnzeigen(browser.i18n.getMessage("errNoPermission")
|
||||
|| "Ohne Zugriffsrecht auf die Adresse kann die Erweiterung nicht arbeiten.");
|
||||
await rechtAnzeigen();
|
||||
return;
|
||||
}
|
||||
await speichereEinstellungen(werte);
|
||||
meldungAnzeigen(browser.i18n.getMessage("msgSaved") || "Gespeichert.");
|
||||
// Anzeige und Prüfung sind Beiwerk; ein Fehler darin soll nicht
|
||||
// aussehen, als wäre das Speichern gescheitert.
|
||||
try { await rechtAnzeigen(); } catch { /* egal */ }
|
||||
try { await pruefen(); } catch { /* egal */ }
|
||||
})
|
||||
.catch((err) => fehlerAnzeigen(err.message));
|
||||
}
|
||||
|
||||
async function pruefen() {
|
||||
$("status").className = "hinweis";
|
||||
$("status").textContent = "…";
|
||||
try {
|
||||
await speichereEinstellungen(gelesen());
|
||||
const treffer = await ruf("suchen", { query: "" });
|
||||
$("status").textContent =
|
||||
browser.i18n.getMessage("msgConnectionOk", [String(treffer.length)])
|
||||
|| `Verbindung steht (${treffer.length} Dokumente sichtbar).`;
|
||||
} catch (err) {
|
||||
$("status").className = "fehler";
|
||||
$("status").textContent = String(err.message).slice(0, 200);
|
||||
}
|
||||
}
|
||||
85
ui/store.html
Normal file
85
ui/store.html
Normal file
@@ -0,0 +1,85 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title data-i18n="storeTitle">In Paperless ablegen</title>
|
||||
<link rel="stylesheet" href="gemeinsam.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1 data-i18n="storeTitle">In Paperless ablegen</h1>
|
||||
<div id="quelle" class="hinweis"></div>
|
||||
|
||||
<fieldset>
|
||||
<legend data-i18n="storeWhat">Was soll abgelegt werden?</legend>
|
||||
<div id="dateien"></div>
|
||||
<div class="hinweis" data-i18n="storeHintPreselect">
|
||||
Kleine Bilder und Signaturen sind vorab abgewählt — sie sind meist
|
||||
Teil der Nachricht, keine eigenen Dokumente.
|
||||
</div>
|
||||
</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 class="fuss">
|
||||
<button id="abbrechen" data-i18n="btnCancel">Abbrechen</button>
|
||||
<button id="ablegen" class="haupt" data-i18n="btnStore">Ablegen</button>
|
||||
</div>
|
||||
|
||||
<script type="module" src="store.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
212
ui/store.js
Normal file
212
ui/store.js
Normal file
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* store.js - Dialog zum Ablegen von Anhängen und ganzen Nachrichten.
|
||||
*
|
||||
* Der Dialog spricht nicht selbst mit Paperless, sondern lässt die
|
||||
* Ereignisseite arbeiten. Dort hängt die zur Laufzeit erteilte
|
||||
* Berechtigung für die Zieladresse.
|
||||
*/
|
||||
|
||||
import { groesse, kurz, titelAusBetreff } from "../lib/settings.js";
|
||||
import { uebersetzeSeite, ruf, fehlerAnzeigen } from "./gemeinsam.js";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
let auftrag = null;
|
||||
let einstellungen = null;
|
||||
let treffer = [];
|
||||
let gewaehlterTreffer = null;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", start);
|
||||
|
||||
async function start() {
|
||||
uebersetzeSeite();
|
||||
const nummer = Number(new URLSearchParams(location.search).get("auftrag"));
|
||||
|
||||
einstellungen = await ruf("einstellungen");
|
||||
auftrag = await ruf("auftrag", { nummer });
|
||||
if (!auftrag) {
|
||||
fehlerAnzeigen("Der Auftrag ist nicht mehr vorhanden.");
|
||||
return;
|
||||
}
|
||||
|
||||
quelleAnzeigen();
|
||||
dateienAnzeigen();
|
||||
vorbelegen();
|
||||
verdrahten();
|
||||
}
|
||||
|
||||
function quelleAnzeigen() {
|
||||
const teile = [];
|
||||
if (auftrag.betreff) teile.push(kurz(auftrag.betreff, 80));
|
||||
if (auftrag.absender) teile.push(kurz(auftrag.absender, 40));
|
||||
if (auftrag.datum) teile.push(auftrag.datum);
|
||||
$("quelle").textContent = teile.join(" · ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Anhänge zur Auswahl anbieten.
|
||||
*
|
||||
* Vorab abgewählt werden kleine Bilder und Signaturen. Sie gehören zur
|
||||
* Darstellung der Nachricht, nicht zum Inhalt - wer sie alle mit
|
||||
* archiviert, hat nach einem Jahr hunderte Logos im Bestand.
|
||||
*/
|
||||
function dateienAnzeigen() {
|
||||
const ziel = $("dateien");
|
||||
ziel.replaceChildren();
|
||||
|
||||
if (auftrag.quelle === "nachricht") {
|
||||
// Aufbau über createElement statt innerHTML. Hier stünde zwar nur
|
||||
// festes Markup, aber die Zusicherung "kein innerHTML" soll ohne
|
||||
// Ausnahme gelten - sonst muss man bei jeder Änderung neu prüfen, ob
|
||||
// sich inzwischen ein Fremdwert eingeschlichen hat.
|
||||
const zeile = document.createElement("div");
|
||||
zeile.className = "wahl";
|
||||
const box = document.createElement("input");
|
||||
box.type = "checkbox";
|
||||
box.id = "ganz";
|
||||
box.checked = true;
|
||||
box.disabled = true;
|
||||
const bez = document.createElement("label");
|
||||
bez.htmlFor = "ganz";
|
||||
bez.textContent = browser.i18n.getMessage("storeWholeMessage")
|
||||
|| "Die vollständige Nachricht als .eml";
|
||||
zeile.append(box, bez);
|
||||
ziel.append(zeile);
|
||||
|
||||
if (auftrag.dateien?.length) {
|
||||
const h = document.createElement("div");
|
||||
h.className = "hinweis";
|
||||
h.textContent = browser.i18n.getMessage("storeAttachmentsInside",
|
||||
[String(auftrag.dateien.length)])
|
||||
|| `Die ${auftrag.dateien.length} Anhänge bleiben darin enthalten.`;
|
||||
ziel.append(h);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [i, d] of (auftrag.dateien || []).entries()) {
|
||||
const uninteressant =
|
||||
einstellungen.ignorierteTypen.includes(d.contentType)
|
||||
|| (d.size || 0) < einstellungen.ignorierteKleiner;
|
||||
d.ausgewaehlt = !uninteressant;
|
||||
|
||||
const zeile = document.createElement("div");
|
||||
zeile.className = "wahl";
|
||||
const box = document.createElement("input");
|
||||
box.type = "checkbox";
|
||||
box.id = `d${i}`;
|
||||
box.checked = d.ausgewaehlt;
|
||||
box.addEventListener("change", () => {
|
||||
d.ausgewaehlt = box.checked;
|
||||
knopfPruefen();
|
||||
});
|
||||
const bez = document.createElement("label");
|
||||
bez.htmlFor = box.id;
|
||||
bez.textContent = `${d.name} (${groesse(d.size)}, ${d.contentType})`;
|
||||
zeile.append(box, bez);
|
||||
ziel.append(zeile);
|
||||
}
|
||||
}
|
||||
|
||||
function vorbelegen() {
|
||||
$("titel").value = titelAusBetreff(auftrag.betreff);
|
||||
if (auftrag.datum) $("erstellt").value = auftrag.datum;
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
$("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;
|
||||
$("meldung").className = "hinweis";
|
||||
$("meldung").textContent =
|
||||
browser.i18n.getMessage("msgUploading") || "Wird abgelegt …";
|
||||
|
||||
try {
|
||||
const ergebnisse = await ruf("ablegen", {
|
||||
auftrag,
|
||||
ziel: $("zielNeu").checked ? "neu" : "vorhanden",
|
||||
zielId: gewaehlterTreffer?.id,
|
||||
titel: $("titel").value.trim(),
|
||||
erstellt: $("erstellt").value.trim()
|
||||
});
|
||||
|
||||
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) {
|
||||
fehlerAnzeigen(err.message);
|
||||
$("ablegen").disabled = false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user