SHA256
Эмодзи/мобильный чат: убрать загрузку с GitHub, клавиатура, тихий Script error
- Пикер: анимированные webp грузились с raw.githubusercontent.com (в репо их нет) — у пользователей с VPN/операторскими блокировками тап по эмодзи давал сетевую ошибку и «моргание»/подмену картинки. Теперь всегда локальные статичные превью, без внешних запросов и pointer capture; мёртвая константа GitHub-URL удалена. Возврат анимаций — только локальным паком. - Чат (тач): открытие эмодзи-пикера прячет клавиатуру (blur), вставка эмодзи не фокусирует textarea на сенсорных устройствах — поле больше не перекрывается клавиатурой (на десктопе фокус как раньше). - Глобальный обработчик ошибок: кросс-ориджин «Script error.» без файла/стека больше не показывается алертом (лог и captureClientError остаются) — устраняет пугающую плашку на Xiaomi/Safari. - VERSION: client 1.2.327. Проверено в превью: тап по эмодзи — 0 запросов к githubusercontent, 😈 вставляется ровно как 😈, картинка кнопки стабильна; фильтр алерта: Script error. — тихо, реальная ошибка — алерт показывается. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.326
|
client.version=1.2.327
|
||||||
server.version=1.2.297
|
server.version=1.2.297
|
||||||
|
|||||||
@@ -777,6 +777,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', {
|
showGlobalErrorAlert('Поймана глобальная ошибка UI', {
|
||||||
message: event.message || 'Global JS error',
|
message: event.message || 'Global JS error',
|
||||||
stack: event.error?.stack || '',
|
stack: event.error?.stack || '',
|
||||||
|
|||||||
@@ -40,12 +40,25 @@ function saveRecent(items) {
|
|||||||
function appendAnimatedAsset(root, item) {
|
function appendAnimatedAsset(root, item) {
|
||||||
const previewUrl = item.previewUrl || getTelegramEmojiPreviewUrl(item.file);
|
const previewUrl = item.previewUrl || getTelegramEmojiPreviewUrl(item.file);
|
||||||
const animatedUrl = item.animatedUrl || getTelegramEmojiAssetUrl(item.file);
|
const animatedUrl = item.animatedUrl || getTelegramEmojiAssetUrl(item.file);
|
||||||
|
const hasAnimation = animatedUrl && animatedUrl !== previewUrl;
|
||||||
const image = document.createElement('img');
|
const image = document.createElement('img');
|
||||||
image.className = 'telegram-emoji-image';
|
image.className = 'telegram-emoji-image';
|
||||||
image.alt = '';
|
image.alt = '';
|
||||||
image.decoding = 'async';
|
image.decoding = 'async';
|
||||||
image.setAttribute('aria-hidden', 'true');
|
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 isActive = false;
|
||||||
let sourceVersion = 0;
|
let sourceVersion = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -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_SYMBOLS } from './telegram-emoji-symbols.js?v=202607151545';
|
||||||
import { TELEGRAM_EMOJI_GROUP_TRAVEL_PLACES } from './telegram-emoji-travel_places.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/';
|
const TELEGRAM_EMOJI_PREVIEW_BASE_URL = '/assets/emoji/telegram-previews/';
|
||||||
|
|
||||||
function getTelegramEmojiName(file) {
|
function getTelegramEmojiName(file) {
|
||||||
@@ -40,11 +39,13 @@ export const TELEGRAM_EMOJI_GROUPS = [
|
|||||||
].map(withPreviewUrls);
|
].map(withPreviewUrls);
|
||||||
|
|
||||||
export function getTelegramEmojiAssetUrl(file) {
|
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) {
|
export function getTelegramEmojiPreviewUrl(file) {
|
||||||
return `${TELEGRAM_EMOJI_PREVIEW_BASE_URL}${String(file || '').replace(/\.webp$/i, '.png').split('/').map((part) => encodeURIComponent(part)).join('/')}`;
|
return `${TELEGRAM_EMOJI_PREVIEW_BASE_URL}${String(file || '').replace(/\.webp$/i, '.png').split('/').map((part) => encodeURIComponent(part)).join('/')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { TELEGRAM_EMOJI_ASSET_BASE_URL };
|
|
||||||
|
|||||||
@@ -914,6 +914,10 @@ export function render({ navigate, route }) {
|
|||||||
emojiToggle?.setAttribute('aria-expanded', 'false');
|
emojiToggle?.setAttribute('aria-expanded', 'false');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// На сенсорных устройствах фокус в textarea поднимает клавиатуру поверх пикера —
|
||||||
|
// поэтому при вставке эмодзи фокусируем поле только там, где есть «точный» указатель.
|
||||||
|
const isFinePointer = window.matchMedia?.('(hover: hover) and (pointer: fine)')?.matches ?? true;
|
||||||
|
|
||||||
const insertEmoji = (emoji) => {
|
const insertEmoji = (emoji) => {
|
||||||
if (!input) return;
|
if (!input) return;
|
||||||
const value = String(input.value || '');
|
const value = String(input.value || '');
|
||||||
@@ -922,12 +926,14 @@ export function render({ navigate, route }) {
|
|||||||
input.value = `${value.slice(0, start)}${emoji}${value.slice(end)}`;
|
input.value = `${value.slice(0, start)}${emoji}${value.slice(end)}`;
|
||||||
const nextPosition = start + emoji.length;
|
const nextPosition = start + emoji.length;
|
||||||
emojiSelection = { start: nextPosition, end: nextPosition };
|
emojiSelection = { start: nextPosition, end: nextPosition };
|
||||||
|
if (isFinePointer) {
|
||||||
input.focus();
|
input.focus();
|
||||||
try {
|
try {
|
||||||
input.setSelectionRange(nextPosition, nextPosition);
|
input.setSelectionRange(nextPosition, nextPosition);
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore browsers that do not expose textarea selection during focus changes.
|
// Ignore browsers that do not expose textarea selection during focus changes.
|
||||||
}
|
}
|
||||||
|
}
|
||||||
autoResizeComposer(input);
|
autoResizeComposer(input);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1247,6 +1253,9 @@ export function render({ navigate, route }) {
|
|||||||
emojiPickerOpen = !emojiPickerOpen;
|
emojiPickerOpen = !emojiPickerOpen;
|
||||||
emojiSlot.hidden = !emojiPickerOpen;
|
emojiSlot.hidden = !emojiPickerOpen;
|
||||||
emojiToggle.setAttribute('aria-expanded', String(emojiPickerOpen));
|
emojiToggle.setAttribute('aria-expanded', String(emojiPickerOpen));
|
||||||
|
// Как в Telegram: либо клавиатура, либо пикер. На тач-устройствах при
|
||||||
|
// открытии пикера прячем клавиатуру, чтобы поле ввода оставалось видно.
|
||||||
|
if (emojiPickerOpen && !isFinePointer) input?.blur();
|
||||||
});
|
});
|
||||||
screen.addEventListener('pointerdown', (event) => {
|
screen.addEventListener('pointerdown', (event) => {
|
||||||
if (!emojiPickerOpen || form.contains(event.target)) return;
|
if (!emojiPickerOpen || form.contains(event.target)) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user