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