Smilies bei Nachrichten

This commit is contained in:
2026-05-28 11:37:32 +02:00
parent d60e3a29d8
commit d932f1edb5

View File

@@ -10,6 +10,68 @@ import {
decryptMsg, decryptMsg,
} from '../crypto.js'; } from '../crypto.js';
// ── Emoji-Konvertierung ───────────────────────────────────────────────────────
const EMOJI_MAP = {
':D':'😁', ':)':'🙂', ':-)':'🙂', '(:':'🙂', ';)':'😉', ';-)':'😉',
':P':'😛', ':-P':'😛', ':p':'😛', ':O':'😮', ':-O':'😮', ':o':'😮',
':(':'😢', ':-(':'😢', ":'(":'😭', '>:(':"😠", '>:-(':"😠",
':*':'😘', ':-*':'😘', '<3':'❤️', '</3':'💔',
'xD':'😆', 'XD':'😆', 'x)':"😄",
':/':"😕", ':-/':"😕", ':|':"😐", ':-|':"😐",
'B)':"😎", 'B-)':"😎", '^_^':"😊", '-_-':"😑",
'O:)':"😇", 'O:-)':"😇", '>:)':"😈",
':3':"🐱", ':$':"😳",
};
// 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;
}
// ── Emoji-Picker Daten ────────────────────────────────────────────────────────
const EMOJI_GROUPS = [
{ label:'Smileys', emojis:['😀','😁','😂','🤣','😊','😇','🙂','😉','😌','😍','🥰','😘','😎','🤩','😏','😒','😞','😢','😭','😤','😠','😡','🤬','😈','👿','💀','😱','😨','😰','😓','🤔','🤐','😶','😐','😑','😬','🙄','😴','🤧','🤒','😷','🤕'] },
{ label:'Gesten', emojis:['👍','👎','👌','✌️','🤞','🤟','🤙','👋','🤚','🖐','✋','🤜','🤛','👏','🙌','🤝','🙏','💪','🫶','❤️','🧡','💛','💚','💙','💜','🖤','💔','💯','💥','✨','🎉','🔥','⭐','🌟'] },
{ label:'Dinge', emojis:['😂💀','🎮','🏆','🎯','🎲','🃏','🎸','🎵','🎤','📱','💻','⌨️','🖱️','📷','🎬','📺','🚀','🛸','🌍','🌈','☀️','🌙','⭐','🍕','🍔','🍟','🌮','🍩','🍺','☕','🧃'] },
];
function EmojiPicker({ onSelect, onClose }) {
const [group, setGroup] = useState(0);
return (
<div style={{ position:'absolute', bottom:'100%', right:0, marginBottom:6, zIndex:100,
background:'#1a1a1e', border:'1px solid rgba(255,255,255,0.12)', borderRadius:12,
width:272, boxShadow:'0 8px 32px rgba(0,0,0,0.5)' }}
onMouseDown={e => e.preventDefault()}>
<div style={{ display:'flex', borderBottom:'1px solid rgba(255,255,255,0.07)', padding:'6px 8px', gap:4 }}>
{EMOJI_GROUPS.map((g,i) => (
<button key={i} onClick={() => setGroup(i)} style={{ flex:1, background: group===i ?
'rgba(78,205,196,0.15)' : 'transparent', border:'none', borderRadius:7,
color: group===i ? '#4ecdc4' : 'rgba(255,255,255,0.4)', cursor:'pointer',
fontSize:9, fontFamily:'monospace', padding:'4px 2px' }}>{g.label}</button>
))}
</div>
<div style={{ display:'flex', flexWrap:'wrap', padding:8, gap:2, maxHeight:180, overflowY:'auto' }}>
{EMOJI_GROUPS[group].emojis.filter(e => e.length <= 2 || [...e].length <= 2).concat(
EMOJI_GROUPS[group].emojis.filter(e => [...e].length > 2)
).map((em,i) => (
<button key={i} onClick={() => onSelect(em)} style={{ background:'transparent', border:'none',
borderRadius:6, cursor:'pointer', fontSize:20, padding:'4px 5px',
transition:'background 0.1s' }}
onMouseEnter={e => e.target.style.background='rgba(255,255,255,0.08)'}
onMouseLeave={e => e.target.style.background='transparent'}>
{em}
</button>
))}
</div>
</div>
);
}
export default function Nachrichten({ toast, mobile }) { export default function Nachrichten({ toast, mobile }) {
const [kp, setKp] = useState(null); const [kp, setKp] = useState(null);
const [initError, setInitError] = useState(false); const [initError, setInitError] = useState(false);
@@ -21,6 +83,7 @@ export default function Nachrichten({ toast, mobile }) {
const [partnerFp, setPartnerFp] = useState(''); const [partnerFp, setPartnerFp] = useState('');
const [text, setText] = useState(''); const [text, setText] = useState('');
const [sending, setSending] = useState(false); const [sending, setSending] = useState(false);
const [showPicker, setShowPicker] = useState(false);
const bottomRef = useRef(null); const bottomRef = useRef(null);
const pollRef = useRef(null); const pollRef = useRef(null);
@@ -367,19 +430,42 @@ export default function Nachrichten({ toast, mobile }) {
Senden nicht möglich Senden nicht möglich
</div> </div>
) : ( ) : (
<div style={{ display:'flex', gap:8, alignItems:'flex-end' }}> <div style={{ display:'flex', gap:8, alignItems:'flex-end', position:'relative' }}>
{showPicker && (
<EmojiPicker
onSelect={em => { setText(t => t + em); setShowPicker(false); }}
onClose={() => setShowPicker(false)}
/>
)}
<textarea <textarea
value={text} onChange={e => setText(e.target.value)} value={text}
onChange={e => {
const val = e.target.value;
// Kürzel nur konvertieren wenn gerade ein Leerzeichen oder Enter getippt wurde
const last = val[val.length - 1];
if (last === ' ' || last === '\n') {
setText(convertEmojis(val));
} else {
setText(val);
}
}}
onKeyDown={e => { if (e.key==='Enter' && !e.shiftKey) { e.preventDefault(); send(); }}} onKeyDown={e => { if (e.key==='Enter' && !e.shiftKey) { e.preventDefault(); send(); }}}
placeholder="Nachricht… (Enter senden · Shift+Enter neue Zeile)" placeholder="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:13, padding:'8px 10px',
lineHeight:1.45, minHeight:42 }} lineHeight:1.45, minHeight:42 }}
/> />
<button onClick={send} disabled={sending || !text.trim()} style={{ <div style={{ display:'flex', flexDirection:'column', gap:4, flexShrink:0, alignSelf:'stretch' }}>
...S.btn('#4ecdc4'), padding:'10px 16px', flexShrink:0, alignSelf:'stretch', <button onClick={() => setShowPicker(v => !v)} style={{
opacity: sending || !text.trim() ? 0.3 : 1, fontSize:17, lineHeight:1, background: showPicker ? 'rgba(78,205,196,0.12)' : 'rgba(255,255,255,0.05)',
}}></button> 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 }}>😊</button>
<button onClick={send} disabled={sending || !text.trim()} style={{
...S.btn('#4ecdc4'), flex:1, padding:'0 14px',
opacity: sending || !text.trim() ? 0.3 : 1, fontSize:17, lineHeight:1,
}}></button>
</div>
</div> </div>
)} )}
</div> </div>