SHA256
Очистить репозиторий перед переносом продукта
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
import {
|
||||
TELEGRAM_EMOJI_GROUPS,
|
||||
getTelegramEmojiAssetUrl,
|
||||
getTelegramEmojiPreviewUrl,
|
||||
} from './telegram-emoji-catalog.js?v=202607151545';
|
||||
TWEMOJI_GROUPS,
|
||||
getTwemojiAssetUrl,
|
||||
getTwemojiPreviewUrl,
|
||||
} from './twemoji-catalog.js?v=202607151545';
|
||||
|
||||
const RECENT_STORAGE_KEY = 'shine-ui-recent-emojis-v1';
|
||||
const MAX_RECENT = 28;
|
||||
const ALL_EMOJI_ITEMS = TELEGRAM_EMOJI_GROUPS.flatMap((group) => group.items);
|
||||
const ALL_EMOJI_ITEMS = TWEMOJI_GROUPS.flatMap((group) => group.items);
|
||||
const EMOJI_ASSET_BY_CHAR = new Map(ALL_EMOJI_ITEMS.map((item) => [item.emoji, item]));
|
||||
const TELEGRAM_EMOJI_MATCHER = new RegExp(
|
||||
const TWEMOJI_MATCHER = new RegExp(
|
||||
[...EMOJI_ASSET_BY_CHAR.keys()]
|
||||
.sort((left, right) => right.length - left.length)
|
||||
.map((emoji) => emoji.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&'))
|
||||
@@ -38,11 +38,11 @@ function saveRecent(items) {
|
||||
}
|
||||
|
||||
function appendAnimatedAsset(root, item) {
|
||||
const previewUrl = item.previewUrl || getTelegramEmojiPreviewUrl(item.file);
|
||||
const animatedUrl = item.animatedUrl || getTelegramEmojiAssetUrl(item.file);
|
||||
const previewUrl = item.previewUrl || getTwemojiPreviewUrl(item.emoji);
|
||||
const animatedUrl = item.animatedUrl || getTwemojiAssetUrl(item.emoji);
|
||||
const hasAnimation = animatedUrl && animatedUrl !== previewUrl;
|
||||
const image = document.createElement('img');
|
||||
image.className = 'telegram-emoji-image';
|
||||
image.className = 'twemoji-image';
|
||||
image.alt = '';
|
||||
image.decoding = 'async';
|
||||
image.setAttribute('aria-hidden', 'true');
|
||||
@@ -134,7 +134,7 @@ function appendAnimatedAsset(root, item) {
|
||||
root.append(image);
|
||||
}
|
||||
|
||||
export function stopAllTelegramEmojiAnimations() {
|
||||
export function stopAllTwemojiAnimations() {
|
||||
activeAnimation?.deactivate();
|
||||
activeAnimation = null;
|
||||
}
|
||||
@@ -155,14 +155,14 @@ function createEmojiButton(item, onSelect) {
|
||||
return button;
|
||||
}
|
||||
|
||||
export function isTelegramAnimatedEmoji(emoji) {
|
||||
export function hasTwemojiAsset(emoji) {
|
||||
return EMOJI_ASSET_BY_CHAR.has(emoji);
|
||||
}
|
||||
|
||||
export function createTelegramAnimatedEmoji(emoji, { className = '' } = {}) {
|
||||
export function createTwemojiAsset(emoji, { className = '' } = {}) {
|
||||
const item = EMOJI_ASSET_BY_CHAR.get(emoji);
|
||||
const root = document.createElement('span');
|
||||
root.className = `telegram-emoji ${className}`.trim();
|
||||
root.className = `twemoji ${className}`.trim();
|
||||
root.setAttribute('aria-label', emoji);
|
||||
root.setAttribute('role', 'img');
|
||||
|
||||
@@ -175,16 +175,16 @@ export function createTelegramAnimatedEmoji(emoji, { className = '' } = {}) {
|
||||
return root;
|
||||
}
|
||||
|
||||
export function appendTelegramAnimatedText(root, text, { className = '' } = {}) {
|
||||
export function appendTwemojiText(root, text, { className = '' } = {}) {
|
||||
const source = String(text || '');
|
||||
let cursor = 0;
|
||||
let found = false;
|
||||
|
||||
TELEGRAM_EMOJI_MATCHER.lastIndex = 0;
|
||||
for (const match of source.matchAll(TELEGRAM_EMOJI_MATCHER)) {
|
||||
TWEMOJI_MATCHER.lastIndex = 0;
|
||||
for (const match of source.matchAll(TWEMOJI_MATCHER)) {
|
||||
const index = Number(match.index || 0);
|
||||
if (index > cursor) root.append(document.createTextNode(source.slice(cursor, index)));
|
||||
root.append(createTelegramAnimatedEmoji(match[0], { className }));
|
||||
root.append(createTwemojiAsset(match[0], { className }));
|
||||
cursor = index + match[0].length;
|
||||
found = true;
|
||||
}
|
||||
@@ -224,7 +224,7 @@ export function createEmojiPicker({ onSelect }) {
|
||||
categoryGrid.className = 'emoji-picker-grid';
|
||||
categorySection.append(categoryTitle, categoryGrid);
|
||||
|
||||
let activeGroup = TELEGRAM_EMOJI_GROUPS[0];
|
||||
let activeGroup = TWEMOJI_GROUPS[0];
|
||||
const tabs = new Map();
|
||||
|
||||
const rememberAndSelect = (emoji) => {
|
||||
@@ -238,13 +238,13 @@ export function createEmojiPicker({ onSelect }) {
|
||||
const recentItems = (loadRecent().length ? loadRecent() : DEFAULT_RECENT)
|
||||
.map((emoji) => EMOJI_ASSET_BY_CHAR.get(emoji))
|
||||
.filter(Boolean);
|
||||
stopAllTelegramEmojiAnimations();
|
||||
stopAllTwemojiAnimations();
|
||||
recentGrid.replaceChildren(...recentItems.map((item) => createEmojiButton(item, rememberAndSelect)));
|
||||
};
|
||||
|
||||
const renderGroup = () => {
|
||||
categoryTitle.textContent = activeGroup.label;
|
||||
stopAllTelegramEmojiAnimations();
|
||||
stopAllTwemojiAnimations();
|
||||
categoryGrid.replaceChildren(...activeGroup.items.map((item) => createEmojiButton(item, rememberAndSelect)));
|
||||
tabs.forEach((tab, groupId) => {
|
||||
const active = groupId === activeGroup.id;
|
||||
@@ -253,7 +253,7 @@ export function createEmojiPicker({ onSelect }) {
|
||||
});
|
||||
};
|
||||
|
||||
TELEGRAM_EMOJI_GROUPS.forEach((group) => {
|
||||
TWEMOJI_GROUPS.forEach((group) => {
|
||||
const tab = document.createElement('button');
|
||||
tab.type = 'button';
|
||||
tab.className = 'emoji-picker-tab';
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { TELEGRAM_EMOJI_GROUP_ACTIVITY } from './telegram-emoji-activity.js?v=202607151545';
|
||||
import { TELEGRAM_EMOJI_GROUP_ANIMALS_NATURE } from './telegram-emoji-animals_nature.js?v=202607151545';
|
||||
import { TELEGRAM_EMOJI_GROUP_FLAGS } from './telegram-emoji-flags.js?v=202607151545';
|
||||
import { TELEGRAM_EMOJI_GROUP_FOOD_DRINK } from './telegram-emoji-food_drink.js?v=202607151545';
|
||||
import { TELEGRAM_EMOJI_GROUP_OBJECTS } from './telegram-emoji-objects.js?v=202607151545';
|
||||
import { TELEGRAM_EMOJI_GROUP_PEOPLE } from './telegram-emoji-people.js?v=202607151545';
|
||||
import { TELEGRAM_EMOJI_GROUP_SMILEYS } from './telegram-emoji-smileys.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';
|
||||
|
||||
const TELEGRAM_EMOJI_PREVIEW_BASE_URL = '/assets/emoji/telegram-previews/';
|
||||
|
||||
function getTelegramEmojiName(file) {
|
||||
return String(file || '').split('/').pop()?.replace(/\.webp$/i, '') || '';
|
||||
}
|
||||
|
||||
function withPreviewUrls(group) {
|
||||
return {
|
||||
...group,
|
||||
items: group.items.map((item) => ({
|
||||
...item,
|
||||
name: item.name || getTelegramEmojiName(item.file),
|
||||
previewUrl: getTelegramEmojiPreviewUrl(item.file),
|
||||
animatedUrl: getTelegramEmojiAssetUrl(item.file),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export const TELEGRAM_EMOJI_GROUPS = [
|
||||
TELEGRAM_EMOJI_GROUP_SMILEYS,
|
||||
TELEGRAM_EMOJI_GROUP_PEOPLE,
|
||||
TELEGRAM_EMOJI_GROUP_ANIMALS_NATURE,
|
||||
TELEGRAM_EMOJI_GROUP_FOOD_DRINK,
|
||||
TELEGRAM_EMOJI_GROUP_TRAVEL_PLACES,
|
||||
TELEGRAM_EMOJI_GROUP_ACTIVITY,
|
||||
TELEGRAM_EMOJI_GROUP_OBJECTS,
|
||||
TELEGRAM_EMOJI_GROUP_SYMBOLS,
|
||||
TELEGRAM_EMOJI_GROUP_FLAGS,
|
||||
].map(withPreviewUrls);
|
||||
|
||||
export function getTelegramEmojiAssetUrl(file) {
|
||||
// Анимированные 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('/')}`;
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export const TELEGRAM_EMOJI_GROUP_ACTIVITY = {
|
||||
export const TWEMOJI_GROUP_ACTIVITY = {
|
||||
"id": "activity",
|
||||
"label": "Активности",
|
||||
"icon": "🎉",
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export const TELEGRAM_EMOJI_GROUP_ANIMALS_NATURE = {
|
||||
export const TWEMOJI_GROUP_ANIMALS_NATURE = {
|
||||
"id": "animals-and-nature",
|
||||
"label": "Природа и животные",
|
||||
"icon": "🌿",
|
||||
@@ -0,0 +1,52 @@
|
||||
import { TWEMOJI_GROUP_ACTIVITY } from './twemoji-activity.js?v=202607151545';
|
||||
import { TWEMOJI_GROUP_ANIMALS_NATURE } from './twemoji-animals_nature.js?v=202607151545';
|
||||
import { TWEMOJI_GROUP_FLAGS } from './twemoji-flags.js?v=202607151545';
|
||||
import { TWEMOJI_GROUP_FOOD_DRINK } from './twemoji-food_drink.js?v=202607151545';
|
||||
import { TWEMOJI_GROUP_OBJECTS } from './twemoji-objects.js?v=202607151545';
|
||||
import { TWEMOJI_GROUP_PEOPLE } from './twemoji-people.js?v=202607151545';
|
||||
import { TWEMOJI_GROUP_SMILEYS } from './twemoji-smileys.js?v=202607151545';
|
||||
import { TWEMOJI_GROUP_SYMBOLS } from './twemoji-symbols.js?v=202607151545';
|
||||
import { TWEMOJI_GROUP_TRAVEL_PLACES } from './twemoji-travel_places.js?v=202607151545';
|
||||
|
||||
const TWEMOJI_SVG_BASE_URL = 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/';
|
||||
|
||||
function getTwemojiCodepoint(emoji) {
|
||||
return Array.from(String(emoji || ''))
|
||||
.map((char) => char.codePointAt(0))
|
||||
.filter((codepoint) => codepoint && codepoint !== 0xfe0f)
|
||||
.map((codepoint) => codepoint.toString(16))
|
||||
.join('-');
|
||||
}
|
||||
|
||||
function withPreviewUrls(group) {
|
||||
return {
|
||||
...group,
|
||||
items: group.items.map((item) => ({
|
||||
...item,
|
||||
name: item.name || String(item.emoji || ''),
|
||||
previewUrl: getTwemojiPreviewUrl(item.emoji),
|
||||
animatedUrl: getTwemojiAssetUrl(item.emoji),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export const TWEMOJI_GROUPS = [
|
||||
TWEMOJI_GROUP_SMILEYS,
|
||||
TWEMOJI_GROUP_PEOPLE,
|
||||
TWEMOJI_GROUP_ANIMALS_NATURE,
|
||||
TWEMOJI_GROUP_FOOD_DRINK,
|
||||
TWEMOJI_GROUP_TRAVEL_PLACES,
|
||||
TWEMOJI_GROUP_ACTIVITY,
|
||||
TWEMOJI_GROUP_OBJECTS,
|
||||
TWEMOJI_GROUP_SYMBOLS,
|
||||
TWEMOJI_GROUP_FLAGS,
|
||||
].map(withPreviewUrls);
|
||||
|
||||
export function getTwemojiAssetUrl(emoji) {
|
||||
return getTwemojiPreviewUrl(emoji);
|
||||
}
|
||||
|
||||
export function getTwemojiPreviewUrl(emoji) {
|
||||
const codepoint = getTwemojiCodepoint(emoji);
|
||||
return codepoint ? `${TWEMOJI_SVG_BASE_URL}${codepoint}.svg` : '';
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export const TELEGRAM_EMOJI_GROUP_FLAGS = {
|
||||
export const TWEMOJI_GROUP_FLAGS = {
|
||||
"id": "flags",
|
||||
"label": "Флаги",
|
||||
"icon": "🏳️",
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export const TELEGRAM_EMOJI_GROUP_FOOD_DRINK = {
|
||||
export const TWEMOJI_GROUP_FOOD_DRINK = {
|
||||
"id": "food-and-drink",
|
||||
"label": "Еда и напитки",
|
||||
"icon": "🍓",
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export const TELEGRAM_EMOJI_GROUP_OBJECTS = {
|
||||
export const TWEMOJI_GROUP_OBJECTS = {
|
||||
"id": "objects",
|
||||
"label": "Предметы",
|
||||
"icon": "💡",
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export const TELEGRAM_EMOJI_GROUP_PEOPLE = {
|
||||
export const TWEMOJI_GROUP_PEOPLE = {
|
||||
"id": "people",
|
||||
"label": "Люди",
|
||||
"icon": "🧑",
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export const TELEGRAM_EMOJI_GROUP_SMILEYS = {
|
||||
export const TWEMOJI_GROUP_SMILEYS = {
|
||||
"id": "smileys",
|
||||
"label": "Смайлы",
|
||||
"icon": "🙂",
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export const TELEGRAM_EMOJI_GROUP_SYMBOLS = {
|
||||
export const TWEMOJI_GROUP_SYMBOLS = {
|
||||
"id": "symbols",
|
||||
"label": "Символы",
|
||||
"icon": "❤️",
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export const TELEGRAM_EMOJI_GROUP_TRAVEL_PLACES = {
|
||||
export const TWEMOJI_GROUP_TRAVEL_PLACES = {
|
||||
"id": "travel-and-places",
|
||||
"label": "Путешествия",
|
||||
"icon": "✈️",
|
||||
@@ -19,10 +19,10 @@ import {
|
||||
import { startOutgoingCall } from '../services/call-service.js';
|
||||
import {
|
||||
createEmojiPicker,
|
||||
createTelegramAnimatedEmoji,
|
||||
appendTelegramAnimatedText,
|
||||
isTelegramAnimatedEmoji,
|
||||
stopAllTelegramEmojiAnimations,
|
||||
createTwemojiAsset,
|
||||
appendTwemojiText,
|
||||
hasTwemojiAsset,
|
||||
stopAllTwemojiAnimations,
|
||||
} from '../components/emoji-picker.js?v=202607152130';
|
||||
import { openSpeechInputModal } from '../components/speech-input-modal.js';
|
||||
import { isSpeechToTextConfigured, isTextToSpeechConfigured, speakTextBySettings } from '../services/speech-tools-service.js';
|
||||
@@ -473,16 +473,21 @@ function scrollToLatestMessage(list) {
|
||||
function renderMessageText(textNode, messageText) {
|
||||
const text = String(messageText || '');
|
||||
const emoji = text.trim();
|
||||
if (text === emoji && isTelegramAnimatedEmoji(emoji)) {
|
||||
textNode.classList.add('bubble-text--animated-emoji');
|
||||
textNode.append(createTelegramAnimatedEmoji(emoji, { className: 'chat-telegram-emoji' }));
|
||||
if (text === emoji && hasTwemojiAsset(emoji)) {
|
||||
textNode.classList.add('bubble-text--twemoji-only');
|
||||
textNode.append(createTwemojiAsset(emoji, { className: 'chat-twemoji' }));
|
||||
return;
|
||||
}
|
||||
if (appendTelegramAnimatedText(textNode, text, { className: 'chat-telegram-emoji chat-telegram-emoji--inline' })) {
|
||||
textNode.classList.add('bubble-text--telegram-emoji-inline');
|
||||
if (appendTwemojiText(textNode, text, { className: 'chat-twemoji chat-twemoji--inline' })) {
|
||||
textNode.classList.add('bubble-text--twemoji-inline');
|
||||
}
|
||||
}
|
||||
|
||||
function isTwemojiOnlyMessage(messageText) {
|
||||
const emoji = String(messageText || '').trim();
|
||||
return Boolean(emoji && String(messageText || '') === emoji && hasTwemojiAsset(emoji));
|
||||
}
|
||||
|
||||
function scrollToLatestMessageSmart(list, { smoothIfNearBottom = false } = {}) {
|
||||
if (!list) return;
|
||||
const scrollContainer = list.closest('.screen-content') || list.parentElement || list;
|
||||
@@ -547,6 +552,10 @@ function renderLog(list, chatId, { onOpenActions, markAsRead = true, scrollMode
|
||||
const autoKind = parsedText.callSummary ? 'call-tech' : '';
|
||||
const bubbleKind = String(msg?.kind || autoKind || '').trim();
|
||||
bubble.className = `bubble ${msg.from}${bubbleKind ? ` ${bubbleKind}` : ''}`;
|
||||
const emojiOnly = !parsedText.callSummary && !parsedText.replyRef && isTwemojiOnlyMessage(parsedText.displayText || '');
|
||||
if (emojiOnly) {
|
||||
bubble.classList.add('bubble--emoji-only');
|
||||
}
|
||||
|
||||
if (parsedText.callSummary) {
|
||||
const callCard = document.createElement('div');
|
||||
@@ -909,7 +918,7 @@ export function render({ navigate, route }) {
|
||||
const closeEmojiPicker = () => {
|
||||
if (!emojiPickerOpen) return;
|
||||
emojiPickerOpen = false;
|
||||
stopAllTelegramEmojiAnimations();
|
||||
stopAllTwemojiAnimations();
|
||||
emojiSlot.hidden = true;
|
||||
emojiToggle?.setAttribute('aria-expanded', 'false');
|
||||
};
|
||||
@@ -1253,7 +1262,7 @@ export function render({ navigate, route }) {
|
||||
emojiPickerOpen = !emojiPickerOpen;
|
||||
emojiSlot.hidden = !emojiPickerOpen;
|
||||
emojiToggle.setAttribute('aria-expanded', String(emojiPickerOpen));
|
||||
// Как в Telegram: либо клавиатура, либо пикер. На тач-устройствах при
|
||||
// Либо клавиатура, либо пикер. На тач-устройствах при
|
||||
// открытии пикера прячем клавиатуру, чтобы поле ввода оставалось видно.
|
||||
if (emojiPickerOpen && !isFinePointer) input?.blur();
|
||||
});
|
||||
@@ -1360,7 +1369,7 @@ export function render({ navigate, route }) {
|
||||
void sendReadReceiptsForVisible(chatId);
|
||||
screen.cleanup = () => {
|
||||
setChatKeyboardOpen(false);
|
||||
stopAllTelegramEmojiAnimations();
|
||||
stopAllTwemojiAnimations();
|
||||
window.removeEventListener('shine-chat-messages-updated', handleIncomingChatRefresh);
|
||||
window.visualViewport?.removeEventListener('resize', syncKeyboardUi);
|
||||
window.removeEventListener('resize', syncKeyboardUi);
|
||||
|
||||
Reference in New Issue
Block a user