All checks were successful
Build & Deploy / deploy (push) Successful in 1m29s
Bildbearbeitung im Nachgang: Drehen in 90-Grad-Schritten, Ausrichten per
Schieberegler, Zuschneiden per Rahmen mit anfassbaren Ecken. Der Server
rechnet jede Korrektur vom unbearbeiteten Original, das beim ersten
Eingriff daneben abgelegt wird - sonst summieren sich die Verluste über
mehrere Korrekturen, und Zurücksetzen wäre nicht möglich. Zwei
Zuschnitte hintereinander werden ineinander verrechnet.
Der Ort ließ sich nicht übernehmen, weil beide Quellen versagen können:
iOS entfernt beim Weitergeben an eine Webseite oft die GPS-Daten aus dem
Foto, und der Browserstandort scheiterte bisher stumm. Jetzt meldet er
seinen Grund ("Standortfreigabe fehlt"), die Detailansicht zeigt die
Herkunft des Ortes, und der Scan-Ort ist von Hand nachtragbar.
E-Mail, Web, Telefon und Mobil sind in der Tabelle jetzt Links; Straße,
PLZ, Ort und Land stehen als eine Adresszeile, die die eingestellte
Navigations-App öffnet.
Einstellungen als vierter Reiter: Navigations-App (Apple Karten, Google
Maps, OpenStreetMap), Statusübersicht und Abmelden - der Knopf saß
vorher in der Kopfzeile.
Die Kopfzeile ist weg. Sie kostete auf dem Telefon eine Bildschirmzeile
und wiederholte nur, was die Tableiste schon sagt.
Kein full_name mehr, nur Vor- und Nachname; akademische Titel gehören in
den Vornamen. Bestehende Datensätze werden bei der Migration am letzten
Leerzeichen aufgeteilt. Die Spalte bleibt ungenutzt stehen: SQLite baut
zum Löschen die ganze Tabelle neu, der Gewinn wären ein paar Byte.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
85 lines
2.8 KiB
Python
85 lines
2.8 KiB
Python
"""vCard 3.0 erzeugen - das Format, das iOS beim Antippen zuverlaessig oeffnet."""
|
|
from datetime import datetime
|
|
|
|
_ESCAPES = (("\\", "\\\\"), (";", "\\;"), (",", "\\,"), ("\n", "\\n"))
|
|
|
|
|
|
def _escape(value) -> str:
|
|
text = str(value)
|
|
for char, replacement in _ESCAPES:
|
|
text = text.replace(char, replacement)
|
|
return text
|
|
|
|
|
|
def _line(name: str, value) -> list:
|
|
return [name + ":" + _escape(value)] if value else []
|
|
|
|
|
|
def _display_name(card: dict) -> str:
|
|
joined = " ".join(filter(None, (card.get("first_name"), card.get("last_name"))))
|
|
return joined or card.get("company") or "Unbenannt"
|
|
|
|
|
|
def _note(card: dict) -> str:
|
|
parts = []
|
|
if card.get("notes"):
|
|
parts.append(card["notes"])
|
|
|
|
stamp = card.get("captured_at") or card.get("created_at")
|
|
scanned = ""
|
|
if stamp:
|
|
try:
|
|
when = datetime.fromisoformat(stamp).strftime("%d.%m.%Y um %H:%M")
|
|
scanned = "Gescannt am " + when
|
|
except ValueError:
|
|
scanned = "Gescannt am " + str(stamp)
|
|
if card.get("place"):
|
|
scanned = (scanned + " in " if scanned else "Gescannt in ") + card["place"]
|
|
if scanned:
|
|
parts.append(scanned)
|
|
return "\n".join(parts)
|
|
|
|
|
|
def build(card: dict) -> str:
|
|
lines = ["BEGIN:VCARD", "VERSION:3.0"]
|
|
lines.append(
|
|
"N:{};{};;;".format(
|
|
_escape(card.get("last_name") or ""), _escape(card.get("first_name") or "")
|
|
)
|
|
)
|
|
lines.append("FN:" + _escape(_display_name(card)))
|
|
lines += _line("ORG", card.get("company"))
|
|
lines += _line("TITLE", card.get("job_title"))
|
|
if card.get("email"):
|
|
lines.append("EMAIL;TYPE=INTERNET,WORK:" + _escape(card["email"]))
|
|
if card.get("phone"):
|
|
lines.append("TEL;TYPE=WORK,VOICE:" + _escape(card["phone"]))
|
|
if card.get("mobile"):
|
|
lines.append("TEL;TYPE=CELL:" + _escape(card["mobile"]))
|
|
if card.get("fax"):
|
|
lines.append("TEL;TYPE=WORK,FAX:" + _escape(card["fax"]))
|
|
if card.get("website"):
|
|
lines.append("URL:" + _escape(card["website"]))
|
|
if any(card.get(k) for k in ("street", "postal_code", "city", "country")):
|
|
lines.append(
|
|
"ADR;TYPE=WORK:;;{};{};;{};{}".format(
|
|
_escape(card.get("street") or ""),
|
|
_escape(card.get("city") or ""),
|
|
_escape(card.get("postal_code") or ""),
|
|
_escape(card.get("country") or ""),
|
|
)
|
|
)
|
|
if card.get("lat") is not None and card.get("lon") is not None:
|
|
lines.append("GEO:{};{}".format(card["lat"], card["lon"]))
|
|
note = _note(card)
|
|
if note:
|
|
lines.append("NOTE:" + _escape(note))
|
|
lines.append("END:VCARD")
|
|
return "\r\n".join(lines) + "\r\n"
|
|
|
|
|
|
def filename(card: dict) -> str:
|
|
base = _display_name(card).replace(" ", "-")
|
|
safe = "".join(c for c in base if c.isalnum() or c in "-_")
|
|
return (safe or "kontakt") + ".vcf"
|