diff --git a/shine-UI/js/app.js b/shine-UI/js/app.js index 5883c89e..27f95d22 100644 --- a/shine-UI/js/app.js +++ b/shine-UI/js/app.js @@ -779,6 +779,12 @@ window.addEventListener('error', (event) => { }, }); + // Кросс-ориджин ошибки приходят как «Script error.» без файла/строки/стека — + // алертом такие не показываем (в лог и captureClientError выше уже записаны). + const isOpaqueCrossOriginError = String(event.message || '') === 'Script error.' + && !event.filename && !(event.error?.stack); + if (isOpaqueCrossOriginError) return; + showGlobalErrorAlert('Поймана глобальная ошибка UI', { message: event.message || 'Global JS error', stack: event.error?.stack || '', diff --git a/shine-UI/js/components/emoji-picker.js b/shine-UI/js/components/emoji-picker.js index 0eadeb07..f91f1c2c 100644 --- a/shine-UI/js/components/emoji-picker.js +++ b/shine-UI/js/components/emoji-picker.js @@ -40,12 +40,25 @@ function saveRecent(items) { function appendAnimatedAsset(root, item) { const previewUrl = item.previewUrl || getTelegramEmojiPreviewUrl(item.file); const animatedUrl = item.animatedUrl || getTelegramEmojiAssetUrl(item.file); + const hasAnimation = animatedUrl && animatedUrl !== previewUrl; const image = document.createElement('img'); image.className = 'telegram-emoji-image'; image.alt = ''; image.decoding = 'async'; image.setAttribute('aria-hidden', 'true'); + // Без отдельного анимационного ассета — просто статичная картинка, + // без hover/tap-подмены src и pointer capture (лишние запросы и «моргание»). + if (!hasAnimation) { + image.src = previewUrl; + image.onerror = () => { + root.classList.add('is-error'); + image.removeAttribute('src'); + }; + root.append(image); + return; + } + let isActive = false; let sourceVersion = 0; diff --git a/shine-UI/js/components/telegram-emoji-catalog.js b/shine-UI/js/components/telegram-emoji-catalog.js index 88232171..69e6bc64 100644 --- a/shine-UI/js/components/telegram-emoji-catalog.js +++ b/shine-UI/js/components/telegram-emoji-catalog.js @@ -8,7 +8,6 @@ import { TELEGRAM_EMOJI_GROUP_SMILEYS } from './telegram-emoji-smileys.js?v=2026 import { TELEGRAM_EMOJI_GROUP_SYMBOLS } from './telegram-emoji-symbols.js?v=202607151545'; import { TELEGRAM_EMOJI_GROUP_TRAVEL_PLACES } from './telegram-emoji-travel_places.js?v=202607151545'; -const TELEGRAM_EMOJI_ASSET_BASE_URL = 'https://raw.githubusercontent.com/Tarikul-Islam-Anik/Telegram-Animated-Emojis/main/'; const TELEGRAM_EMOJI_PREVIEW_BASE_URL = '/assets/emoji/telegram-previews/'; function getTelegramEmojiName(file) { @@ -40,11 +39,13 @@ export const TELEGRAM_EMOJI_GROUPS = [ ].map(withPreviewUrls); export function getTelegramEmojiAssetUrl(file) { - return `${TELEGRAM_EMOJI_ASSET_BASE_URL}${file.split('/').map((part) => encodeURIComponent(part)).join('/')}`; + // Анимированные webp с raw.githubusercontent.com отключены: внешний хост + // блокируется у части пользователей (VPN/операторы) — тап по эмодзи вызывал + // сетевую ошибку и «моргание» картинки. Пока отдаём локальное статичное превью; + // возврат анимаций — только локальным паком в assets. + return getTelegramEmojiPreviewUrl(file); } export function getTelegramEmojiPreviewUrl(file) { return `${TELEGRAM_EMOJI_PREVIEW_BASE_URL}${String(file || '').replace(/\.webp$/i, '.png').split('/').map((part) => encodeURIComponent(part)).join('/')}`; } - -export { TELEGRAM_EMOJI_ASSET_BASE_URL }; diff --git a/shine-UI/js/pages/chat-view.js b/shine-UI/js/pages/chat-view.js index bdbd3417..25f90ad0 100644 --- a/shine-UI/js/pages/chat-view.js +++ b/shine-UI/js/pages/chat-view.js @@ -914,6 +914,10 @@ export function render({ navigate, route }) { emojiToggle?.setAttribute('aria-expanded', 'false'); }; + // На сенсорных устройствах фокус в textarea поднимает клавиатуру поверх пикера — + // поэтому при вставке эмодзи фокусируем поле только там, где есть «точный» указатель. + const isFinePointer = window.matchMedia?.('(hover: hover) and (pointer: fine)')?.matches ?? true; + const insertEmoji = (emoji) => { if (!input) return; const value = String(input.value || ''); @@ -922,11 +926,13 @@ export function render({ navigate, route }) { input.value = `${value.slice(0, start)}${emoji}${value.slice(end)}`; const nextPosition = start + emoji.length; emojiSelection = { start: nextPosition, end: nextPosition }; - input.focus(); - try { - input.setSelectionRange(nextPosition, nextPosition); - } catch { - // Ignore browsers that do not expose textarea selection during focus changes. + if (isFinePointer) { + input.focus(); + try { + input.setSelectionRange(nextPosition, nextPosition); + } catch { + // Ignore browsers that do not expose textarea selection during focus changes. + } } autoResizeComposer(input); }; @@ -1247,6 +1253,9 @@ export function render({ navigate, route }) { emojiPickerOpen = !emojiPickerOpen; emojiSlot.hidden = !emojiPickerOpen; emojiToggle.setAttribute('aria-expanded', String(emojiPickerOpen)); + // Как в Telegram: либо клавиатура, либо пикер. На тач-устройствах при + // открытии пикера прячем клавиатуру, чтобы поле ввода оставалось видно. + if (emojiPickerOpen && !isFinePointer) input?.blur(); }); screen.addEventListener('pointerdown', (event) => { if (!emojiPickerOpen || form.contains(event.target)) return;