diff --git a/wwwroot/css/chat.css b/wwwroot/css/chat.css index 8467fdf..a889b5d 100644 --- a/wwwroot/css/chat.css +++ b/wwwroot/css/chat.css @@ -318,6 +318,57 @@ color: var(--dangerous); } +/* Emoji Picker */ + +.emoji-picker { + position: fixed; + z-index: 500; + background: var(--f2-bg); + border: var(--border-width) solid var(--f2-border); + border-radius: var(--border-radius); + width: 280px; + max-height: 320px; + overflow-y: auto; + padding: var(--badge-spacing); + display: flex; + flex-direction: column; + gap: var(--box-spacing); + box-shadow: 0 4px 16px rgb(0 0 0 / 0.2); +} + +.emoji-picker-section { + display: flex; + flex-direction: column; + gap: var(--badge-spacing); +} + +.emoji-picker-label { + font-size: var(--font-size); + opacity: var(--semi-transparent); + padding: 0 .2em; +} + +.emoji-picker-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(2.6em, 1fr)); +} + +.emoji-picker-btn { + background: none; + border: none; + padding: .2em .25em; + font-size: 1.3rem; + line-height: 1; + cursor: pointer; + border-radius: var(--border-radius); + transition: background var(--transition-duration); +} + +.emoji-picker-btn:hover { + background: var(--f3-bg); + border: none; +} + .stop-btn:hover { background: rgb(from var(--dangerous) r g b / 0.15); } diff --git a/wwwroot/index.html b/wwwroot/index.html index 1c48b4f..e18ff64 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -217,6 +217,7 @@ + @@ -389,6 +390,7 @@ + diff --git a/wwwroot/js/emoji.userdata.js b/wwwroot/js/emoji.userdata.js new file mode 100644 index 0000000..e34533f --- /dev/null +++ b/wwwroot/js/emoji.userdata.js @@ -0,0 +1,9 @@ +const EMOJI_USERDATA = { + 'Smileys': ['๐Ÿ˜Š','๐Ÿ˜‚','๐Ÿคฃ','๐Ÿ˜„','๐Ÿ˜','๐Ÿ˜…','๐Ÿ˜†','๐Ÿ™‚','๐Ÿ˜‰','๐Ÿ˜Ž','๐Ÿค“','๐Ÿ˜‡','๐Ÿฅณ','๐Ÿคฉ','๐Ÿ˜','๐Ÿฅฐ','๐Ÿ˜˜','๐Ÿ˜‹','๐Ÿค”','๐Ÿคจ','๐Ÿ˜','๐Ÿ˜‘','๐Ÿ™„','๐Ÿ˜','๐Ÿ˜ฌ','๐Ÿคซ','๐Ÿคญ','๐Ÿ˜Œ','๐Ÿ˜ด','๐Ÿฅฑ','๐Ÿ˜ฎ','๐Ÿ˜ฒ','๐Ÿคฏ','๐Ÿ˜ณ','๐Ÿ˜ฑ','๐Ÿ˜จ','๐Ÿ˜ฐ','๐Ÿ˜ข','๐Ÿ˜ญ','๐Ÿ˜ค','๐Ÿ˜ก','๐Ÿคฌ','๐Ÿ’€','๐Ÿ˜ˆ','๐Ÿ‘ฟ','๐Ÿคก','๐Ÿ’ฉ','๐Ÿ‘ป','๐Ÿ‘ฝ','๐Ÿค–'], + 'Gesten': ['๐Ÿ‘','๐Ÿ‘Ž','๐Ÿ‘Œ','โœŒ๏ธ','๐Ÿคž','๐Ÿค™','๐Ÿ‘‹','๐Ÿคš','โœ‹','๐Ÿ–๏ธ','๐Ÿ‘†','โ˜๏ธ','๐Ÿ‘‡','๐Ÿ‘ˆ','๐Ÿ‘‰','๐Ÿ‘Š','โœŠ','๐Ÿค›','๐Ÿคœ','๐Ÿ™','๐Ÿ‘','๐Ÿ™Œ','๐Ÿค','๐Ÿ’ช','๐ŸคŸ','๐Ÿคฒ'], + 'Herzen': ['โค๏ธ','๐Ÿงก','๐Ÿ’›','๐Ÿ’š','๐Ÿ’™','๐Ÿ’œ','๐Ÿ–ค','๐Ÿค','๐Ÿ’”','๐Ÿ’•','๐Ÿ’–','๐Ÿ’—','๐Ÿ’˜','๐Ÿ’'], + 'Objekte': ['๐Ÿ”ฅ','โญ','โœจ','๐Ÿ’ก','๐Ÿ’Ž','๐ŸŽฏ','๐ŸŽฎ','๐ŸŽต','๐ŸŽถ','๐Ÿ“Œ','๐Ÿ“Ž','โœ๏ธ','๐Ÿ“','๐Ÿ“š','๐Ÿ’ป','โŒจ๏ธ','๐Ÿ–ฅ๏ธ','๐Ÿ“ฑ','๐Ÿ”ง','๐Ÿ”จ','โšก','๐Ÿš€','๐Ÿ†','๐ŸŽ‰','๐ŸŽŠ','๐ŸŽ','๐ŸŽˆ'], + 'Natur': ['๐ŸŒž','๐ŸŒ™','โ›…','๐ŸŒˆ','๐ŸŒŠ','๐Ÿ€','๐ŸŒธ','๐ŸŒบ','๐ŸŒป','๐ŸŒฒ','๐Ÿ‚','โ„๏ธ','๐Ÿฑ','๐Ÿถ','๐ŸฆŠ','๐Ÿป','๐Ÿผ','๐Ÿจ','๐Ÿฆ','๐Ÿธ','๐Ÿง','๐Ÿฆ„','๐Ÿ','๐Ÿฆ‹'], + 'Essen': ['โ˜•','๐Ÿบ','๐Ÿท','๐Ÿ•','๐Ÿ”','๐ŸŸ','๐ŸŒญ','๐Ÿฟ','๐Ÿง','๐Ÿฐ','๐Ÿฉ','๐Ÿช','๐Ÿซ','๐Ÿ‰','๐Ÿ“','๐Ÿ‘','๐Ÿฅ‘','๐ŸŒถ๏ธ'], + 'Reaktionen': ['โœ…','โŒ','โš ๏ธ','โ“','โ—','๐Ÿ’ฏ','โ™ป๏ธ','๐Ÿ”„','โณ','โฐ','๐Ÿ””','๐Ÿ”•','๐Ÿ“ข','๐Ÿ’ฌ','๐Ÿ’ญ','๐Ÿ‘€','๐Ÿซ ','๐Ÿคท','๐Ÿคฆ','๐Ÿฅบ',] +}; diff --git a/wwwroot/js/pages/chat-page.js b/wwwroot/js/pages/chat-page.js index 05f2de0..559c6ab 100644 --- a/wwwroot/js/pages/chat-page.js +++ b/wwwroot/js/pages/chat-page.js @@ -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;