Nachrichten: Emoji-Button volle Höhe, mIRC-Design-Toggle

This commit is contained in:
2026-05-28 14:56:11 +02:00
parent 637a77af2d
commit 82a9609f0c

View File

@@ -82,6 +82,8 @@ export default function Nachrichten({ toast, mobile }) {
const [text, setText] = useState('');
const [sending, setSending] = useState(false);
const [showPicker, setShowPicker] = useState(false);
const [mirc, setMirc] = useState(() => localStorage.getItem('dd_chat_design') === 'mirc');
const toggleDesign = () => setMirc(v => { const n = !v; localStorage.setItem('dd_chat_design', n ? 'mirc' : 'modern'); return n; });
const bottomRef = useRef(null);
const pollRef = useRef(null);
const pickerRef = useRef(null);
@@ -244,11 +246,20 @@ export default function Nachrichten({ toast, mobile }) {
<div style={{ padding:'16px 14px 12px', borderBottom:'1px solid rgba(255,255,255,0.06)',
display:'flex', alignItems:'center', justifyContent:'space-between' }}>
<div style={{ ...S.head, marginBottom:0 }}>NACHRICHTEN</div>
<div style={{ display:'flex', gap:5 }}>
<button onClick={toggleDesign} title={mirc ? 'Modern' : 'mIRC-Stil'}
style={{ background: mirc ? 'rgba(78,205,196,0.12)' : 'transparent',
border:`1px solid ${mirc ? 'rgba(78,205,196,0.3)' : 'rgba(255,255,255,0.1)'}`,
borderRadius:7, color: mirc ? '#4ecdc4' : 'rgba(255,255,255,0.35)',
cursor:'pointer', padding:'3px 7px', fontSize:10, fontFamily:'monospace', lineHeight:1 }}>
{mirc ? '◈ mIRC' : '◈'}
</button>
<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,
color:'rgba(255,255,255,0.35)', cursor:'pointer', padding:'3px 8px',
fontSize:13, fontFamily:'monospace', lineHeight:1 }}></button>
</div>
</div>
<div style={{ flex:1, overflowY:'auto' }}>
{users.length === 0 ? (
<div style={{ color:'rgba(255,255,255,0.28)', fontSize:11, fontFamily:'monospace',
@@ -349,7 +360,10 @@ export default function Nachrichten({ toast, mobile }) {
</div>
{/* Nachrichten */}
<div style={{ flex:1, overflowY:'auto', padding:'14px', display:'flex', flexDirection:'column', gap:10 }}>
<div style={{ flex:1, overflowY:'auto', padding: mirc ? '0' : '14px',
display:'flex', flexDirection:'column', gap: mirc ? 0 : 10,
background: mirc ? '#000' : undefined,
fontFamily: mirc ? "'Courier New', Courier, monospace" : undefined }}>
{!activeUser.public_key && (
<InfoBox color="#ffe66d">
Dieser Benutzer hat die Nachrichten noch nicht geöffnet und besitzt noch kein Schlüsselpaar.
@@ -357,25 +371,56 @@ export default function Nachrichten({ toast, mobile }) {
)}
{sharedKey === 'error' && (
<InfoBox color="#ff6b9d">
Schlüssel inkompatibel der Empfänger verwendet ein anderes Schlüsselformat (z. B. altes P-256).
Schlüssel inkompatibel der Empfänger verwendet ein anderes Schlüsselformat.
Er muss in Einstellungen Sicherheit einen neuen Schlüssel generieren.
</InfoBox>
)}
{messages.length === 0 && activeUser.public_key && sharedKey !== 'error' && (
<div style={{ flex:1, display:'flex', alignItems:'center', justifyContent:'center',
color:'rgba(255,255,255,0.18)', fontFamily:'monospace', fontSize:11 }}>
Noch keine Nachrichten
color: mirc ? '#808080' : 'rgba(255,255,255,0.18)',
fontFamily: mirc ? "'Courier New', monospace" : 'monospace', fontSize:11 }}>
{mirc ? '*** Noch keine Nachrichten ***' : 'Noch keine Nachrichten'}
</div>
)}
{/* Hinweis wenn Nachrichten nicht entschlüsselt werden können */}
{Object.values(decrypted).some(v => v === null) && (
<InfoBox color="#ffe66d">
🔑 Einige Nachrichten können nicht entschlüsselt werden dein privater Schlüssel passt nicht mehr.{' '}
Importiere deinen alten Schlüssel unter{' '}
<strong>Einstellungen Sicherheit Schlüssel importieren</strong>,
oder erzeuge einen neuen Schlüssel (löscht alle bisherigen Nachrichten).
🔑 Einige Nachrichten können nicht entschlüsselt werden.
Importiere deinen alten Schlüssel unter Einstellungen Sicherheit.
</InfoBox>
)}
{mirc ? (
/* ── mIRC Design ─────────────────────────────────────── */
<div style={{ padding:'4px 0' }}>
{messages.map(m => {
const isMine = !!m.is_mine;
const txt = decrypted[m.id];
const time = m.created_at ? (() => { const d = new Date(m.created_at.replace(' ','T')); return isNaN(d)?'':d.toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'}); })() : '';
const nick = isMine ? 'du' : activeUser.username;
const nickColor = isMine ? '#00ff00' : '#00ffff';
return (
<div key={m.id} style={{ display:'flex', alignItems:'baseline', gap:0,
padding:'1px 8px', lineHeight:1.5,
background: isMine ? 'rgba(0,255,0,0.03)' : 'transparent' }}
onMouseEnter={e=>e.currentTarget.style.background='rgba(255,255,255,0.04)'}
onMouseLeave={e=>e.currentTarget.style.background=isMine?'rgba(0,255,0,0.03)':'transparent'}>
<span style={{ color:'#808080', fontSize:11, flexShrink:0, marginRight:2 }}>[{time}]</span>
<span style={{ color:'#808080', fontSize:11, flexShrink:0, marginRight:2 }}>&lt;</span>
<span style={{ color:nickColor, fontSize:11, fontWeight:700, flexShrink:0 }}>{nick}</span>
<span style={{ color:'#808080', fontSize:11, flexShrink:0, marginRight:6 }}>&gt;</span>
<span style={{ color: txt===null?'#404040':'#d0d0d0', fontSize:12, wordBreak:'break-word', flex:1 }}>
{txt===undefined ? '…' : txt===null ? '[verschlüsselt]' : txt}
</span>
{isMine && m.read_by_recipient && <span style={{color:'#004400',fontSize:9,marginLeft:4,flexShrink:0}}></span>}
<button onClick={() => deleteMsg(m.id)} style={{ background:'transparent', border:'none',
cursor:'pointer', color:'#400000', fontSize:9, padding:'0 0 0 6px', flexShrink:0, opacity:0.5 }}></button>
</div>
);
})}
</div>
) : (
/* ── Modernes Design ─────────────────────────────────── */
<>
{messages.map(m => {
const isMine = !!m.is_mine;
const txt = decrypted[m.id];
@@ -385,9 +430,7 @@ export default function Nachrichten({ toast, mobile }) {
{!isMine && (
<div style={{ width:22, height:22, borderRadius:'50%', flexShrink:0,
background:'rgba(255,255,255,0.04)', border:'1px solid rgba(255,255,255,0.07)',
display:'flex', alignItems:'center', justifyContent:'center' }}>
<UserIcon size={10} color="rgba(255,255,255,0.4)"/>
</div>
display:'flex', alignItems:'center', justifyContent:'center', fontSize:10 }}>👤</div>
)}
<div style={{ maxWidth:'75%', display:'flex', flexDirection:'column',
alignItems: isMine ? 'flex-end' : 'flex-start', gap:4 }}>
@@ -400,9 +443,7 @@ export default function Nachrichten({ toast, mobile }) {
{txt === undefined ? (
<span style={{ color:'rgba(255,255,255,0.18)', fontSize:12 }}></span>
) : txt === null ? (
<span style={{ color:'rgba(255,107,157,0.5)', fontSize:11, fontFamily:'monospace' }}>
🔒 Nicht entschlüsselbar
</span>
<span style={{ color:'rgba(255,107,157,0.5)', fontSize:11, fontFamily:'monospace' }}>🔒 Nicht entschlüsselbar</span>
) : (
<span style={{ color:'#e6e6e6', fontSize:13, fontFamily:'monospace',
lineHeight:1.55, whiteSpace:'pre-wrap', wordBreak:'break-word' }}>{txt}</span>
@@ -428,19 +469,22 @@ export default function Nachrichten({ toast, mobile }) {
</div>
);
})}
</>
)}
<div ref={bottomRef}/>
</div>
{/* Eingabe */}
<div style={{ padding:'10px 14px', borderTop:'1px solid rgba(255,255,255,0.06)',
background:'#0d0d0f', flexShrink:0 }}>
<div style={{ padding:'10px 14px', flexShrink:0,
borderTop:`1px solid ${mirc?'#333':'rgba(255,255,255,0.06)'}`,
background: mirc ? '#000' : '#0d0d0f' }}>
{!activeUser.public_key || sharedKey === 'error' ? (
<div style={{ color:'rgba(255,255,255,0.2)', fontSize:11, fontFamily:'monospace',
textAlign:'center', padding:'8px 0' }}>
Senden nicht möglich
</div>
) : (
<div style={{ display:'flex', gap:8, alignItems:'flex-end', position:'relative' }}>
<div style={{ display:'flex', gap:8, alignItems:'stretch', position:'relative' }}>
<div ref={pickerRef} style={{ position:'absolute', bottom:'100%', right:0 }}>
{showPicker && (
<EmojiPicker
@@ -449,16 +493,17 @@ export default function Nachrichten({ toast, mobile }) {
/>
)}
</div>
{mirc && (
<span style={{ color:'#00ff00', fontFamily:"'Courier New',monospace", fontSize:12,
alignSelf:'center', flexShrink:0 }}>[du]</span>
)}
<textarea
value={text}
onChange={e => {
const val = e.target.value;
const last = val[val.length - 1];
if (last === ' ' || last === '\n') {
setText(convertEmojis(val));
} else {
setText(val);
}
if (last === ' ' || last === '\n') setText(convertEmojis(val));
else setText(val);
}}
onFocus={() => setShowPicker(false)}
onKeyDown={e => {
@@ -469,18 +514,18 @@ export default function Nachrichten({ toast, mobile }) {
send(converted);
}
}}
placeholder="Nachricht… (Enter senden · Shift+Enter neue Zeile)"
placeholder={mirc ? 'Nachricht…' : 'Nachricht… (Enter senden · Shift+Enter neue Zeile)'}
rows={2}
style={{ ...S.inp, resize:'none', flex:1, fontSize:13, padding:'8px 10px',
lineHeight:1.45, minHeight:42 }}
style={{ ...S.inp, resize:'none', flex:1, fontSize: mirc ? 12 : 13,
padding:'8px 10px', lineHeight:1.45, minHeight:42,
...(mirc ? { background:'#111', border:'1px solid #444', color:'#d0d0d0',
fontFamily:"'Courier New',monospace", borderRadius:2 } : {}) }}
/>
<div style={{ display:'flex', flexDirection:'column', gap:4, flexShrink:0, alignSelf:'stretch' }}>
<button onClick={() => setShowPicker(v => !v)} style={{
background: showPicker ? 'rgba(78,205,196,0.12)' : 'rgba(255,255,255,0.05)',
border:`1px solid ${showPicker ? 'rgba(78,205,196,0.3)' : 'rgba(255,255,255,0.1)'}`,
borderRadius:8, cursor:'pointer', fontSize:17, lineHeight:1,
padding:'5px 8px', flexShrink:0, alignSelf:'flex-end' }}>😊</button>
</div>
background: showPicker ? 'rgba(78,205,196,0.12)' : (mirc ? '#111' : 'rgba(255,255,255,0.05)'),
border:`1px solid ${showPicker ? 'rgba(78,205,196,0.3)' : (mirc ? '#444' : 'rgba(255,255,255,0.1)')}`,
borderRadius: mirc ? 2 : 8, cursor:'pointer', fontSize:17,
padding:'0 10px', flexShrink:0, alignSelf:'stretch' }}>😊</button>
</div>
)}
</div>