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); }}