feat: Emoji-Picker Button im Chat-Input (rechts neben Senden)
- #emojiBtn links neben #send platziert - Fluent Emoji 3D Rendering via parseFluentEmojis() - Kein Tooltip auf den Emoji-Buttons - Picker öffnet sich rechts-ausgerichtet über dem Button - emoji.userdata.js geladen (Kategorien + Mappings) - Schließt beim Klick außerhalb oder beim destroy()
This commit is contained in:
1 parent
cdd3414771
commit
2673aecc5b
4 files changed
+163
No files matched your search
@@ -0,0 +1,9 @@
|
||||
const EMOJI_USERDATA = {
|
||||
'Smileys': ['😊','😂','🤣','😄','😁','😅','😆','🙂','😉','😎','🤓','😇','🥳','🤩','😍','🥰','😘','😋','🤔','🤨','😐','😑','🙄','😏','😬','🤫','🤭','😌','😴','🥱','😮','😲','🤯','😳','😱','😨','😰','😢','😭','😤','😡','🤬','💀','😈','👿','🤡','💩','👻','👽','🤖'],
|
||||
'Gesten': ['👍','👎','👌','✌️','🤞','🤙','👋','🤚','✋','🖐️','👆','☝️','👇','👈','👉','👊','✊','🤛','🤜','🙏','👏','🙌','🤝','💪','🤟','🤲'],
|
||||
'Herzen': ['❤️','🧡','💛','💚','💙','💜','🖤','🤍','💔','💕','💖','💗','💘','💝'],
|
||||
'Objekte': ['🔥','⭐','✨','💡','💎','🎯','🎮','🎵','🎶','📌','📎','✏️','📝','📚','💻','⌨️','🖥️','📱','🔧','🔨','⚡','🚀','🏆','🎉','🎊','🎁','🎈'],
|
||||
'Natur': ['🌞','🌙','⛅','🌈','🌊','🍀','🌸','🌺','🌻','🌲','🍂','❄️','🐱','🐶','🦊','🐻','🐼','🐨','🦁','🐸','🐧','🦄','🐝','🦋'],
|
||||
'Essen': ['☕','🍺','🍷','🍕','🍔','🍟','🌭','🍿','🧁','🍰','🍩','🍪','🍫','🍉','🍓','🍑','🥑','🌶️'],
|
||||
'Reaktionen': ['✅','❌','⚠️','❓','❗','💯','♻️','🔄','⏳','⏰','🔔','🔕','📢','💬','💭','👀','🫠','🤷','🤦','🥺',]
|
||||
};
|
||||
@@ -2,6 +2,97 @@
|
||||
|
||||
let _streamingPollInterval = null;
|
||||
let _recoveryChainId = null;
|
||||
let _emojiPickerEl = null;
|
||||
let _emojiPickerCloseHandler = null;
|
||||
|
||||
function _buildEmojiPicker() {
|
||||
const picker = document.createElement('div');
|
||||
picker.className = 'emoji-picker';
|
||||
|
||||
for (const [category, emojis] of Object.entries(EMOJI_USERDATA)) {
|
||||
const section = document.createElement('div');
|
||||
section.className = 'emoji-picker-section';
|
||||
|
||||
const label = document.createElement('div');
|
||||
label.className = 'emoji-picker-label';
|
||||
label.textContent = category;
|
||||
section.appendChild(label);
|
||||
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'emoji-picker-grid';
|
||||
|
||||
for (const emoji of emojis) {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'emoji-picker-btn';
|
||||
btn.textContent = emoji;
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const input = document.getElementById('input');
|
||||
if (input) {
|
||||
const start = input.selectionStart;
|
||||
const end = input.selectionEnd;
|
||||
const val = input.value;
|
||||
input.value = val.slice(0, start) + emoji + val.slice(end);
|
||||
const pos = start + emoji.length;
|
||||
input.setSelectionRange(pos, pos);
|
||||
input.dispatchEvent(new Event('input'));
|
||||
input.focus();
|
||||
}
|
||||
closeEmojiPicker();
|
||||
});
|
||||
// Fluent Emoji im Button rendern
|
||||
parseFluentEmojis(btn);
|
||||
grid.appendChild(btn);
|
||||
}
|
||||
|
||||
section.appendChild(grid);
|
||||
picker.appendChild(section);
|
||||
}
|
||||
|
||||
return picker;
|
||||
}
|
||||
|
||||
function toggleEmojiPicker(anchor) {
|
||||
if (_emojiPickerEl) {
|
||||
closeEmojiPicker();
|
||||
return;
|
||||
}
|
||||
|
||||
const picker = _buildEmojiPicker();
|
||||
document.body.appendChild(picker);
|
||||
_emojiPickerEl = picker;
|
||||
|
||||
// Positionierung: über dem Button, rechts ausgerichtet
|
||||
const rect = anchor.getBoundingClientRect();
|
||||
const pickerHeight = 320;
|
||||
const pickerWidth = 280;
|
||||
let top = rect.top - pickerHeight - 6;
|
||||
let left = rect.right - pickerWidth;
|
||||
|
||||
// Falls kein Platz oben → unter dem Button
|
||||
if (top < 8) top = rect.bottom + 6;
|
||||
// Falls links abgeschnitten → linksbündig zum Button
|
||||
if (left < 8) left = 8;
|
||||
|
||||
picker.style.top = top + 'px';
|
||||
picker.style.left = left + 'px';
|
||||
|
||||
_emojiPickerCloseHandler = (e) => {
|
||||
if (!picker.contains(e.target)) closeEmojiPicker();
|
||||
};
|
||||
setTimeout(() => document.addEventListener('click', _emojiPickerCloseHandler), 0);
|
||||
}
|
||||
|
||||
function closeEmojiPicker() {
|
||||
if (_emojiPickerEl) {
|
||||
_emojiPickerEl.remove();
|
||||
_emojiPickerEl = null;
|
||||
}
|
||||
if (_emojiPickerCloseHandler) {
|
||||
document.removeEventListener('click', _emojiPickerCloseHandler);
|
||||
_emojiPickerCloseHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
function startStreamingPoll(sessionId, sendBtn, chainId) {
|
||||
_recoveryChainId = chainId;
|
||||
@@ -190,6 +281,15 @@ registerPage('chat', {
|
||||
inputEl.dispatchEvent(new Event('input'))
|
||||
}
|
||||
|
||||
// Emoji Picker
|
||||
const emojiBtn = document.getElementById('emojiBtn');
|
||||
if (emojiBtn) {
|
||||
emojiBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
toggleEmojiPicker(emojiBtn);
|
||||
});
|
||||
}
|
||||
|
||||
// Workspace Explorer
|
||||
const explorerBtn = document.getElementById('explorerBtn');
|
||||
if (explorerBtn) {
|
||||
@@ -275,6 +375,7 @@ registerPage('chat', {
|
||||
// Stream nicht abbrechen (Backend soll weiterlaufen),
|
||||
// aber Frontend-Processing stoppen
|
||||
_chatAbandoned = true;
|
||||
closeEmojiPicker();
|
||||
if (this._closeMenu) {
|
||||
document.removeEventListener('click', this._closeMenu);
|
||||
this._closeMenu = null;
|
||||
|
||||
Reference in new issue
Block a user