Feature: Tagebuch Ort/POI, Foto/Video-Edit, Modal-UX, iOS-Fixes

Tagebuch — Ort/POI (DayOne-ähnlich):
- diary.location_name Spalte, DiaryCreate/Update mit gps_lat/lon/location_name
- GET /api/dogs/{id}/diary/nearby: Overpass + Nominatim (vor {entry_id}-Route)
- Mini-Karte im Edit-Formular: Leaflet lazy, Edit-Modus, SVG-Pin
- Meilenstein-Toggle: Button statt Checkbox, Filter in Toolbar
- Datenmigration: 97 Ort-Einträge aus text → location_name

Tagebuch — Foto/Video:
- Foto/Video im Edit: Ersetzen + Löschen, DELETE media endpoint
- Media-Picker: Kamera/Mediathek/Datei Buttons
- Video-Wiedergabe (<video controls> in Detail + Edit)

Modal-UX (alle Edit-Karten vereinheitlicht):
- Footer-Pattern: [Speichern vollbreit] / [Löschen][Abbrechen]
- diary, dog-profile, events, health, places, walks, settings, sitting
- Löschen aus Detail-Modal → Edit-Form verschoben

iOS Mobile-Fixes:
- Auto-Zoom: input/select/textarea font-size 16px !important
- Scroll-Through: html.modal-open + touch-action:none auf Overlay
- Kein position:fixed mehr auf body (kein Scroll-Sprung)

PWA & Icons:
- icon-512-any.png + icon-192-any.png (quadratisch, maskable)
- manifest.json: purpose any/maskable getrennt
- Gesundheits-Icon: syringe → first-aid

Import-Fix:
- _HTMLStripper überspringt video/audio/script → kein "Video nicht gefunden" mehr
This commit is contained in:
rene 2026-04-18 11:56:54 +02:00
parent 88912e2746
commit f8d354749d
19 changed files with 963 additions and 198 deletions

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '133'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '164'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => {
@ -417,8 +417,13 @@ const App = (() => {
}
_updateNotifBadge();
// Badge alle 60s aktualisieren
setInterval(_updateNotifBadge, 60_000);
const pendingInvite = sessionStorage.getItem('pending_invite');
if (pendingInvite) {
sessionStorage.removeItem('pending_invite');
_handleInvite(pendingInvite);
}
}
async function _updateNotifBadge() {
@ -670,15 +675,23 @@ const App = (() => {
history.replaceState(null, '', '/');
return;
}
const ok = await UI.modal.confirm(
`<strong>${UI.escape(info.owner_name)}</strong> möchte das Profil von
<strong>${UI.escape(info.dog_name)}</strong> mit dir teilen
(${info.role === 'editor' ? 'Lesen & Schreiben' : 'Nur lesen'}).
Möchtest du die Einladung annehmen?`
);
if (!state.user) {
sessionStorage.setItem('pending_invite', token);
history.replaceState(null, '', '/');
navigate('settings', false);
UI.toast.info('Bitte melde dich an, um die Einladung anzunehmen.');
return;
}
const ok = await UI.modal.confirm({
message: `<strong>${UI.escape(info.owner_name)}</strong> möchte das Profil von
<strong>${UI.escape(info.dog_name)}</strong> mit dir teilen
(${info.role === 'editor' ? 'Lesen & Schreiben' : 'Nur lesen'}).
Möchtest du die Einladung annehmen?`,
});
if (!ok) { history.replaceState(null, '', '/'); return; }
await API.sharing.accept(token);
// Hundeliste neu laden
state.dogs = await API.dogs.list();
const newDog = state.dogs.find(d => d.name === info.dog_name);
if (newDog) {