Files
audi-app/companion-app/src/daten/zwischenablage.ts
T
tobias a99ae432fc Tankstellen-Nadel, doppeltes Tanken-Symbol, Tankstellenname, packageClassList
Fuenf Meldungen des Eigentuemers, vier davon behoben:

1. Tankstellen-Nadel war bei 32 px ein dunkler Fleck (poi zeichnet nur
   einen Ring). Gefuellter Punkt in currentColor zwischen Silhouette und
   Symbol - die Geometrie stand im Pfad. Rot wurde ausdruecklich
   abgelehnt, die Umsetzung aendert keine Farbe.

2. Tanken-Symbol im Standort-Blatt wurde gefuellt UND umrandet, weil das
   CI-Symbol zusaetzlich stroke-Attribute mitbekam. Jetzt ueber ciSVG
   wie das Parkplatz-Symbol daneben; in der App dafuer imRaster an
   SymbolTanken (der 0,84-Faktor gilt nur in der Reiterleiste).

3. Tankstellenname: der Bestand hat zwei Bauformen - aeltere Belege
   tragen den Betreiber in station_name und die Anschrift daneben.
   Neu tankstelleTeile() liest beide Felder, entfernt Firmierungen,
   kuerzt Strassen und trennt die Hausnummer auch ohne Leerzeichen.
   Uebersicht: Marke, Strasse, Ort. Einzelbeleg: zwei Zeilen.
   adresseTeilen() hat keinen Aufrufer mehr und ist entfernt.

4. Zwischenablage und Kalendereintrag hatten denselben Grund: Capacitor 8
   registriert nur, was in packageClassList steht, und cap sync traegt
   dort ausschliesslich npm-Pakete ein. Beide app-eigenen Plugins lagen
   im Programm und wurden nie registriert. ios-teilen-einrichten.mjs
   traegt sie jetzt nach, ios-signieren.sh bricht ab, wenn sie im
   fertigen .ipa fehlen.

5. Geteilter Beleg nur mit Datum und Uhrzeit bleibt offen - der Weg
   nutzt denselben belegLesen()-Pfad wie eine gewaehlte Datei, es fehlt
   der konkrete Beleg oder die Protokollzeile.

tsc sauber, 246 Tests, vite build sauber, Panel als Modul geparst,
audi_ha_test auf 2026.9.4.20 ohne Traceback. Panel und App liefern fuer
die Tankstelle byteweise dasselbe.
2026-09-04 16:34:46 +02:00

135 lines
5.8 KiB
TypeScript

