Nachrichten: Emoji-Button volle Höhe, mIRC-Design-Toggle
This commit is contained in:
@@ -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 }}><</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 }}>></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>
|
||||||
|
|||||||
Reference in New Issue
Block a user