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:
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user