diff --git a/frontend/src/tools/dateien.jsx b/frontend/src/tools/dateien.jsx index ecab45f..42e70e0 100644 --- a/frontend/src/tools/dateien.jsx +++ b/frontend/src/tools/dateien.jsx @@ -29,13 +29,18 @@ const IconBtn = ({ onClick, color, title, children, stop }) => ( // ── Share Modal ─────────────────────────────────────────────────────────────── function ShareModal({ item, type, onClose, toast }) { const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); const [shares, setShares] = useState([]); const base = type==='folder' ? `/tools/dateien/folders/${item.id}` : `/tools/dateien/${item.id}`; useEffect(() => { api(`${base}/shares`).then(setShares).catch(()=>{}); }, [base]); + const share = async () => { if (!username.trim()) return; - try { await api(`${base}/share`,{body:{username:username.trim()}}); toast('Geteilt ✓'); setUsername(''); api(`${base}/shares`).then(setShares); } - catch(e) { toast(e.message,'error'); } + try { + await api(`${base}/share`, { body: { username: username.trim(), password: password.trim() || undefined } }); + toast('Geteilt ✓'); setUsername(''); setPassword(''); + api(`${base}/shares`).then(setShares); + } catch(e) { toast(e.message,'error'); } }; const unshare = async uid => { try { await api(`${base}/share/${uid}`,{method:'DELETE'}); setShares(p=>p.filter(s=>s.id!==uid)); toast('Freigabe entfernt'); } @@ -55,27 +60,57 @@ function ShareModal({ item, type, onClose, toast }) { -
- setUsername(e.target.value)} onKeyDown={e=>e.key==='Enter'&&share()} - placeholder="Benutzername" autoCapitalize="none" style={{...S.inp,flex:1,fontSize:15}}/> - -
+ + {/* Teilen-Formular */} + {type==='file' && ( +
+
+ setUsername(e.target.value)} onKeyDown={e=>e.key==='Enter'&&share()} + placeholder="Benutzername" autoCapitalize="none" style={{...S.inp,flex:1,fontSize:15}}/> + +
+ setPassword(e.target.value)} + placeholder="Passwort (optional)" type="text" autoCapitalize="none" + style={{...S.inp,fontSize:13,padding:'7px 10px'}}/> + {password &&
+ ⚠ Teile das Passwort separat mit dem Empfänger +
} +
+ )} + {type==='folder' && ( +
+ setUsername(e.target.value)} onKeyDown={e=>e.key==='Enter'&&share()} + placeholder="Benutzername" autoCapitalize="none" style={{...S.inp,flex:1,fontSize:15}}/> + +
+ )} + + {/* Bestehende Shares */} {shares.length>0 && (
-
GETEILT MIT
+
GETEILT MIT
{shares.map(s=>(
-
{s.username}
- {s.shared_at &&
seit {fmtDate(s.shared_at)}
} +
+ {s.username} + {s.has_password ? 🔑 : null} +
+
+ {s.shared_at && `geteilt ${fmtDate(s.shared_at)}`} + {s.accessed_at + ? ✓ zugegriffen {fmtDate(s.accessed_at)} + : ○ noch nicht geöffnet + } +
- +
))}
)} - {shares.length===0 &&
Noch mit niemandem geteilt.
} + {shares.length===0 &&
Noch mit niemandem geteilt.
} ); @@ -128,17 +163,26 @@ export default function Dateien({ toast, mobile }) { const onFileInput = e => { doUpload(e.target.files?.[0]); e.target.value=''; }; const onDrop = e => { e.preventDefault(); setDragOver(false); doUpload(e.dataTransfer.files?.[0]); }; - const download = async file => { + const download = async (file, pw) => { try { - const res = await fetch(`/api/tools/dateien/${file.id}/download`,{ - headers:{Authorization:`Bearer ${localStorage.getItem('sk_token')}`} - }); - if (!res.ok){const err=await res.json().catch(()=>({error:'Fehler'}));toast(err.error,'error');return;} + const url = `/api/tools/dateien/${file.id}/download${pw ? `?password=${encodeURIComponent(pw)}` : ''}`; + const res = await fetch(url, { headers:{Authorization:`Bearer ${localStorage.getItem('sk_token')}`} }); + if (!res.ok) { + const err = await res.json().catch(()=>({error:'Fehler'})); + if (err.passwordRequired) { + const entered = window.prompt(`🔑 Datei "${file.originalname}" ist passwortgeschützt.\nBitte Passwort eingeben:`); + if (entered !== null) await download(file, entered); + return; + } + if (pw && res.status===403) { toast('Falsches Passwort', 'error'); return; } + toast(err.error,'error'); return; + } const blob = await res.blob(); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); a.href=url; a.download=file.originalname; + const a = Object.assign(document.createElement('a'), { + href: URL.createObjectURL(blob), download: file.originalname, + }); document.body.appendChild(a); a.click(); document.body.removeChild(a); - setTimeout(()=>URL.revokeObjectURL(url),1000); + setTimeout(()=>URL.revokeObjectURL(a.href),1000); } catch(e){toast(e.message,'error');} }; @@ -345,7 +389,15 @@ export default function Dateien({ toast, mobile }) {
{fmtSize(file.size)} · {fmtDate(file.created_at)} {tab==='shared'?` · von ${file.owner}`:''} - {tab==='sharedByMe'&&file.shared_with_names?` · → ${file.shared_with_names}${file.shared_at?' seit '+fmtDate(file.shared_at):''}`:'' } + {tab==='sharedByMe'&&file.shared_with_names?( + + {` · → ${file.shared_with_names}`} + {file.accessed_at + ? · ✓ {fmtDate(file.accessed_at)} + : · ○ nicht geöffnet + } + + ):''}
{/* Aktionen – gleiche Breite wie bei Ordnern */}