diff --git a/backend/static/js/app.js b/backend/static/js/app.js
index 6e844cd..fb48468 100644
--- a/backend/static/js/app.js
+++ b/backend/static/js/app.js
@@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
-const APP_VER = '167'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
+const APP_VER = '168'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => {
diff --git a/backend/static/js/pages/diary.js b/backend/static/js/pages/diary.js
index dabca72..fa11127 100644
--- a/backend/static/js/pages/diary.js
+++ b/backend/static/js/pages/diary.js
@@ -643,13 +643,37 @@ window.Page_diary = (() => {
}
}
- mediaInput?.addEventListener('change', () => _showPreview(mediaInput.files[0]));
+ function _showAlbumBtn(file) {
+ // Vorherigen Button entfernen falls vorhanden
+ document.getElementById('diary-save-album-btn')?.remove();
+ // Nur anbieten wenn Share-API File-Support hat ODER als Download-Fallback
+ const canShare = navigator.canShare && navigator.canShare({ files: [file] });
+ const canDownload = true; // funktioniert immer als Fallback
+ if (!canShare && !canDownload) return;
+ const btn = document.createElement('button');
+ btn.type = 'button';
+ btn.id = 'diary-save-album-btn';
+ btn.className = 'btn btn-secondary btn-sm';
+ btn.style.cssText = 'display:flex;align-items:center;gap:var(--space-1);margin-top:var(--space-2);width:100%';
+ btn.innerHTML = `
+ ${canShare ? 'Zum Fotoalbum hinzufügen' : 'Foto herunterladen'}`;
+ btn.addEventListener('click', () => UI.saveToAlbum(file));
+ previewWrap.after(btn);
+ }
+
+ mediaInput?.addEventListener('change', () => {
+ _showPreview(mediaInput.files[0]);
+ // Kein Album-Button bei Mediathek-Picks (Fotos sind bereits dort)
+ document.getElementById('diary-save-album-btn')?.remove();
+ });
cameraInput?.addEventListener('change', () => {
// Auswahl in mediaInput spiegeln damit Submit-Handler nur einen Ort abfragt
const dt = new DataTransfer();
if (cameraInput.files[0]) dt.items.add(cameraInput.files[0]);
mediaInput.files = dt.files;
_showPreview(cameraInput.files[0]);
+ // Album-Button nach Kamera-Aufnahme anzeigen
+ if (cameraInput.files[0]) _showAlbumBtn(cameraInput.files[0]);
});
document.getElementById('diary-btn-camera') ?.addEventListener('click', () => cameraInput.click());
@@ -677,6 +701,7 @@ window.Page_diary = (() => {
previewWrap.style.display = 'none';
photoPreview.src = ''; videoPreview.src = '';
mediaInput.value = '';
+ document.getElementById('diary-save-album-btn')?.remove();
});
// "Entfernen"-Button löscht Medium direkt
diff --git a/backend/static/js/ui.js b/backend/static/js/ui.js
index 04e6246..2d8c1df 100644
--- a/backend/static/js/ui.js
+++ b/backend/static/js/ui.js
@@ -307,6 +307,41 @@ const UI = (() => {
if (parseFloat(tip.style.left) > maxL) tip.style.left = maxL + 'px';
});
+ // ----------------------------------------------------------
+ // SAVE TO ALBUM — Foto/Video nach Kamera-Aufnahme in Mediathek
+ // anbieten. Nur bei capture-Aufnahmen aufrufen (nicht Galerie).
+ // Nutzt Web Share API wenn verfügbar, sonst Fallback.
+ // ----------------------------------------------------------
+ async function saveToAlbum(file) {
+ if (!file) return;
+
+ // Web Share API mit Datei-Support (iOS Safari 15+, Chrome Android)
+ if (navigator.canShare && navigator.canShare({ files: [file] })) {
+ try {
+ await navigator.share({
+ files: [file],
+ title: file.name || 'Foto',
+ });
+ } catch (err) {
+ // AbortError = User hat Sheet geschlossen — kein Fehler anzeigen
+ if (err?.name !== 'AbortError') {
+ console.warn('saveToAlbum share error:', err);
+ }
+ }
+ return;
+ }
+
+ // Fallback: direkter Download per
+ const url = URL.createObjectURL(file);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = file.name || ('foto_' + Date.now() + (file.type === 'video/mp4' ? '.mp4' : '.jpg'));
+ a.style.display = 'none';
+ document.body.appendChild(a);
+ a.click();
+ setTimeout(() => { URL.revokeObjectURL(url); a.remove(); }, 2000);
+ }
+
// Öffentliche API
return {
toast, modal,
@@ -316,6 +351,7 @@ const UI = (() => {
setupPhotoPreview, scrollTop, skeleton,
icon: _svgIcon,
escape, help,
+ saveToAlbum,
};
})();
diff --git a/backend/static/sw.js b/backend/static/sw.js
index f0234d9..c26cbbc 100644
--- a/backend/static/sw.js
+++ b/backend/static/sw.js
@@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
-const CACHE_VERSION = 'by-v199';
+const CACHE_VERSION = 'by-v200';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten