Fix clipboard paste on PC, round price per litre to 2 decimals

The "paste from clipboard" button could never work on a desktop:
clipboard.read() only ever exposes text, HTML and images by spec, so a
file copied in Explorer is not offered through it - and the dialog then
wrongly blamed the file for not being a PDF. It is now hidden on
pointer-coarse devices only, where it does work; on a PC the drop zone
names Strg+V, which is the path that carries the actual file.

Detection also relied solely on the MIME type, but a file copied or
dragged from Explorer can arrive with an empty type and was rejected -
istPdf() now falls back to the extension.

Price per litre now shows 2 decimals everywhere (list, receipt detail,
both entry forms, CSV) instead of 3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 00:38:55 +02:00
parent 5aaf1cd958
commit e5e82b3634
5 changed files with 124 additions and 34 deletions
+34
View File
@@ -1169,6 +1169,40 @@ wraps the web app for iPhone; a PWA home-screen install is the accepted intermed
drop → zone highlights (`darueber`), dialog closes, service called with the right filename and
base64 payload; paste → same; a dropped PNG → "Das war kein PDF." and no service call; Escape →
closes. Synced to `installationspaket/`.
**Nachbesserung am selben Tag, nach Fehlermeldung des Nutzers** ("copy paste from pc brings
error no pdf but it is the latest bill") — zwei echte Fehler, beide bestätigt:
(1) `navigator.clipboard.read()` (der "Aus Zwischenablage einfügen"-Knopf) gibt laut
Spezifikation **nur Text, HTML und Bilder** heraus. Eine im Explorer/Finder kopierte PDF-Datei
taucht dort grundsätzlich nicht auf - der Knopf konnte am Rechner also gar nicht funktionieren
und behauptete dann fälschlich, es liege kein PDF vor. Der Weg, der dort funktioniert, ist
Strg+V: das `paste`-Ereignis trägt die Datei in `clipboardData.files`. Die Meldung nennt jetzt
genau das, statt die Datei zu beschuldigen; die Ablagefläche sagt es von vornherein
("oder mit Strg+V einfügen", per `tastenkuerzelEinfuegen()` auf Mac Cmd+V).
(2) Die Erkennung hing allein am MIME-Typ (`type === "application/pdf"`) - eine aus dem
Explorer kopierte oder gezogene Datei kommt je nach Browser und Quelle aber mit **leerem**
`type` an und wurde dadurch abgelehnt. Neue Hilfsfunktion `istPdf()` prüft zusätzlich die
Dateiendung; live nachgestellt mit genau diesem Fall (`new File([...], "Tankbeleg_August.pdf",
{})`, `type === ""`) - vorher abgelehnt, jetzt korrekt hochgeladen.
(3) Auf Rückfrage des Nutzers ("why is it that way? computer has copy paste function") noch
einen Schritt weiter: Der Einfügen-Knopf wird am Rechner jetzt **gar nicht mehr angeboten**
(`zwischenablageKnopfSinnvoll()`, Prüfung auf `(pointer: coarse)`), weil er dort prinzipiell
nicht funktionieren kann - ein Knopf, der zwangsläufig in eine Fehlermeldung läuft, ist
schlechter als keiner. "Datei auswählen" wird dort zur primären Aktion; auf dem Telefon bleibt
alles wie zuvor. Der Unterschied dahinter, kurz: Strg+V ist eine bewusste Nutzergeste an die
Seite, deshalb bekommt die Seite den vollen Inhalt inklusive Dateien - `clipboard.read()` ist
die Seite, die von sich aus die Zwischenablage ausliest, und das wird aus Datenschutzgründen
stark eingeschränkt (sonst könnte jede Seite still mitlesen, was zuletzt kopiert wurde).
Live geprüft: Desktop zeigt nur noch "Datei auswählen [primaer]"/"Abbrechen", Mobil (375px,
`pointer: coarse`) weiterhin alle drei Knöpfe.
- [x] Preis je Liter überall auf 2 Nachkommastellen (2026-08-17, Nutzerwunsch: "€/l for gas is
common to be displayed as x,xx €/l") — alle sieben Stellen von `de(..., 3)` auf `de(..., 2)`
umgestellt: Jahres- und Monatsschnitt in der Tankliste, Zeilenwert je Tankvorgang, "Preis je
Liter" im Einzelbeleg, die Live-Vorschau in beiden Tankformularen (statisch und im
`data-tankpreisfeld`-Handler) sowie die CSV-Spalte "€/l". Im CSV kein Informationsverlust: der
Wert ist aus den ebenfalls exportierten Spalten Liter und Kosten jederzeit neu berechenbar.
`companion-app` (Nachfolger-App) brauchte keine Änderung - deren `eur()` rundet ohnehin schon
auf 2 Stellen. Live geprüft: keine `de(..., 3)`-Aufrufe mehr in der Datei, Liste und
Einzelbeleg zeigen "2,07 €/l", "1,99 €/l" usw.
- [ ] Fix remaining documentation drift (statistics claim, README gaps, obsolete TODO comment) —
text-only changes; INSTALL.md's WLAN/TommiG1 drift and stale variable names were fixed
2026-08-12 (see section B); `DESIGN_REVIEW_2026-08-13.md` and `REVIEW_main_2026-08-13.md`
@@ -2805,7 +2805,7 @@ function tankFelder(praefix, f) {
const kraftstoff = q && q.fuel_type ? q.fuel_type : KRAFTSTOFFSORTEN[0];
const station = q && q.station_name ? q.station_name : "";
const preisText = q && q.liters && q.fuel_total_eur != null && q.liters > 0
? `Preis je Liter: ${de(q.fuel_total_eur / q.liters, 3)}` : "Preis je Liter: wird berechnet";
? `Preis je Liter: ${de(q.fuel_total_eur / q.liters, 2)}` : "Preis je Liter: wird berechnet";
return `
<div class="feld"><label>Datum</label><input type="date" id="${praefix}Datum" value="${datum}"></div>
<div class="feld"><label>Uhrzeit</label><input type="time" id="${praefix}Zeit" value="${zeit}"></div>
@@ -2871,7 +2871,7 @@ function vFuel() {
<span class="k">${j}</span>
<span class="v" style="display:flex;flex-direction:column;align-items:flex-end;gap:5px">
<span style="font-size:17px;color:var(--fg)">${eur(sum(fj, (f) => f.fuel_total_eur || 0))} </span>
<span>${de(sum(fj, (f) => f.liters || 0), 1)} l · Ø ${de(schnittpreis(fj), 3)} /l</span>
<span>${de(sum(fj, (f) => f.liters || 0), 1)} l · Ø ${de(schnittpreis(fj), 2)} /l</span>
${CAR.smartdeal.aktiv ? `<span class="marke"><span class="pill" style="font-size:11.5px;padding:4px 10px">${markenlogo()}<span style="margin-left:6px">${eur(ersparnisJahr)} €</span></span></span>` : ""}
</span></button>
<div class="acc-b ${ji === 0 ? "on" : ""}" id="fy${j}">`;
@@ -2882,11 +2882,11 @@ function vFuel() {
<span class="mark"></span><span class="k">${m}</span>
<span class="v" style="display:flex;flex-direction:column;align-items:flex-end;gap:3px">
<span style="color:var(--fg)">${eur(sum(fm, (f) => f.fuel_total_eur || 0))} </span>
<span>Ø ${de(schnittpreis(fm), 3)} /l</span></span></button>
<span>Ø ${de(schnittpreis(fm), 2)} /l</span></span></button>
<div class="acc-b ${mi === 0 ? "on" : ""}" id="fm${j}${m}">`;
fm.forEach((f) => { const preis = f.liters ? f.fuel_total_eur / f.liters : null; h += swipeZeile(`fill:${f.tank_id}`, `<button class="leaf" data-go="fill:${f.tank_id}">
<span class="k">${dedat(new Date(f.ts))}<small>${esc(f.station_name || "")}</small></span>
<span class="v">${f.liters != null ? de(f.liters, 2) + " l" : ""}<small>${preis != null ? de(preis, 3) + " €/l" : ""}</small></span>
<span class="v">${f.liters != null ? de(f.liters, 2) + " l" : ""}<small>${preis != null ? de(preis, 2) + " €/l" : ""}</small></span>
<svg class="chev" viewBox="0 0 6 10"><polyline points="1,1 5,5 1,9"/></svg></button>`); });
h += `</div></div>`;
});
@@ -2925,7 +2925,7 @@ function vFill(id) {
<div class="row"><dt>Kraftstoff</dt><dd>${esc(f.fuel_type || KRAFTSTOFFSORTEN[0])}</dd></div>
<div class="row"><dt>Getankte Liter</dt><dd>${f.liters != null ? de(f.liters, 2) + " l" : ""}</dd></div>
<div class="row"><dt>Kosten</dt><dd>${f.fuel_total_eur != null ? eur(f.fuel_total_eur) + " " : ""}<small>real, nach Rabatt</small></dd></div>
<div class="row"><dt>Preis je Liter</dt><dd>${berechneterPreis != null ? de(berechneterPreis, 3) + " " : ""}<small>Kosten ÷ Liter</small></dd></div>
<div class="row"><dt>Preis je Liter</dt><dd>${berechneterPreis != null ? de(berechneterPreis, 2) + " " : ""}<small>Kosten ÷ Liter</small></dd></div>
${CAR.smartdeal.aktiv && f.discount != null ? `<div class="row"><dt><span class="marke">${markenlogo()} SmartDeal-Ersparnis</span></dt><dd>${eur(f.discount)} €</dd></div>` : ""}
<div class="row" style="border-bottom:0"><dt>Tankstelle</dt><dd>${esc(f.station_name || "unbekannt")}<small>${esc(f.station_address || "")}</small></dd></div>
</dl>
@@ -3006,7 +3006,7 @@ function hinweis(titel, text) {
unverändert. */
function belegDateiVerarbeiten(datei, tankId, kontext) {
if (!datei) return;
if (datei.type && datei.type !== "application/pdf") {
if (!istPdf(datei)) {
belegFehler = { context: kontext, meldung: "Nur PDF-Belege werden unterstützt" };
render(); return;
}
@@ -3025,9 +3025,16 @@ function belegDateiVerarbeiten(datei, tankId, kontext) {
leser.readAsDataURL(datei);
}
/* Nicht nur auf den MIME-Typ verlassen: eine aus dem Explorer/Finder kopierte
oder gezogene Datei kommt je nach Browser und Quelle mit leerem type an. */
function istPdf(datei) {
if (!datei) return false;
return datei.type === "application/pdf" || /\.pdf$/i.test(datei.name || "");
}
/* Nimmt das erste PDF aus einer DataTransfer-/Clipboard-Dateiliste. */
function belegAusDateiliste(dateien) {
return [...(dateien || [])].find((d) => d.type === "application/pdf") || null;
return [...(dateien || [])].find(istPdf) || null;
}
function belegPopupSchliessenUndLaden(datei) {
@@ -3040,12 +3047,21 @@ function belegPopupSchliessenUndLaden(datei) {
/* Telefon-Weg: das aus der Mail kopierte PDF liegt in der Zwischenablage.
navigator.clipboard.read() ist der einzige Weg, ohne dass der Nutzer in ein
Eingabefeld tippen muss - iOS zeigt dafür seine eigene Einfügen-Abfrage. */
Eingabefeld tippen muss - iOS zeigt dafür seine eigene Einfügen-Abfrage.
WICHTIG (Ursache eines gemeldeten Fehlers): Am Rechner hilft dieser Knopf
bei einer im Explorer/Finder kopierten Datei NICHT. clipboard.read() gibt
laut Spezifikation nur Text, HTML und Bilder heraus - eine kopierte
PDF-Datei taucht dort gar nicht auf, egal wie frisch sie ist. Der Weg, der
dort funktioniert, ist Strg+V: das paste-Ereignis trägt die echte Datei in
clipboardData.files. Deshalb sagt die Meldung unten genau das, statt
pauschal "kein PDF" zu behaupten. */
async function belegAusZwischenablage() {
const ctx = belegPopup;
if (!ctx) return;
const strgVHinweis = `Am Rechner kann der Browser eine kopierte Datei nur über ${tastenkuerzelEinfuegen()} entgegennehmen - bitte so einfügen oder das PDF hineinziehen.`;
if (!navigator.clipboard || !navigator.clipboard.read) {
belegPopup.fehler = "Dieser Browser kann die Zwischenablage nicht lesen - bitte ziehen oder Datei auswählen.";
belegPopup.fehler = strgVHinweis;
render(); return;
}
try {
@@ -3053,13 +3069,25 @@ async function belegAusZwischenablage() {
const typ = (eintrag.types || []).find((t) => t === "application/pdf");
if (typ) { belegPopupSchliessenUndLaden(new File([await eintrag.getType(typ)], "beleg.pdf", { type: "application/pdf" })); return; }
}
belegPopup.fehler = "In der Zwischenablage liegt kein PDF.";
belegPopup.fehler = strgVHinweis;
} catch (err) {
belegPopup.fehler = "Kein Zugriff auf die Zwischenablage.";
belegPopup.fehler = "Kein Zugriff auf die Zwischenablage. " + strgVHinweis;
}
render();
}
function tastenkuerzelEinfuegen() {
return /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent) ? "Cmd+V" : "Strg+V";
}
/* Der Einfügen-Knopf wird nur auf Tippgeräten angeboten. Am Rechner kann er
aus Datenschutzgründen prinzipiell nicht funktionieren (clipboard.read()
gibt Dateien nie heraus, siehe oben) - ein Knopf, der dort zwangsläufig
scheitert, führt nur in eine Sackgasse. Dort bleiben Ziehen und Strg+V. */
function zwischenablageKnopfSinnvoll() {
return window.matchMedia && window.matchMedia("(pointer: coarse)").matches;
}
function vBelegPopup() {
if (!belegPopup) return "";
return `<div class="beleg-catcher" data-belegzu></div>
@@ -3067,11 +3095,11 @@ function vBelegPopup() {
<span class="label">Beleg hochladen</span>
<div class="beleg-drop" id="belegDrop">
<strong>PDF hierher ziehen</strong>
<span>oder aus der Zwischenablage einfügen</span>
<span>oder mit ${tastenkuerzelEinfuegen()} einfügen</span>
</div>
${belegPopup.fehler ? `<span class="label" style="margin-top:10px;color:var(--red)">${esc(belegPopup.fehler)}</span>` : ""}
<button class="aktion primaer" data-belegeinfuegen>Aus Zwischenablage einfügen</button>
<button class="aktion" data-belegdatei>Datei auswählen</button>
${zwischenablageKnopfSinnvoll() ? `<button class="aktion primaer" data-belegeinfuegen>Aus Zwischenablage einfügen</button>` : ""}
<button class="aktion ${zwischenablageKnopfSinnvoll() ? "" : "primaer"}" data-belegdatei>Datei auswählen</button>
<button class="aktion" data-belegzu>Abbrechen</button>
</div>`;
}
@@ -3928,7 +3956,7 @@ function ereignisseVerdrahten() {
if (w === "fahrten") csvDatei("fahrten.csv", ["Start", "Ende", "km", "Art", "Status"],
TRIPS.map((t) => [t.ts_start, t.ts_end, t.distance_km != null ? de(t.distance_km) : "", t.art, t.status]));
else if (w === "tanken") csvDatei("tankvorgaenge.csv", ["Zeitpunkt", "Station", "Liter", "€/l", "Kosten €"],
FILLS.map((f) => [f.ts, f.station_name || "", f.liters != null ? de(f.liters, 2) : "", f.liters ? de(f.fuel_total_eur / f.liters, 3) : "", f.fuel_total_eur != null ? eur(f.fuel_total_eur) : ""]));
FILLS.map((f) => [f.ts, f.station_name || "", f.liters != null ? de(f.liters, 2) : "", f.liters ? de(f.fuel_total_eur / f.liters, 2) : "", f.fuel_total_eur != null ? eur(f.fuel_total_eur) : ""]));
else csvDatei("servicebuch.csv", ["Datum", "km", "Art", "Werkstatt", "Kosten €"],
CAR.service.buch.map((e2) => [e2.datum, de(e2.km), e2.art, e2.werkstatt, e2.kosten != null ? eur(e2.kosten) : ""]));
return;
@@ -3995,7 +4023,7 @@ function ereignisseVerdrahten() {
const liter = parseFloat((ROOT.getElementById(`${praefix}Liter`).value || "0").replace(",", "."));
const kosten = parseFloat((ROOT.getElementById(`${praefix}Kosten`).value || "0").replace(",", "."));
const anzeige = ROOT.getElementById(`${praefix}PreisAnzeige`);
if (anzeige) anzeige.textContent = liter > 0 ? `Preis je Liter: ${de(kosten / liter, 3)}` : "Preis je Liter: wird berechnet";
if (anzeige) anzeige.textContent = liter > 0 ? `Preis je Liter: ${de(kosten / liter, 2)}` : "Preis je Liter: wird berechnet";
});
ROOT.addEventListener("change", (e) => {
@@ -1 +1 @@
{"version": 1786919196}
{"version": 1786919856}
+44 -16
View File
@@ -2805,7 +2805,7 @@ function tankFelder(praefix, f) {
const kraftstoff = q && q.fuel_type ? q.fuel_type : KRAFTSTOFFSORTEN[0];
const station = q && q.station_name ? q.station_name : "";
const preisText = q && q.liters && q.fuel_total_eur != null && q.liters > 0
? `Preis je Liter: ${de(q.fuel_total_eur / q.liters, 3)}` : "Preis je Liter: wird berechnet";
? `Preis je Liter: ${de(q.fuel_total_eur / q.liters, 2)}` : "Preis je Liter: wird berechnet";
return `
<div class="feld"><label>Datum</label><input type="date" id="${praefix}Datum" value="${datum}"></div>
<div class="feld"><label>Uhrzeit</label><input type="time" id="${praefix}Zeit" value="${zeit}"></div>
@@ -2871,7 +2871,7 @@ function vFuel() {
<span class="k">${j}</span>
<span class="v" style="display:flex;flex-direction:column;align-items:flex-end;gap:5px">
<span style="font-size:17px;color:var(--fg)">${eur(sum(fj, (f) => f.fuel_total_eur || 0))} </span>
<span>${de(sum(fj, (f) => f.liters || 0), 1)} l · Ø ${de(schnittpreis(fj), 3)} /l</span>
<span>${de(sum(fj, (f) => f.liters || 0), 1)} l · Ø ${de(schnittpreis(fj), 2)} /l</span>
${CAR.smartdeal.aktiv ? `<span class="marke"><span class="pill" style="font-size:11.5px;padding:4px 10px">${markenlogo()}<span style="margin-left:6px">${eur(ersparnisJahr)} €</span></span></span>` : ""}
</span></button>
<div class="acc-b ${ji === 0 ? "on" : ""}" id="fy${j}">`;
@@ -2882,11 +2882,11 @@ function vFuel() {
<span class="mark"></span><span class="k">${m}</span>
<span class="v" style="display:flex;flex-direction:column;align-items:flex-end;gap:3px">
<span style="color:var(--fg)">${eur(sum(fm, (f) => f.fuel_total_eur || 0))} </span>
<span>Ø ${de(schnittpreis(fm), 3)} /l</span></span></button>
<span>Ø ${de(schnittpreis(fm), 2)} /l</span></span></button>
<div class="acc-b ${mi === 0 ? "on" : ""}" id="fm${j}${m}">`;
fm.forEach((f) => { const preis = f.liters ? f.fuel_total_eur / f.liters : null; h += swipeZeile(`fill:${f.tank_id}`, `<button class="leaf" data-go="fill:${f.tank_id}">
<span class="k">${dedat(new Date(f.ts))}<small>${esc(f.station_name || "")}</small></span>
<span class="v">${f.liters != null ? de(f.liters, 2) + " l" : ""}<small>${preis != null ? de(preis, 3) + " €/l" : ""}</small></span>
<span class="v">${f.liters != null ? de(f.liters, 2) + " l" : ""}<small>${preis != null ? de(preis, 2) + " €/l" : ""}</small></span>
<svg class="chev" viewBox="0 0 6 10"><polyline points="1,1 5,5 1,9"/></svg></button>`); });
h += `</div></div>`;
});
@@ -2925,7 +2925,7 @@ function vFill(id) {
<div class="row"><dt>Kraftstoff</dt><dd>${esc(f.fuel_type || KRAFTSTOFFSORTEN[0])}</dd></div>
<div class="row"><dt>Getankte Liter</dt><dd>${f.liters != null ? de(f.liters, 2) + " l" : ""}</dd></div>
<div class="row"><dt>Kosten</dt><dd>${f.fuel_total_eur != null ? eur(f.fuel_total_eur) + " " : ""}<small>real, nach Rabatt</small></dd></div>
<div class="row"><dt>Preis je Liter</dt><dd>${berechneterPreis != null ? de(berechneterPreis, 3) + " " : ""}<small>Kosten ÷ Liter</small></dd></div>
<div class="row"><dt>Preis je Liter</dt><dd>${berechneterPreis != null ? de(berechneterPreis, 2) + " " : ""}<small>Kosten ÷ Liter</small></dd></div>
${CAR.smartdeal.aktiv && f.discount != null ? `<div class="row"><dt><span class="marke">${markenlogo()} SmartDeal-Ersparnis</span></dt><dd>${eur(f.discount)} €</dd></div>` : ""}
<div class="row" style="border-bottom:0"><dt>Tankstelle</dt><dd>${esc(f.station_name || "unbekannt")}<small>${esc(f.station_address || "")}</small></dd></div>
</dl>
@@ -3006,7 +3006,7 @@ function hinweis(titel, text) {
unverändert. */
function belegDateiVerarbeiten(datei, tankId, kontext) {
if (!datei) return;
if (datei.type && datei.type !== "application/pdf") {
if (!istPdf(datei)) {
belegFehler = { context: kontext, meldung: "Nur PDF-Belege werden unterstützt" };
render(); return;
}
@@ -3025,9 +3025,16 @@ function belegDateiVerarbeiten(datei, tankId, kontext) {
leser.readAsDataURL(datei);
}
/* Nicht nur auf den MIME-Typ verlassen: eine aus dem Explorer/Finder kopierte
oder gezogene Datei kommt je nach Browser und Quelle mit leerem type an. */
function istPdf(datei) {
if (!datei) return false;
return datei.type === "application/pdf" || /\.pdf$/i.test(datei.name || "");
}
/* Nimmt das erste PDF aus einer DataTransfer-/Clipboard-Dateiliste. */
function belegAusDateiliste(dateien) {
return [...(dateien || [])].find((d) => d.type === "application/pdf") || null;
return [...(dateien || [])].find(istPdf) || null;
}
function belegPopupSchliessenUndLaden(datei) {
@@ -3040,12 +3047,21 @@ function belegPopupSchliessenUndLaden(datei) {
/* Telefon-Weg: das aus der Mail kopierte PDF liegt in der Zwischenablage.
navigator.clipboard.read() ist der einzige Weg, ohne dass der Nutzer in ein
Eingabefeld tippen muss - iOS zeigt dafür seine eigene Einfügen-Abfrage. */
Eingabefeld tippen muss - iOS zeigt dafür seine eigene Einfügen-Abfrage.
WICHTIG (Ursache eines gemeldeten Fehlers): Am Rechner hilft dieser Knopf
bei einer im Explorer/Finder kopierten Datei NICHT. clipboard.read() gibt
laut Spezifikation nur Text, HTML und Bilder heraus - eine kopierte
PDF-Datei taucht dort gar nicht auf, egal wie frisch sie ist. Der Weg, der
dort funktioniert, ist Strg+V: das paste-Ereignis trägt die echte Datei in
clipboardData.files. Deshalb sagt die Meldung unten genau das, statt
pauschal "kein PDF" zu behaupten. */
async function belegAusZwischenablage() {
const ctx = belegPopup;
if (!ctx) return;
const strgVHinweis = `Am Rechner kann der Browser eine kopierte Datei nur über ${tastenkuerzelEinfuegen()} entgegennehmen - bitte so einfügen oder das PDF hineinziehen.`;
if (!navigator.clipboard || !navigator.clipboard.read) {
belegPopup.fehler = "Dieser Browser kann die Zwischenablage nicht lesen - bitte ziehen oder Datei auswählen.";
belegPopup.fehler = strgVHinweis;
render(); return;
}
try {
@@ -3053,13 +3069,25 @@ async function belegAusZwischenablage() {
const typ = (eintrag.types || []).find((t) => t === "application/pdf");
if (typ) { belegPopupSchliessenUndLaden(new File([await eintrag.getType(typ)], "beleg.pdf", { type: "application/pdf" })); return; }
}
belegPopup.fehler = "In der Zwischenablage liegt kein PDF.";
belegPopup.fehler = strgVHinweis;
} catch (err) {
belegPopup.fehler = "Kein Zugriff auf die Zwischenablage.";
belegPopup.fehler = "Kein Zugriff auf die Zwischenablage. " + strgVHinweis;
}
render();
}
function tastenkuerzelEinfuegen() {
return /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent) ? "Cmd+V" : "Strg+V";
}
/* Der Einfügen-Knopf wird nur auf Tippgeräten angeboten. Am Rechner kann er
aus Datenschutzgründen prinzipiell nicht funktionieren (clipboard.read()
gibt Dateien nie heraus, siehe oben) - ein Knopf, der dort zwangsläufig
scheitert, führt nur in eine Sackgasse. Dort bleiben Ziehen und Strg+V. */
function zwischenablageKnopfSinnvoll() {
return window.matchMedia && window.matchMedia("(pointer: coarse)").matches;
}
function vBelegPopup() {
if (!belegPopup) return "";
return `<div class="beleg-catcher" data-belegzu></div>
@@ -3067,11 +3095,11 @@ function vBelegPopup() {
<span class="label">Beleg hochladen</span>
<div class="beleg-drop" id="belegDrop">
<strong>PDF hierher ziehen</strong>
<span>oder aus der Zwischenablage einfügen</span>
<span>oder mit ${tastenkuerzelEinfuegen()} einfügen</span>
</div>
${belegPopup.fehler ? `<span class="label" style="margin-top:10px;color:var(--red)">${esc(belegPopup.fehler)}</span>` : ""}
<button class="aktion primaer" data-belegeinfuegen>Aus Zwischenablage einfügen</button>
<button class="aktion" data-belegdatei>Datei auswählen</button>
${zwischenablageKnopfSinnvoll() ? `<button class="aktion primaer" data-belegeinfuegen>Aus Zwischenablage einfügen</button>` : ""}
<button class="aktion ${zwischenablageKnopfSinnvoll() ? "" : "primaer"}" data-belegdatei>Datei auswählen</button>
<button class="aktion" data-belegzu>Abbrechen</button>
</div>`;
}
@@ -3928,7 +3956,7 @@ function ereignisseVerdrahten() {
if (w === "fahrten") csvDatei("fahrten.csv", ["Start", "Ende", "km", "Art", "Status"],
TRIPS.map((t) => [t.ts_start, t.ts_end, t.distance_km != null ? de(t.distance_km) : "", t.art, t.status]));
else if (w === "tanken") csvDatei("tankvorgaenge.csv", ["Zeitpunkt", "Station", "Liter", "€/l", "Kosten €"],
FILLS.map((f) => [f.ts, f.station_name || "", f.liters != null ? de(f.liters, 2) : "", f.liters ? de(f.fuel_total_eur / f.liters, 3) : "", f.fuel_total_eur != null ? eur(f.fuel_total_eur) : ""]));
FILLS.map((f) => [f.ts, f.station_name || "", f.liters != null ? de(f.liters, 2) : "", f.liters ? de(f.fuel_total_eur / f.liters, 2) : "", f.fuel_total_eur != null ? eur(f.fuel_total_eur) : ""]));
else csvDatei("servicebuch.csv", ["Datum", "km", "Art", "Werkstatt", "Kosten €"],
CAR.service.buch.map((e2) => [e2.datum, de(e2.km), e2.art, e2.werkstatt, e2.kosten != null ? eur(e2.kosten) : ""]));
return;
@@ -3995,7 +4023,7 @@ function ereignisseVerdrahten() {
const liter = parseFloat((ROOT.getElementById(`${praefix}Liter`).value || "0").replace(",", "."));
const kosten = parseFloat((ROOT.getElementById(`${praefix}Kosten`).value || "0").replace(",", "."));
const anzeige = ROOT.getElementById(`${praefix}PreisAnzeige`);
if (anzeige) anzeige.textContent = liter > 0 ? `Preis je Liter: ${de(kosten / liter, 3)}` : "Preis je Liter: wird berechnet";
if (anzeige) anzeige.textContent = liter > 0 ? `Preis je Liter: ${de(kosten / liter, 2)}` : "Preis je Liter: wird berechnet";
});
ROOT.addEventListener("change", (e) => {
@@ -1 +1 @@
{"version": 1786919196}
{"version": 1786919856}