diff --git a/backend/src/tools/dateien/routes.js b/backend/src/tools/dateien/routes.js index 0eb6af3..dfdb304 100644 --- a/backend/src/tools/dateien/routes.js +++ b/backend/src/tools/dateien/routes.js @@ -279,6 +279,22 @@ router.delete('/:id/share/:userId', authenticate, (req, res) => { res.json({ success: true }); }); +router.get('/storage', authenticate, (req, res) => { + const uid = req.user.id; + const used = db.prepare('SELECT COALESCE(SUM(size),0) AS s FROM files WHERE user_id=?').get(uid).s; + const max = parseInt(getSetting('file_max_size_mb') || '50') * + parseInt(getSetting('file_max_count') || '20') * 1024 * 1024; + // Disk space via Node + const { execSync } = require('child_process'); + let diskFree = null; + try { + const out = execSync(`df -k "${process.env.UPLOAD_DIR || '/data/uploads'}" | tail -1`).toString(); + const parts = out.trim().split(/\s+/); + diskFree = parseInt(parts[3]) * 1024; // available bytes + } catch {} + res.json({ used, max, diskFree }); +}); + // Settings router.get('/settings', authenticate, requireAdmin, (req, res) => { const s = db.prepare('SELECT * FROM admin_settings').all(); diff --git a/frontend/src/tools/dateien.jsx b/frontend/src/tools/dateien.jsx index b009a70..b2995c6 100644 --- a/frontend/src/tools/dateien.jsx +++ b/frontend/src/tools/dateien.jsx @@ -172,6 +172,7 @@ export default function Dateien({ toast, mobile }) { const [dragOver, setDragOver] = useState(false); const [newFolder, setNewFolder] = useState(false); const [folderName, setFolderName] = useState(''); + const [storage, setStorage] = useState(null); const fileRef = useRef(null); const { confirm, ConfirmDialog } = useConfirm(); @@ -186,6 +187,7 @@ export default function Dateien({ toast, mobile }) { }) .catch(e=>toast(e.message,'error')) .finally(()=>setLoading(false)); + api('/tools/dateien/storage').then(setStorage).catch(()=>{}); }, [currentFolderId]); useEffect(()=>{load();},[load]); @@ -276,7 +278,28 @@ export default function Dateien({ toast, mobile }) { return (
- {shareItem && {setShareItem(null);load();}} toast={toast}/>} + {/* ── Speicherplatz ─────────────────────────────────────────────────── */} + {storage && ( +
+
+ {fmtSize(storage.used)} belegt + {storage.diskFree!=null ? `${fmtSize(storage.diskFree)} frei auf Disk` : ''} +
+
+
0.85 ? '#ff6b9d' : + storage.used/storage.max > 0.6 ? '#ffe66d' : '#4ecdc4', + transition:'width 0.4s', + }}/> +
+
+ Limit: {fmtSize(storage.max)} +
+
+ )} item={shareItem.item} type={shareItem.type} onClose={()=>{setShareItem(null);load();}} toast={toast}/>} {pwPrompt && { setPwPrompt(null); download(pwPrompt, pw); }} diff --git a/frontend/src/tools/nachrichten.jsx b/frontend/src/tools/nachrichten.jsx index 915b44f..55bb5c2 100644 --- a/frontend/src/tools/nachrichten.jsx +++ b/frontend/src/tools/nachrichten.jsx @@ -25,12 +25,10 @@ const EMOJI_MAP = { // Ersetze Kürzel am Ende des Textes (nach Leerzeichen oder Satzanfang) function convertEmojis(text) { - let result = text; - for (const [code, emoji] of Object.entries(EMOJI_MAP)) { - const escaped = code.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - result = result.replace(new RegExp(`(^|\\s)${escaped}(?=\\s|$)`, 'g'), `$1${emoji}`); - } - return result; + // Letztes "Wort" vor dem Leerzeichen/Newline prüfen + return text.replace(/(\S+)(\s)$/, (_, word, space) => { + return (EMOJI_MAP[word] ?? word) + space; + }); } // ── Emoji-Picker Daten ──────────────────────────────────────────────────────── @@ -86,6 +84,17 @@ export default function Nachrichten({ toast, mobile }) { const [showPicker, setShowPicker] = useState(false); const bottomRef = useRef(null); const pollRef = useRef(null); + const pickerRef = useRef(null); + + useEffect(() => { + if (!showPicker) return; + const handler = e => { + if (pickerRef.current && !pickerRef.current.contains(e.target)) setShowPicker(false); + }; + document.addEventListener('mousedown', handler); + document.addEventListener('touchstart', handler); + return () => { document.removeEventListener('mousedown', handler); document.removeEventListener('touchstart', handler); }; + }, [showPicker]); // ── Initialisierung ────────────────────────────────────────────────────────── useEffect(() => { @@ -430,7 +439,7 @@ export default function Nachrichten({ toast, mobile }) { Senden nicht möglich
) : ( -
+
{showPicker && ( { setText(t => t + em); setShowPicker(false); }}