Улучшения UI чата: круглые кнопки, emoji-picker, исправление WebSocket переподключения
- Сделаны круглыми кнопки отправки и прокрутки вниз - Убран фон у поля ввода сообщений - Добавлен emoji-picker-element с поддержкой категорий - Скрыта кнопка выбора тона кожи в emoji picker - Изменена иконка кнопки отправки на ракету - Исправлена бесконечная перезагрузка при падении WebSocket - Добавлено умное переподключение WebSocket с 5 попытками - Улучшена анимация кнопки прокрутки вниз
This commit is contained in:
parent
0b3a633290
commit
67f118a1df
2
backend/static/css/bootstrap-custom.css
vendored
2
backend/static/css/bootstrap-custom.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -60,6 +60,7 @@ class ChatComposer {
|
||||
}
|
||||
|
||||
this.bindEvents();
|
||||
this.bindEmojiPicker();
|
||||
form.dataset.composerBound = '1';
|
||||
}
|
||||
|
||||
@ -95,6 +96,42 @@ class ChatComposer {
|
||||
window.addEventListener('chat:message-received', this.boundMessageHandler);
|
||||
}
|
||||
|
||||
bindEmojiPicker() {
|
||||
// Новый emoji-picker-element
|
||||
const picker = this.form.querySelector('[data-emoji-picker]');
|
||||
if (picker) {
|
||||
picker.addEventListener('emoji-click', (event) => {
|
||||
event.preventDefault();
|
||||
const emoji = event.detail?.unicode;
|
||||
if (emoji) {
|
||||
this.insertEmoji(emoji);
|
||||
}
|
||||
});
|
||||
|
||||
// Предотвращаем закрытие dropdown при клике внутри emoji-picker
|
||||
const emojiDropdown = picker.closest('.dropdown-menu');
|
||||
if (emojiDropdown) {
|
||||
emojiDropdown.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insertEmoji(emoji) {
|
||||
if (!this.textarea) return;
|
||||
const textarea = this.textarea;
|
||||
const start = textarea.selectionStart ?? textarea.value.length;
|
||||
const end = textarea.selectionEnd ?? textarea.value.length;
|
||||
const value = textarea.value || '';
|
||||
const emojiText = typeof emoji === 'string' ? emoji : emoji.emoji || emoji.unicode || emoji;
|
||||
textarea.value = `${value.slice(0, start)}${emojiText}${value.slice(end)}`;
|
||||
const caret = start + emojiText.length;
|
||||
textarea.focus();
|
||||
textarea.setSelectionRange?.(caret, caret);
|
||||
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
|
||||
handleFileSelect(event) {
|
||||
const files = Array.from(event.target.files || []);
|
||||
if (!files.length) return;
|
||||
|
||||
@ -27,6 +27,11 @@ import {
|
||||
* @param {Object} [options.markReadApi] - API из chatMarkRead для интеграции
|
||||
* @returns {Object|null} API WebSocket или null если параметры невалидны
|
||||
*/
|
||||
|
||||
// Храним активное соединение для предотвращения дубликатов
|
||||
let activeWebSocket = null;
|
||||
let lastOptions = null;
|
||||
|
||||
export function initChatWebSocket(options = {}) {
|
||||
const {
|
||||
chatId,
|
||||
@ -47,6 +52,15 @@ export function initChatWebSocket(options = {}) {
|
||||
console.warn('ChatWebSocket: chatId and meId are required');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Закрываем существующее соединение перед созданием нового
|
||||
if (activeWebSocket && activeWebSocket.readyState !== WebSocket.CLOSED) {
|
||||
console.log('ChatWebSocket: closing existing connection');
|
||||
activeWebSocket.close(1000, 'Reconnecting');
|
||||
}
|
||||
|
||||
// Сохраняем опции для переподключения
|
||||
lastOptions = options;
|
||||
|
||||
// Получение элементов
|
||||
const scrollEl = document.getElementById(scrollContainerId);
|
||||
@ -66,6 +80,14 @@ export function initChatWebSocket(options = {}) {
|
||||
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const wsUrl = `${proto}://${location.host}/ws/chat/${chatId}/`;
|
||||
const ws = new WebSocket(wsUrl);
|
||||
|
||||
// Сохраняем активное соединение
|
||||
activeWebSocket = ws;
|
||||
|
||||
// Настройки переподключения
|
||||
let reconnectAttempts = 0;
|
||||
const maxReconnectAttempts = 5;
|
||||
const reconnectDelay = 3000;
|
||||
|
||||
/**
|
||||
* Инициализирует последний день из DOM
|
||||
@ -165,6 +187,14 @@ export function initChatWebSocket(options = {}) {
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обрабатывает успешное подключение
|
||||
*/
|
||||
ws.addEventListener('open', () => {
|
||||
console.log('ChatWebSocket: connection established');
|
||||
reconnectAttempts = 0; // Сбрасываем счетчик при успешном подключении
|
||||
});
|
||||
|
||||
/**
|
||||
* Обрабатывает входящие сообщения WebSocket
|
||||
*/
|
||||
@ -189,9 +219,40 @@ export function initChatWebSocket(options = {}) {
|
||||
/**
|
||||
* Обрабатывает закрытие соединения
|
||||
*/
|
||||
ws.addEventListener('close', () => {
|
||||
console.log('ChatWebSocket: connection closed, reloading in 2s...');
|
||||
setTimeout(() => location.reload(), 2000);
|
||||
ws.addEventListener('close', (event) => {
|
||||
console.log('ChatWebSocket: connection closed', event.code, event.reason);
|
||||
|
||||
// Не переподключаемся если закрытие было нормальным (код 1000)
|
||||
if (event.code === 1000) {
|
||||
console.log('ChatWebSocket: normal closure, not reconnecting');
|
||||
return;
|
||||
}
|
||||
|
||||
// Пробуем переподключиться
|
||||
if (reconnectAttempts < maxReconnectAttempts) {
|
||||
reconnectAttempts++;
|
||||
console.log(`ChatWebSocket: reconnecting in ${reconnectDelay}ms (attempt ${reconnectAttempts}/${maxReconnectAttempts})`);
|
||||
|
||||
setTimeout(() => {
|
||||
if (document.querySelector('[data-chat-ws]')) {
|
||||
console.log('ChatWebSocket: attempting to reconnect...');
|
||||
// Переинициализируем WebSocket с сохраненными опциями
|
||||
initChatWebSocket(lastOptions);
|
||||
}
|
||||
}, reconnectDelay);
|
||||
} else {
|
||||
console.warn('ChatWebSocket: max reconnect attempts reached');
|
||||
// Показываем уведомление пользователю вместо перезагрузки
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'alert alert-warning position-fixed bottom-0 start-50 translate-middle-x mb-3';
|
||||
notification.style.zIndex = '9999';
|
||||
notification.innerHTML = `
|
||||
<i class="bi-exclamation-triangle me-2"></i>
|
||||
Соединение с сервером потеряно.
|
||||
<button class="btn btn-sm btn-link" onclick="location.reload()">Обновить страницу</button>
|
||||
`;
|
||||
document.body.appendChild(notification);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
@ -199,6 +260,7 @@ export function initChatWebSocket(options = {}) {
|
||||
*/
|
||||
ws.addEventListener('error', (err) => {
|
||||
console.error('ChatWebSocket: connection error', err);
|
||||
// Не перезагружаем страницу при ошибке - дожидаемся события 'close'
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
343
backend/static/js/data/emoji-ru.js
Normal file
343
backend/static/js/data/emoji-ru.js
Normal file
@ -0,0 +1,343 @@
|
||||
/**
|
||||
* Русские ключевые слова для поиска эмодзи
|
||||
* Формат: emoji -> массив русских синонимов
|
||||
*/
|
||||
export const emojiRussianKeywords = {
|
||||
// Смайлики и эмоции
|
||||
'😀': ['улыбка', 'радость', 'счастье', 'смайлик'],
|
||||
'😃': ['улыбка', 'радость', 'смайлик'],
|
||||
'😄': ['смех', 'улыбка', 'радость'],
|
||||
'😁': ['ухмылка', 'улыбка', 'зубы'],
|
||||
'😆': ['смех', 'хохот', 'веселье'],
|
||||
'😅': ['пот', 'облегчение', 'смех'],
|
||||
'🤣': ['смех', 'хохот', 'ржу'],
|
||||
'😂': ['слезы', 'смех', 'плакать от смеха'],
|
||||
'🙂': ['улыбка', 'легкая улыбка'],
|
||||
'🙃': ['перевернутый', 'сарказм'],
|
||||
'😉': ['подмигивание', 'намек'],
|
||||
'😊': ['застенчивый', 'милый', 'улыбка'],
|
||||
'😇': ['ангел', 'святой', 'невинный'],
|
||||
'🥰': ['любовь', 'влюблен', 'сердечки'],
|
||||
'😍': ['любовь', 'влюблен', 'сердце в глазах'],
|
||||
'🤩': ['звезды', 'восторг', 'вау'],
|
||||
'😘': ['поцелуй', 'воздушный поцелуй'],
|
||||
'😗': ['поцелуй', 'губы'],
|
||||
'😚': ['поцелуй', 'закрытые глаза'],
|
||||
'😙': ['поцелуй', 'улыбка'],
|
||||
'🥲': ['слеза', 'трогательно', 'счастье со слезой'],
|
||||
'😋': ['вкусно', 'язык', 'ням'],
|
||||
'😛': ['язык', 'дразнить'],
|
||||
'😜': ['подмигивание', 'язык', 'шутка'],
|
||||
'🤪': ['безумный', 'сумасшедший', 'веселье'],
|
||||
'😝': ['язык', 'закрытые глаза'],
|
||||
'🤑': ['деньги', 'богатство', 'жадность'],
|
||||
'🤗': ['объятие', 'обнять'],
|
||||
'🤭': ['ой', 'упс', 'смущение'],
|
||||
'🤫': ['тсс', 'тихо', 'секрет'],
|
||||
'🤔': ['думать', 'размышление', 'хм'],
|
||||
'🤐': ['молчание', 'замок на губах'],
|
||||
'🤨': ['подозрение', 'скептицизм', 'бровь'],
|
||||
'😐': ['нейтральный', 'без эмоций'],
|
||||
'😑': ['без эмоций', 'недовольство'],
|
||||
'😶': ['без рта', 'молчание'],
|
||||
'😏': ['ухмылка', 'самодовольство'],
|
||||
'😒': ['недовольство', 'скука'],
|
||||
'🙄': ['закатить глаза', 'раздражение'],
|
||||
'😬': ['неловко', 'смущение', 'зубы'],
|
||||
'😮': ['удивление', 'шок', 'о'],
|
||||
'😯': ['удивление', 'ошеломление'],
|
||||
'😲': ['шок', 'удивление'],
|
||||
'😳': ['смущение', 'покраснение'],
|
||||
'🥺': ['умоляющий', 'жалость', 'щенячьи глаза'],
|
||||
'😦': ['нахмуренный', 'озабоченность'],
|
||||
'😧': ['тревога', 'страх'],
|
||||
'😨': ['испуг', 'страх'],
|
||||
'😰': ['тревога', 'пот', 'нервы'],
|
||||
'😥': ['грусть', 'облегчение', 'пот'],
|
||||
'😢': ['слезы', 'плач', 'грусть'],
|
||||
'😭': ['рыдание', 'плач', 'слезы'],
|
||||
'😱': ['крик', 'ужас', 'страх'],
|
||||
'😖': ['смущение', 'разочарование'],
|
||||
'😣': ['упорство', 'настойчивость'],
|
||||
'😞': ['разочарование', 'грусть'],
|
||||
'😓': ['усталость', 'пот', 'стресс'],
|
||||
'😩': ['усталость', 'стон'],
|
||||
'😫': ['устал', 'измучен'],
|
||||
'🥱': ['зевок', 'усталость', 'скука'],
|
||||
'😤': ['триумф', 'гордость', 'пар из носа'],
|
||||
'😡': ['злость', 'гнев'],
|
||||
'😠': ['злой', 'гневный'],
|
||||
'🤬': ['ругань', 'мат', 'злость'],
|
||||
'😈': ['дьявол', 'зло', 'рога'],
|
||||
'👿': ['дьявол', 'злой', 'рога'],
|
||||
'💀': ['череп', 'смерть', 'умираю'],
|
||||
'☠️': ['череп', 'яд', 'опасность'],
|
||||
|
||||
// Жесты
|
||||
'👋': ['привет', 'пока', 'махать рукой'],
|
||||
'🤚': ['рука', 'стоп'],
|
||||
'🖐️': ['рука', 'пять', 'ладонь'],
|
||||
'✋': ['стоп', 'рука', 'ладонь'],
|
||||
'🖖': ['вулканский привет', 'спок'],
|
||||
'👌': ['ок', 'окей', 'отлично'],
|
||||
'🤌': ['жест', 'итальянский'],
|
||||
'🤏': ['маленький', 'чуть-чуть'],
|
||||
'✌️': ['победа', 'пис', 'два'],
|
||||
'🤞': ['скрестить пальцы', 'удача'],
|
||||
'🤟': ['люблю тебя', 'рок'],
|
||||
'🤘': ['рок', 'рога'],
|
||||
'🤙': ['позвони', 'телефон'],
|
||||
'👈': ['влево', 'указать'],
|
||||
'👉': ['вправо', 'указать'],
|
||||
'👆': ['вверх', 'указать'],
|
||||
'👇': ['вниз', 'указать'],
|
||||
'☝️': ['вверх', 'один'],
|
||||
'👍': ['лайк', 'класс', 'отлично'],
|
||||
'👎': ['дизлайк', 'плохо'],
|
||||
'✊': ['кулак', 'сила'],
|
||||
'👊': ['кулак', 'удар'],
|
||||
'🤛': ['кулак', 'удар слева'],
|
||||
'🤜': ['кулак', 'удар справа'],
|
||||
'👏': ['аплодисменты', 'хлопать'],
|
||||
'🙌': ['руки вверх', 'ура', 'праздник'],
|
||||
'👐': ['открытые руки', 'объятие'],
|
||||
'🤲': ['молитва', 'просьба'],
|
||||
'🤝': ['рукопожатие', 'согласие', 'сделка'],
|
||||
'🙏': ['молитва', 'спасибо', 'пожалуйста'],
|
||||
|
||||
// Сердца и любовь
|
||||
'💘': ['любовь', 'сердце', 'стрела'],
|
||||
'💝': ['подарок', 'сердце'],
|
||||
'💖': ['блестящее сердце', 'любовь'],
|
||||
'💗': ['растущее сердце', 'любовь'],
|
||||
'💓': ['бьющееся сердце', 'любовь'],
|
||||
'💞': ['вращающиеся сердца', 'любовь'],
|
||||
'💕': ['два сердца', 'любовь'],
|
||||
'💟': ['сердце', 'украшение'],
|
||||
'❣️': ['восклицание', 'сердце'],
|
||||
'💔': ['разбитое сердце', 'грусть'],
|
||||
'❤️': ['красное сердце', 'любовь'],
|
||||
'🧡': ['оранжевое сердце'],
|
||||
'💛': ['желтое сердце'],
|
||||
'💚': ['зеленое сердце'],
|
||||
'💙': ['синее сердце'],
|
||||
'💜': ['фиолетовое сердце'],
|
||||
'🤎': ['коричневое сердце'],
|
||||
'🖤': ['черное сердце'],
|
||||
'🤍': ['белое сердце'],
|
||||
|
||||
// Животные
|
||||
'🐶': ['собака', 'пес', 'щенок'],
|
||||
'🐱': ['кот', 'кошка', 'котенок'],
|
||||
'🐭': ['мышь', 'мышка'],
|
||||
'🐹': ['хомяк'],
|
||||
'🐰': ['кролик', 'зайчик'],
|
||||
'🦊': ['лиса', 'лисица'],
|
||||
'🐻': ['медведь', 'мишка'],
|
||||
'🐼': ['панда'],
|
||||
'🐨': ['коала'],
|
||||
'🐯': ['тигр'],
|
||||
'🦁': ['лев'],
|
||||
'🐮': ['корова'],
|
||||
'🐷': ['свинья', 'хрюшка'],
|
||||
'🐸': ['лягушка'],
|
||||
'🐵': ['обезьяна'],
|
||||
'🐔': ['курица'],
|
||||
'🐧': ['пингвин'],
|
||||
'🐦': ['птица'],
|
||||
'🐤': ['цыпленок'],
|
||||
'🦆': ['утка'],
|
||||
'🦅': ['орел'],
|
||||
'🦉': ['сова'],
|
||||
'🦇': ['летучая мышь'],
|
||||
'🐺': ['волк'],
|
||||
'🐗': ['кабан'],
|
||||
'🐴': ['лошадь', 'конь'],
|
||||
'🦄': ['единорог'],
|
||||
'🐝': ['пчела'],
|
||||
'🐛': ['гусеница', 'червяк'],
|
||||
'🦋': ['бабочка'],
|
||||
'🐌': ['улитка'],
|
||||
'🐞': ['божья коровка'],
|
||||
'🐢': ['черепаха'],
|
||||
'🐍': ['змея'],
|
||||
'🦎': ['ящерица'],
|
||||
'🐙': ['осьминог'],
|
||||
'🦀': ['краб'],
|
||||
'🐡': ['рыба', 'фугу'],
|
||||
'🐠': ['тропическая рыба'],
|
||||
'🐟': ['рыба'],
|
||||
'🐬': ['дельфин'],
|
||||
'🐳': ['кит'],
|
||||
'🦈': ['акула'],
|
||||
|
||||
// Еда
|
||||
'🍎': ['яблоко'],
|
||||
'🍊': ['апельсин'],
|
||||
'🍋': ['лимон'],
|
||||
'🍌': ['банан'],
|
||||
'🍉': ['арбуз'],
|
||||
'🍇': ['виноград'],
|
||||
'🍓': ['клубника'],
|
||||
'🍑': ['персик'],
|
||||
'🍒': ['вишня'],
|
||||
'🍍': ['ананас'],
|
||||
'🥝': ['киви'],
|
||||
'🥑': ['авокадо'],
|
||||
'🍅': ['помидор', 'томат'],
|
||||
'🍆': ['баклажан'],
|
||||
'🥒': ['огурец'],
|
||||
'🥕': ['морковь'],
|
||||
'🌽': ['кукуруза'],
|
||||
'🌶️': ['перец', 'острый'],
|
||||
'🥔': ['картофель', 'картошка'],
|
||||
'🍠': ['батат', 'сладкий картофель'],
|
||||
'🥐': ['круассан'],
|
||||
'🥖': ['багет', 'хлеб'],
|
||||
'🥨': ['крендель'],
|
||||
'🥞': ['блины'],
|
||||
'🧀': ['сыр'],
|
||||
'🍖': ['мясо', 'кость'],
|
||||
'🍗': ['курица', 'ножка'],
|
||||
'🥩': ['стейк', 'мясо'],
|
||||
'🥓': ['бекон'],
|
||||
'🍔': ['бургер', 'гамбургер'],
|
||||
'🍟': ['картофель фри', 'фри'],
|
||||
'🍕': ['пицца'],
|
||||
'🌭': ['хот-дог'],
|
||||
'🥪': ['сэндвич', 'бутерброд'],
|
||||
'🌮': ['тако'],
|
||||
'🌯': ['буррито'],
|
||||
'🥙': ['лаваш', 'шаурма'],
|
||||
'🥚': ['яйцо'],
|
||||
'🍳': ['яичница'],
|
||||
'🥘': ['паэлья', 'сковорода'],
|
||||
'🍲': ['суп', 'рагу'],
|
||||
'🥣': ['каша', 'миска'],
|
||||
'🥗': ['салат'],
|
||||
'🍿': ['попкорн'],
|
||||
'🧈': ['масло'],
|
||||
'🧂': ['соль'],
|
||||
'🥫': ['консервы'],
|
||||
'🍱': ['бенто', 'ланч'],
|
||||
'🍘': ['рисовый крекер'],
|
||||
'🍙': ['онигири'],
|
||||
'🍚': ['рис'],
|
||||
'🍛': ['карри'],
|
||||
'🍜': ['лапша', 'рамен'],
|
||||
'🍝': ['спагетти', 'паста'],
|
||||
'🍠': ['батат'],
|
||||
'🍢': ['оден'],
|
||||
'🍣': ['суши'],
|
||||
'🍤': ['креветка жареная'],
|
||||
'🍥': ['камабоко'],
|
||||
'🥮': ['лунный пряник'],
|
||||
'🍡': ['данго'],
|
||||
'🥟': ['пельмени'],
|
||||
'🥠': ['печенье с предсказанием'],
|
||||
'🥡': ['коробка для еды'],
|
||||
'🦀': ['краб'],
|
||||
'🦞': ['лобстер'],
|
||||
'🦐': ['креветка'],
|
||||
'🦑': ['кальмар'],
|
||||
'🦪': ['устрица'],
|
||||
'🍦': ['мороженое'],
|
||||
'🍧': ['фруктовый лед'],
|
||||
'🍨': ['мороженое'],
|
||||
'🍩': ['пончик'],
|
||||
'🍪': ['печенье'],
|
||||
'🎂': ['торт', 'день рождения'],
|
||||
'🍰': ['кусок торта'],
|
||||
'🧁': ['кекс'],
|
||||
'🥧': ['пирог'],
|
||||
'🍫': ['шоколад'],
|
||||
'🍬': ['конфета'],
|
||||
'🍭': ['леденец'],
|
||||
'🍮': ['пудинг'],
|
||||
'🍯': ['мед'],
|
||||
'🍼': ['детская бутылочка'],
|
||||
'🥛': ['молоко'],
|
||||
'☕': ['кофе', 'чай'],
|
||||
'🫖': ['чайник'],
|
||||
'🍵': ['зеленый чай'],
|
||||
'🍶': ['саке'],
|
||||
'🍾': ['шампанское'],
|
||||
'🍷': ['вино'],
|
||||
'🍸': ['коктейль'],
|
||||
'🍹': ['тропический напиток'],
|
||||
'🍺': ['пиво'],
|
||||
'🍻': ['пиво', 'чирс'],
|
||||
'🥂': ['бокалы', 'тост'],
|
||||
'🥃': ['виски', 'стакан'],
|
||||
'🥤': ['напиток', 'стакан'],
|
||||
'🧃': ['сок', 'коробка'],
|
||||
'🧉': ['мате'],
|
||||
'🧊': ['лед'],
|
||||
|
||||
// Праздники и объекты
|
||||
'🎉': ['праздник', 'вечеринка', 'конфетти'],
|
||||
'🎊': ['конфетти', 'праздник'],
|
||||
'🎈': ['шарик', 'воздушный шар'],
|
||||
'🎁': ['подарок', 'сюрприз'],
|
||||
'🎀': ['бант', 'лента'],
|
||||
'🎂': ['торт', 'день рождения'],
|
||||
'🎄': ['елка', 'новый год', 'рождество'],
|
||||
'🎅': ['санта', 'дед мороз'],
|
||||
'🎃': ['тыква', 'хэллоуин'],
|
||||
'👻': ['привидение', 'призрак'],
|
||||
'🎆': ['фейерверк', 'салют'],
|
||||
'🎇': ['бенгальский огонь'],
|
||||
'✨': ['искры', 'блеск', 'магия'],
|
||||
'🎈': ['шарик'],
|
||||
'🎉': ['конфетти', 'праздник'],
|
||||
|
||||
// Символы
|
||||
'❤️': ['сердце', 'любовь'],
|
||||
'💯': ['сто', 'процентов', 'отлично'],
|
||||
'💢': ['злость', 'гнев'],
|
||||
'💥': ['взрыв', 'бум'],
|
||||
'💫': ['головокружение'],
|
||||
'💦': ['капли', 'пот'],
|
||||
'💨': ['ветер', 'быстро'],
|
||||
'🔥': ['огонь', 'пламя', 'жар'],
|
||||
'⭐': ['звезда'],
|
||||
'🌟': ['сияющая звезда'],
|
||||
'✨': ['искры', 'блеск'],
|
||||
'⚡': ['молния', 'электричество'],
|
||||
'☀️': ['солнце'],
|
||||
'🌙': ['луна', 'месяц'],
|
||||
'☁️': ['облако'],
|
||||
'⛅': ['солнце за облаком'],
|
||||
'⛈️': ['гроза'],
|
||||
'🌧️': ['дождь'],
|
||||
'⛄': ['снеговик'],
|
||||
'❄️': ['снежинка', 'снег'],
|
||||
'🌈': ['радуга'],
|
||||
'☂️': ['зонт'],
|
||||
|
||||
// Флаги
|
||||
'🏁': ['финиш', 'флаг'],
|
||||
'🚩': ['красный флаг'],
|
||||
'🏴': ['черный флаг'],
|
||||
'🏳️': ['белый флаг'],
|
||||
'🏳️🌈': ['радужный флаг', 'прайд', 'лгбт'],
|
||||
'🇷🇺': ['россия', 'российский флаг'],
|
||||
};
|
||||
|
||||
/**
|
||||
* Функция для поиска эмодзи по русскому запросу
|
||||
*/
|
||||
export function searchEmojiRussian(query) {
|
||||
if (!query) return [];
|
||||
|
||||
const lowerQuery = query.toLowerCase().trim();
|
||||
const results = [];
|
||||
|
||||
for (const [emoji, keywords] of Object.entries(emojiRussianKeywords)) {
|
||||
if (keywords.some(keyword => keyword.includes(lowerQuery))) {
|
||||
results.push(emoji);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
@ -150,10 +150,18 @@ main.main {
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
.chat-composer .btn-emoji {
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
.chat-composer .btn-send {
|
||||
box-shadow: 0 4px 16px rgba(var(--bs-primary-rgb), 0.35);
|
||||
}
|
||||
|
||||
.chat-composer .message-emoji .dropdown-menu {
|
||||
background: var(--bs-body-bg);
|
||||
}
|
||||
|
||||
/* Превью вложений */
|
||||
.attachment-preview {
|
||||
padding: var(--space-1) 0;
|
||||
@ -331,10 +339,51 @@ main.main {
|
||||
bottom: 4.25rem;
|
||||
z-index: 2;
|
||||
display: none;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50% !important;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
background: var(--bs-primary) !important;
|
||||
color: #fff !important;
|
||||
box-shadow: 0 4px 16px rgba(var(--bs-primary-rgb), 0.25) !important;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
i {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.08);
|
||||
box-shadow: 0 6px 20px rgba(var(--bs-primary-rgb), 0.35) !important;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.scroll-to-bottom.show {
|
||||
display: block;
|
||||
display: inline-flex;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
animation: fadeInUp 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────────
|
||||
|
||||
@ -3,12 +3,12 @@
|
||||
* Унифицированные стили для полей ввода сообщений и комментариев
|
||||
*/
|
||||
|
||||
$message-bg-default: color-mix(in srgb, var(--bs-body-bg) 95%, var(--bs-secondary-bg) 5%);
|
||||
$message-bg-focused: color-mix(in srgb, var(--bs-body-bg) 85%, var(--bs-secondary-bg) 15%);
|
||||
$message-bg-default: transparent;
|
||||
$message-bg-focused: transparent;
|
||||
|
||||
.message-field {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
width: 100%;
|
||||
padding: 0.35rem 0.4rem;
|
||||
@ -16,6 +16,7 @@ $message-bg-focused: color-mix(in srgb, var(--bs-body-bg) 85%, var(--bs-secondar
|
||||
border-radius: 999px;
|
||||
background: $message-bg-default;
|
||||
transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
|
||||
--message-action-size: 42px;
|
||||
}
|
||||
|
||||
.message-field:focus-within {
|
||||
@ -35,6 +36,7 @@ $message-bg-focused: color-mix(in srgb, var(--bs-body-bg) 85%, var(--bs-secondar
|
||||
.message-field--compact {
|
||||
padding: 0.25rem 0.3rem;
|
||||
border-radius: 24px;
|
||||
--message-action-size: 36px;
|
||||
}
|
||||
|
||||
.message-input,
|
||||
@ -42,14 +44,16 @@ $message-bg-focused: color-mix(in srgb, var(--bs-body-bg) 85%, var(--bs-secondar
|
||||
flex: 1 1 auto;
|
||||
min-height: 44px;
|
||||
max-height: 260px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
resize: none;
|
||||
padding: 0.5rem 0.75rem;
|
||||
line-height: 1.4;
|
||||
font-size: 1rem;
|
||||
color: inherit;
|
||||
scrollbar-width: none;
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.message-field--compact .message-input {
|
||||
@ -70,32 +74,36 @@ $message-bg-focused: color-mix(in srgb, var(--bs-body-bg) 85%, var(--bs-secondar
|
||||
.message-input::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.message-icon-btn,
|
||||
.btn-attachment,
|
||||
.btn-send,
|
||||
.message-send {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 999px;
|
||||
display: inline-flex;
|
||||
.message-send,
|
||||
.btn-emoji {
|
||||
width: var(--message-action-size, 42px) !important;
|
||||
min-width: var(--message-action-size, 42px) !important;
|
||||
height: var(--message-action-size, 42px) !important;
|
||||
border-radius: 50% !important;
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
border: none;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
background: transparent;
|
||||
color: var(--bs-secondary-color);
|
||||
transition: background-color 0.15s ease, color 0.15s ease, transform 0.15s ease;
|
||||
}
|
||||
|
||||
.message-field--compact .message-icon-btn,
|
||||
.message-field--compact .message-send {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
aspect-ratio: 1 / 1;
|
||||
flex: 0 0 auto;
|
||||
line-height: 1;
|
||||
font-size: 1.05rem;
|
||||
--bs-btn-padding-x: 0 !important;
|
||||
--bs-btn-padding-y: 0 !important;
|
||||
--bs-btn-border-radius: 50% !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.message-icon-btn:hover,
|
||||
.btn-attachment:hover {
|
||||
.btn-attachment:hover,
|
||||
.btn-emoji:hover {
|
||||
background: var(--bs-secondary-bg);
|
||||
color: var(--bs-body-color);
|
||||
transform: scale(1.02);
|
||||
@ -130,6 +138,38 @@ $message-bg-focused: color-mix(in srgb, var(--bs-body-bg) 85%, var(--bs-secondar
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.message-emoji .dropdown-menu {
|
||||
min-width: 260px;
|
||||
padding: 0;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-emoji-picker {
|
||||
width: min(340px, 90vw);
|
||||
max-width: 360px;
|
||||
height: 340px;
|
||||
border: none;
|
||||
outline: none;
|
||||
--background-color: var(--bs-body-bg);
|
||||
--text-color: var(--bs-body-color);
|
||||
--category-font-color: var(--bs-secondary-color);
|
||||
--hover-color: var(--bs-secondary-bg);
|
||||
--button-active-background: var(--bs-primary-bg-subtle, var(--bs-primary));
|
||||
--button-hover-background: var(--bs-secondary-bg);
|
||||
--focus-indicator: var(--bs-primary);
|
||||
--border-color: color-mix(in srgb, var(--bs-border-color), transparent 25%);
|
||||
--input-border-color: color-mix(in srgb, var(--bs-border-color), transparent 30%);
|
||||
--input-placeholder-color: var(--bs-secondary-color);
|
||||
--input-focus-border-color: var(--bs-primary);
|
||||
--indicator-color: var(--bs-primary);
|
||||
|
||||
// Скрываем кнопку выбора тона кожи
|
||||
button.favorites {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.message-field--solo {
|
||||
display: block;
|
||||
border-radius: 24px;
|
||||
@ -148,11 +188,10 @@ $message-bg-focused: color-mix(in srgb, var(--bs-body-bg) 85%, var(--bs-secondar
|
||||
border-radius: 28px;
|
||||
}
|
||||
|
||||
.message-send,
|
||||
.btn-send,
|
||||
.message-icon-btn,
|
||||
.btn-attachment {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
.message-field {
|
||||
--message-action-size: 38px;
|
||||
}
|
||||
.message-field--compact {
|
||||
--message-action-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,8 +42,8 @@
|
||||
<article class="feed-card chat-box">
|
||||
|
||||
<!-- Кнопка «вниз» -->
|
||||
<button id="scrollBtn" class="btn btn-light shadow scroll-to-bottom" type="button" aria-label="Прокрутить вниз">
|
||||
<i class="bi-arrow-down-circle"></i>
|
||||
<button id="scrollBtn" class="scroll-to-bottom" type="button" aria-label="Прокрутить вниз">
|
||||
<i class="bi-arrow-down"></i>
|
||||
</button>
|
||||
|
||||
<!-- Лента сообщений -->
|
||||
@ -175,7 +175,7 @@
|
||||
<div class="message-field">
|
||||
<!-- Кнопка вложений с выпадающим меню -->
|
||||
<div class="dropdown message-attach">
|
||||
<button type="button" class="btn btn-ghost btn-attachment"
|
||||
<button type="button" class="btn btn-ghost btn-attachment message-icon-btn"
|
||||
id="attachmentDropdown"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
@ -210,6 +210,21 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Кнопка выбора эмодзи -->
|
||||
<div class="dropdown message-emoji">
|
||||
<button type="button" class="btn btn-ghost btn-emoji message-icon-btn"
|
||||
id="emojiDropdown"
|
||||
data-bs-toggle="dropdown"
|
||||
data-emoji-toggle="1"
|
||||
aria-expanded="false"
|
||||
title="Вставить смайлик">
|
||||
<i class="bi-emoji-smile"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-start message-emoji-menu" aria-labelledby="emojiDropdown">
|
||||
<emoji-picker data-emoji-picker class="chat-emoji-picker"></emoji-picker>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Поле ввода текста -->
|
||||
<textarea name="content"
|
||||
id="id_content"
|
||||
@ -243,6 +258,7 @@
|
||||
|
||||
{% block extra_js %}
|
||||
{{ block.super }}
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@^1/index.js"></script>
|
||||
<script type="module" src="{% static 'js/components/chatComposer.js' %}"></script>
|
||||
<script type="module" src="{% static 'js/components/chatHistoryLoader.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user