/**
* Beleg-PDF aus der Zwischenablage oder einer Dateiliste.
*
* Übernommen aus dem Panel (`belegAusZwischenablage()`, `istPdf()`,
* `zwischenablageKnopfSinnvoll()` in audi-dashboard-app.js) — dort gibt es
* diesen Weg seit längerem, in der App fehlte er ganz: der Beleg-Knopf öffnete
* direkt die Dateiauswahl, und ein aus der Mail kopiertes PDF ließ sich
* überhaupt nicht einfügen (gemeldet vom Eigentümer, 2026-08-31).
*
* WARUM DER KNOPF NUR AUF TIPPGERÄTEN ERSCHEINT
* ---------------------------------------------
* `navigator.clipboard.read()` gibt aus Datenschutzgründen nur Text, HTML und
* Bilder heraus. Eine im Explorer oder Finder kopierte Datei taucht dort nie
* auf — am Rechner scheitert der Weg also zwangsläufig, und ein Knopf, der
* immer fehlschlägt, ist schlimmer als kein Knopf. Dort bleiben Ziehen und
* Dateiauswahl.
*
* AUF DEM TELEFON LÄUFT ER SEIT DEM 02.09.2026 NATIV
* --------------------------------------------------
* Dieselbe Grenze der Web-Schnittstelle traf nämlich auch iOS: ein aus Mail
* kopiertes PDF ist über `navigator.clipboard.read()` NIE zu bekommen, der
* Knopf endete dort zwangsläufig mit „In der Zwischenablage liegt kein PDF"
* (gemeldet vom Eigentümer). `UIPasteboard` kennt die Grenze nicht — deshalb
* geht die Anfrage auf dem Gerät über ein kleines eigenes Plugin
* (native/BelegZwischenablage.swift). Die Einfügen-Rückfrage des Systems
* erscheint dabei genauso wie im Web.
*/
import { Capacitor, registerPlugin } from "@capacitor/core"
/** 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. */
export function istPdf(datei: File | null | undefined): boolean {
if (!datei) return false
return datei.type === "application/pdf" || /\.pdf$/i.test(datei.name || "")
}
/** Nimmt das erste PDF aus einer DataTransfer-/Dateiliste. */
export function pdfAusDateiliste(dateien: FileList | File[] | null | undefined): File | null {
return [...(dateien ?? [])].find(istPdf) ?? null
}
export class ZwischenablageFehler extends Error {}
interface BelegZwischenablage {
/** Leeres Ergebnis heißt „nichts Passendes kopiert" - kein Fehler. */
pdfHolen(): Promise<{ base64?: string; name?: string }>
}
/** Muss zu `jsName` in BelegZwischenablage.swift passen. */
const Nativ = registerPlugin<BelegZwischenablage>("BelegZwischenablage")
function base64ZuDatei(base64: string, name: string): File {
const roh = atob(base64)
const bytes = new Uint8Array(roh.length)
for (let i = 0; i < roh.length; i++) bytes[i] = roh.charCodeAt(i)
return new File([bytes], name, { type: "application/pdf" })
}
/**
* Holt ein PDF aus der Zwischenablage.
*
* Wirft mit einem Text, der zur jeweiligen Ursache die richtige Abhilfe nennt —
* "bitte Datei auswählen" hilft nicht weiter, wenn schlicht kein PDF kopiert
* wurde, und "erneut kopieren" nicht, wenn der Browser die Zwischenablage gar
* nicht herausgibt.
*/
export async function pdfAusZwischenablage(): Promise<File> {
if (Capacitor.isNativePlatform()) {
/* Meldet sich der native Teil nicht bei der Bruecke, hilft nur die
Dateiauswahl - der Web-Weg kann ein PDF grundsaetzlich nicht liefern
(Begruendung im Modulkopf).
Bis zum 04.09.2026 stand hier die Vermutung, die App sei nur ueber die
Aktualisierung gewachsen. Sie war falsch: der Eigentuemer hatte frisch
installiert, und BelegZwischenablagePlugin lag nachweislich im
Programm. Capacitor 8 sucht Plugin-Klassen aber nicht im Programm,
sondern registriert ausschliesslich, was in packageClassList der
capacitor.config.json steht - und `cap sync` traegt dort nur
npm-Pakete ein, keine app-eigenen Plugins. Behoben in
scripts/ios-teilen-einrichten.mjs; hier steht deshalb nur noch die
Tatsache und nicht mehr eine geratene Ursache. */
if (!Capacitor.isPluginAvailable("BelegZwischenablage")) {
throw new ZwischenablageFehler(
'Diese App-Fassung kann die Zwischenablage nicht abfragen - bitte ' +
'"Datei auswählen".',
)
}
let ergebnis: { base64?: string; name?: string }
try {
ergebnis = await Nativ.pdfHolen()
} catch {
// Abgelehnte Einfügen-Rückfrage des Systems.
throw new ZwischenablageFehler(
'Kein Zugriff auf die Zwischenablage - bitte "Datei auswählen".',
)
}
if (!ergebnis?.base64) {
throw new ZwischenablageFehler(
'In der Zwischenablage liegt kein PDF - bitte den Beleg erneut kopieren oder "Datei auswählen".',
)
}
return base64ZuDatei(ergebnis.base64, ergebnis.name || "beleg.pdf")
}
if (!navigator.clipboard?.read) {
throw new ZwischenablageFehler(
'Dieser Browser gibt die Zwischenablage nicht heraus - bitte "Datei auswählen".',
)
}
let eintraege: ClipboardItems
try {
eintraege = await navigator.clipboard.read()
} catch {
// Verweigerte Freigabe, abgebrochene System-Abfrage, unsicherer Ursprung.
throw new ZwischenablageFehler('Kein Zugriff auf die Zwischenablage - bitte "Datei auswählen".')
}
for (const eintrag of eintraege) {
const typ = eintrag.types.find((t) => t === "application/pdf")
if (typ) {
const blob = await eintrag.getType(typ)
// Ein Name muss sein: das Backend legt den Beleg unter ihm ab.
return new File([blob], "beleg.pdf", { type: "application/pdf" })
}
}
throw new ZwischenablageFehler(
'In der Zwischenablage liegt kein PDF - bitte den Beleg erneut kopieren oder "Datei auswählen".',
)
}
/** Tippgerät? Nur dort wird der Einfügen-Knopf angeboten (Begründung oben). */
export function zwischenablageKnopfSinnvoll(): boolean {
return typeof window !== "undefined" && !!window.matchMedia?.("(pointer: coarse)").matches
}