Smilies bei Nachrichten
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user