Nachrichten: Design-Label, server-seitige Präferenz, mobiler Leerraum fix

This commit is contained in:
2026-05-28 15:05:45 +02:00
parent 82a9609f0c
commit c082051d44
4 changed files with 38 additions and 9 deletions

View File

@@ -246,6 +246,11 @@ if (msgCols2.length && !msgCols2.includes('read_by_recipient')) {
db.exec("ALTER TABLE messages ADD COLUMN read_by_recipient INTEGER NOT NULL DEFAULT 0"); db.exec("ALTER TABLE messages ADD COLUMN read_by_recipient INTEGER NOT NULL DEFAULT 0");
} }
// User-Preferences (JSON-Blob)
const userCols2 = db.prepare("PRAGMA table_info(users)").all().map(r => r.name);
if (!userCols2.includes('preferences'))
db.exec("ALTER TABLE users ADD COLUMN preferences TEXT DEFAULT NULL");
// ── Login-Sicherheit ────────────────────────────────────────────────────────── // ── Login-Sicherheit ──────────────────────────────────────────────────────────
// Spalten für Account-Lockout in users-Tabelle // Spalten für Account-Lockout in users-Tabelle
const userCols = db.prepare("PRAGMA table_info(users)").all().map(r => r.name); const userCols = db.prepare("PRAGMA table_info(users)").all().map(r => r.name);

View File

@@ -150,4 +150,20 @@ router.delete('/account', authenticate, (req, res) => {
res.json({ success: true }); res.json({ success: true });
}); });
// ── GET/PUT /api/auth/preferences ─────────────────────────────────────────────
router.get('/preferences', authenticate, (req, res) => {
const user = db.prepare('SELECT preferences FROM users WHERE id=?').get(req.user.id);
try { res.json(JSON.parse(user?.preferences || '{}')); }
catch { res.json({}); }
});
router.put('/preferences', authenticate, (req, res) => {
const user = db.prepare('SELECT preferences FROM users WHERE id=?').get(req.user.id);
let current = {};
try { current = JSON.parse(user?.preferences || '{}'); } catch {}
const merged = { ...current, ...req.body };
db.prepare('UPDATE users SET preferences=? WHERE id=?').run(JSON.stringify(merged), req.user.id);
res.json(merged);
});
module.exports = router; module.exports = router;

View File

@@ -1660,7 +1660,7 @@ export default function App() {
const activeTool = TOOLS.find(t=>t.id===active); const activeTool = TOOLS.find(t=>t.id===active);
// Mobile padding bottom für fixed nav // Mobile padding bottom für fixed nav
const mainStyle = { flex:1, overflowY:'auto', ...(mobile ? { paddingBottom:'calc(56px + env(safe-area-inset-bottom, 0px))' } : {}) }; const mainStyle = { flex:1, overflowY:'auto', ...(mobile ? { paddingBottom: active==='nachrichten' ? 0 : 'calc(56px + env(safe-area-inset-bottom, 0px))' } : {}) };
return( return(
<> <>

View File

@@ -82,8 +82,16 @@ export default function Nachrichten({ toast, mobile }) {
const [text, setText] = useState(''); const [text, setText] = useState('');
const [sending, setSending] = useState(false); const [sending, setSending] = useState(false);
const [showPicker, setShowPicker] = useState(false); const [showPicker, setShowPicker] = useState(false);
const [mirc, setMirc] = useState(() => localStorage.getItem('dd_chat_design') === 'mirc'); const [mirc, setMirc] = useState(false);
const toggleDesign = () => setMirc(v => { const n = !v; localStorage.setItem('dd_chat_design', n ? 'mirc' : 'modern'); return n; }); // Preference vom Server laden
useEffect(() => {
api('/auth/preferences').then(p => { if (p.chat_design === 'mirc') setMirc(true); }).catch(()=>{});
}, []);
const toggleDesign = () => setMirc(v => {
const next = !v;
api('/auth/preferences', { method:'PUT', body:{ chat_design: next ? 'mirc' : 'modern' } }).catch(()=>{});
return next;
});
const bottomRef = useRef(null); const bottomRef = useRef(null);
const pollRef = useRef(null); const pollRef = useRef(null);
const pickerRef = useRef(null); const pickerRef = useRef(null);
@@ -247,12 +255,12 @@ export default function Nachrichten({ toast, mobile }) {
display:'flex', alignItems:'center', justifyContent:'space-between' }}> display:'flex', alignItems:'center', justifyContent:'space-between' }}>
<div style={{ ...S.head, marginBottom:0 }}>NACHRICHTEN</div> <div style={{ ...S.head, marginBottom:0 }}>NACHRICHTEN</div>
<div style={{ display:'flex', gap:5 }}> <div style={{ display:'flex', gap:5 }}>
<button onClick={toggleDesign} title={mirc ? 'Modern' : 'mIRC-Stil'} <button onClick={toggleDesign} title={mirc ? 'Zu Modern wechseln' : 'Zu mIRC wechseln'}
style={{ background: mirc ? 'rgba(78,205,196,0.12)' : 'transparent', style={{ background: 'rgba(255,255,255,0.05)',
border:`1px solid ${mirc ? 'rgba(78,205,196,0.3)' : 'rgba(255,255,255,0.1)'}`, border:'1px solid rgba(255,255,255,0.1)',
borderRadius:7, color: mirc ? '#4ecdc4' : 'rgba(255,255,255,0.35)', borderRadius:7, color:'rgba(255,255,255,0.55)',
cursor:'pointer', padding:'3px 7px', fontSize:10, fontFamily:'monospace', lineHeight:1 }}> cursor:'pointer', padding:'3px 8px', fontSize:10, fontFamily:'monospace', lineHeight:1 }}>
{mirc ? '◈ mIRC' : '◈'} {mirc ? '◈ mIRC' : '◈ Modern'}
</button> </button>
<button onClick={() => { loadUsers(); if (activeUser) loadMessages(activeUser.id); }} title="Neu laden" <button onClick={() => { loadUsers(); if (activeUser) loadMessages(activeUser.id); }} title="Neu laden"
style={{ background:'transparent', border:'1px solid rgba(255,255,255,0.1)', borderRadius:7, style={{ background:'transparent', border:'1px solid rgba(255,255,255,0.1)', borderRadius:7,