tweb/src/components/emoticonsDropdown/tabs/emoji.ts

777 lines
26 KiB
TypeScript

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import emoticonsDropdown, {EmoticonsDropdown, EMOTICONSSTICKERGROUP} from '..';
import cancelEvent from '../../../helpers/dom/cancelEvent';
import findUpClassName from '../../../helpers/dom/findUpClassName';
import {fastRaf} from '../../../helpers/schedulers';
import pause from '../../../helpers/schedulers/pause';
import appImManager from '../../../lib/appManagers/appImManager';
import {LangPackKey} from '../../../lib/langPack';
import rootScope from '../../../lib/rootScope';
import {emojiFromCodePoints} from '../../../vendor/emoji';
import {putPreloader} from '../../putPreloader';
import Scrollable, {ScrollableX} from '../../scrollable';
import IS_EMOJI_SUPPORTED from '../../../environment/emojiSupport';
import IS_TOUCH_SUPPORTED from '../../../environment/touchSupport';
import blurActiveElement from '../../../helpers/dom/blurActiveElement';
import Emoji from '../../../config/emoji';
import fixEmoji from '../../../lib/richTextProcessor/fixEmoji';
import wrapEmojiText from '../../../lib/richTextProcessor/wrapEmojiText';
import wrapSingleEmoji from '../../../lib/richTextProcessor/wrapSingleEmoji';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
import {EmoticonsTabC, StickersTabCategory} from './stickers';
import {makeMediaSize} from '../../../helpers/mediaSize';
import {AppManagers} from '../../../lib/appManagers/managers';
import VisibilityIntersector, {OnVisibilityChangeItem} from '../../visibilityIntersector';
import mediaSizes from '../../../helpers/mediaSizes';
import wrapStickerSetThumb from '../../wrappers/stickerSetThumb';
import attachStickerViewerListeners from '../../stickerViewer';
import ListenerSetter from '../../../helpers/listenerSetter';
import {Document, StickerSet} from '../../../layer';
import {CustomEmojiElement, CustomEmojiRendererElement} from '../../../lib/richTextProcessor/wrapRichText';
import findAndSplice from '../../../helpers/array/findAndSplice';
import positionElementByIndex from '../../../helpers/dom/positionElementByIndex';
import PopupStickers from '../../popups/stickers';
import {hideToast, toastNew} from '../../toast';
import safeAssign from '../../../helpers/object/safeAssign';
import type {AppStickersManager} from '../../../lib/appManagers/appStickersManager';
import liteMode from '../../../helpers/liteMode';
const loadedURLs: Set<string> = new Set();
export function appendEmoji(emoji: string, container?: HTMLElement, prepend = false, unify = false) {
// const emoji = details.unified;
// const emoji = (details.unified as string).split('-')
// .reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
const spanEmoji = document.createElement('span');
spanEmoji.classList.add('super-emoji');
let kek: DocumentFragment;
if(unify && !IS_EMOJI_SUPPORTED) {
kek = wrapSingleEmoji(emoji);
} else {
emoji = fixEmoji(emoji);
kek = wrapEmojiText(emoji);
}
/* if(!kek.includes('emoji')) {
console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji), emojiUnicode(emoji));
return;
} */
// console.log(kek);
spanEmoji.append(kek);
if(spanEmoji.children.length > 1) {
const first = spanEmoji.firstElementChild;
spanEmoji.innerHTML = '';
spanEmoji.append(first);
}
if(spanEmoji.firstElementChild?.tagName === 'IMG') {
const image = spanEmoji.firstElementChild as HTMLImageElement;
const url = image.src;
if(!loadedURLs.has(url)) {
image.setAttribute('loading', 'lazy');
const placeholder = document.createElement('span');
placeholder.classList.add('emoji-placeholder');
if(liteMode.isAvailable('animations')) {
image.style.opacity = '0';
placeholder.style.opacity = '1';
}
image.addEventListener('load', () => {
fastRaf(() => {
if(liteMode.isAvailable('animations')) {
image.style.opacity = '';
placeholder.style.opacity = '';
}
spanEmoji.classList.remove('empty');
loadedURLs.add(url);
});
}, {once: true});
spanEmoji.append(placeholder);
}
}
// spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
// spanEmoji.setAttribute('emoji', emoji);
if(container) {
if(prepend) container.prepend(spanEmoji);
else container.appendChild(spanEmoji);
}
return spanEmoji;
}
export function getEmojiFromElement(element: HTMLElement): {docId?: DocId, emoji: string} {
const superEmoji = findUpClassName(element, 'super-emoji');
if(!superEmoji) return;
const firstElementChild = superEmoji.firstElementChild as HTMLElement;
if(firstElementChild && firstElementChild.classList.contains('custom-emoji')) {
return {emoji: firstElementChild.dataset.stickerEmoji, docId: firstElementChild.dataset.docId} as const;
} else {
if(element.nodeType === element.TEXT_NODE) return {emoji: element.nodeValue};
if(element.tagName === 'SPAN' && !element.classList.contains('emoji') && element.firstElementChild) {
element = element.firstElementChild as HTMLElement;
}
return {emoji: element.getAttribute('alt') || element.innerText} as const;
}
}
const EMOJI_RECENT_ID: (typeof EMOJI_CATEGORIES)[0][0] = 'Emoji.Recent';
const EMOJI_RECENT_CATEGORY: (typeof EMOJI_CATEGORIES)[0] = [EMOJI_RECENT_ID, 'recent'];
const CUSTOM_EMOJI_RECENT_ID: (typeof EMOJI_CATEGORIES)[0][0] = '';
const CUSTOM_EMOJI_RECENT_CATEGORY: (typeof EMOJI_CATEGORIES)[0] = [CUSTOM_EMOJI_RECENT_ID, ''];
const EMOJI_CATEGORIES: [LangPackKey | '', string][] = [
['Emoji.SmilesAndPeople', 'smile'],
['Emoji.AnimalsAndNature', 'animals'],
['Emoji.FoodAndDrink', 'eats'],
['Emoji.TravelAndPlaces', 'car'],
['Emoji.ActivityAndSport', 'sport'],
['Emoji.Objects', 'lamp'],
// ['Emoji.Symbols', 'info'],
['Emoji.Flags', 'flag'],
['Skin Tones' as any, '']
];
let sorted: Map<(typeof EMOJI_CATEGORIES)[0], string[]>;
function prepare() {
if(sorted) {
return sorted;
}
const a: Array<[(typeof EMOJI_CATEGORIES)[0], string[]]> = [
[CUSTOM_EMOJI_RECENT_CATEGORY, []],
[EMOJI_RECENT_CATEGORY, []]
];
sorted = new Map(a);
for(const emoji in Emoji) {
const details = Emoji[emoji];
const i = '' + details;
const category = EMOJI_CATEGORIES[+i[0] - 1];
if(!category) continue; // maybe it's skin tones
let s = sorted.get(category);
if(!s) {
s = [];
sorted.set(category, s);
}
s[+i.slice(1) || 0] = emoji;
}
sorted.delete(EMOJI_CATEGORIES.pop());
EMOJI_CATEGORIES.unshift(CUSTOM_EMOJI_RECENT_CATEGORY, EMOJI_RECENT_CATEGORY);
const order = EMOJI_CATEGORIES.map(([id]) => id);
const entries = [...sorted.entries()].sort((a, b) => order.indexOf(a[0][0]) - order.indexOf(b[0][0]));
sorted = new Map(entries);
return sorted;
}
const EMOJI_ELEMENT_SIZE = makeMediaSize(42, 42);
const RECENT_MAX_LENGTH = 32;
type EmojiTabItem = {element: HTMLElement} & ReturnType<typeof getEmojiFromElement>;
type EmojiTabCategory = StickersTabCategory<EmojiTabItem, {renderer: CustomEmojiRendererElement}>;
export default class EmojiTab extends EmoticonsTabC<EmojiTabCategory> {
private closeScrollTop: number;
private menuInnerScroll: ScrollableX;
private isStandalone?: boolean;
private noRegularEmoji?: boolean;
private stickerSetId?: Parameters<AppStickersManager['getLocalStickerSet']>[0];
private onClick: (emoji: EmojiTabItem) => void;
private activeEmoji: ReturnType<typeof getEmojiFromElement>;
private activeElements: EmojiTabItem[];
constructor(options: {
managers: AppManagers,
isStandalone?: boolean,
noRegularEmoji?: boolean,
stickerSetId?: EmojiTab['stickerSetId'],
onClick?: EmojiTab['onClick']
}) {
super(
options.managers,
'super-emojis',
() => EMOJI_ELEMENT_SIZE,
16,
4,
0
);
safeAssign(this, options);
this.container.classList.add('emoji-padding');
this.content.id = 'content-emoji';
this.activeElements = [];
}
private onCategoryVisibility = ({target, visible}: Pick<OnVisibilityChangeItem, 'target' | 'visible'>) => {
const category = this.categoriesMap.get(target);
// console.log(target, visible, category);
// if(category.local) return;
const renderer = category.elements.renderer;
const newChildren: HTMLElement[] = [];
if(renderer) {
newChildren.push(renderer);
const customEmojis: Parameters<CustomEmojiRendererElement['add']>[0] = new Map();
if(visible) {
newChildren.push(...category.items.map(({docId, element}) => {
if(!docId) {
return element;
}
// return element;
// if(element.firstElementChild) {
// return element;
// }
const customEmojiElement = element.firstElementChild as CustomEmojiElement;
customEmojiElement.clear(false);
// const customEmojiElement = CustomEmojiElement.create(document.id);
customEmojis.set(customEmojiElement.docId, new Set([customEmojiElement]));
// element.append(customEmojiElement);
return element;
}));
renderer.add(customEmojis/* , EmoticonsDropdown.lazyLoadQueue */, undefined, undefined, false);
} else {
renderer.clearCanvas();
renderer.middlewareHelper.clean();
}
} else if(visible) {
newChildren.push(...category.items.map(({element}) => element));
}
// if(visible)
category.elements.items.replaceChildren(...newChildren);
if(renderer && !visible) {
const customEmojis: Parameters<CustomEmojiRendererElement['add']>[0] = new Map();
category.items.forEach(({docId, element}) => {
if(!docId) {
return;
}
const customEmojiElement = element.firstElementChild as CustomEmojiElement;
customEmojiElement.clear();
customEmojis.set(customEmojiElement.docId, new Set([customEmojiElement]));
});
/* const promise = */renderer.add(customEmojis/* , EmoticonsDropdown.lazyLoadQueue */, undefined, true);
// promise.then(() => {
// customEmojis.forEach((elements) => {
// elements.forEach((element) => {
// if(!element.innerHTML) {
// console.log('no thumb', element);
// // debugger;
// }
// });
// });
// const set = customEmojis.get('5766933926429854499');
// console.log(`wrapped ${customEmojis.size} thumbs`, set && set.values().next().value.innerHTML);
// });
}
// if(!visible) {
// const customEmojis: Parameters<CustomEmojiRendererElement['add']>[0] = {};
// category.items.forEach((item) => {
// const {element, document} = item;
// if(!element.firstElementChild) {
// return;
// }
// const customEmojiElement = CustomEmojiElement.create(document.id);
// customEmojis[customEmojiElement.docId] = new Set([customEmojiElement]);
// element.firstElementChild.replaceWith(customEmojiElement);
// });
// renderer.add(customEmojis, EmoticonsDropdown.lazyLoadQueue);
// }
};
public destroy() {
super.destroy();
this.menuInnerScroll?.destroy();
}
public init() {
super.init();
this.init = undefined;
const intersectionOptions: IntersectionObserverInit = {
root: this.isStandalone ? this.content : emoticonsDropdown.getElement()
};
this.categoriesIntersector = new VisibilityIntersector(this.onCategoryVisibility, intersectionOptions);
this.menuOnClickResult = EmoticonsDropdown.menuOnClick(this, this.menu, this.scrollable, this.menuScroll, undefined, this.listenerSetter);
const preloader = putPreloader(this.content, true);
let innerScrollWrapper: HTMLElement;
if(!this.isStandalone) {
const x = this.menuInnerScroll = new ScrollableX(undefined);
x.container.classList.add('menu-horizontal-inner-scroll');
innerScrollWrapper = document.createElement('div');
innerScrollWrapper.classList.add('menu-horizontal-inner');
innerScrollWrapper.append(x.container);
}
let preparedMap: ReturnType<typeof prepare>;
prepare();
if(!this.noRegularEmoji) {
preparedMap = prepare();
} else {
preparedMap = new Map([
[[CUSTOM_EMOJI_RECENT_CATEGORY[0], 'recent'], []]
// [EMOJI_RECENT_CATEGORY, []]
]);
}
preparedMap.forEach((emojis, [titleLangPackKey, icon]) => {
const category = this.createLocalCategory(titleLangPackKey, titleLangPackKey, icon, !icon);
category.elements.container.classList.remove('hide');
category.elements.items.classList.add(icon && !this.isStandalone ? 'is-local' : 'not-local');
emojis.forEach((unified) => {
/* if(emojiUnicode(emoji) === '1f481-200d-2642') {
console.log('append emoji', emoji, emojiUnicode(emoji));
} */
const emoji = emojiFromCodePoints(unified);
// if(emoji.includes('🕵')) {
// console.log('toCodePoints', toCodePoints(emoji));
// emoji = emoji.replace(/(\u200d[\u2640\u2642\u2695])(?!\ufe0f)/, '\ufe0f$1');
// const zwjIndex = emoji.indexOf('\u200d');
// if(zwjIndex !== -1 && !emoji.includes('\ufe0f')) {
// /* if(zwjIndex !== (emoji.length - 1)) {
// emoji = emoji.replace(/(\u200d)/g, '\ufe0f$1');
// } */
// emoji += '\ufe0f';
// //emoji += '\ufe0f';
// }
// debugger;
// }
this.addEmojiToCategory({
category,
emoji: {emoji},
batch: true
});
/* if(category === 'Smileys & Emotion') {
console.log('appended emoji', emoji, itemsDiv.children[itemsDiv.childElementCount - 1].innerHTML, emojiUnicode(emoji));
} */
});
});
const promise = Promise.all([
this.isStandalone ? undefined : pause(200),
!this.noRegularEmoji && this.managers.appEmojiManager.getRecentEmojis('native'),
!this.isStandalone && this.managers.appEmojiManager.getRecentEmojis('custom'),
this.stickerSetId && this.managers.appStickersManager.getLocalStickerSet(this.stickerSetId),
this.managers.appEmojiManager.getCustomEmojis()
]).then(([_, recent, recentCustom, mainSet, sets]) => {
preloader.remove();
if(mainSet) {
recentCustom = mainSet.documents.map((doc) => doc.id);
}
const recentCategory = this.categories[EMOJI_RECENT_ID];
const recentCustomCategory = this.categories[CUSTOM_EMOJI_RECENT_ID];
if(!this.isStandalone) {
const a = [
recentCategory && [recentCategory, recent] as const,
recentCustomCategory && [recentCustomCategory, recentCustom] as const
];
a.filter(Boolean).forEach(([category, recent]) => {
category.limit = RECENT_MAX_LENGTH;
recent.splice(RECENT_MAX_LENGTH, recent.length - RECENT_MAX_LENGTH);
});
}
if(recentCategory && recent) for(const emoji of recent) {
this.addEmojiToCategory({
category: recentCategory,
emoji: {emoji},
batch: true
});
}
if(recentCustomCategory) {
this.createRendererForCategory(recentCustomCategory);
if(recentCustom) for(const docId of recentCustom) {
this.addEmojiToCategory({
category: recentCustomCategory,
emoji: {emoji: '', docId},
batch: true
});
}
recentCustomCategory.elements.container.style.paddingTop = '.5rem';
}
EMOJI_CATEGORIES.forEach(([id]) => {
const category = this.categories[id];
if(!category) {
return;
}
this.toggleLocalCategory(category, true);
if(id !== EMOJI_RECENT_ID && id !== CUSTOM_EMOJI_RECENT_ID) {
category.menuScroll = this.menuInnerScroll;
this.menuInnerScroll.container.append(category.elements.menuTab);
}
});
this.resizeCategories();
recentCategory && innerScrollWrapper && recentCategory.elements.menuTab.after(innerScrollWrapper);
sets.sets.forEach((set) => {
this.renderStickerSet(set);
});
this.listenerSetter.add(rootScope)('premium_toggle', () => {
this.toggleCustomCategory();
});
this.listenerSetter.add(rootScope)('stickers_top', this.postponedEvent((id) => {
const category = this.categories[id];
if(!category) {
return;
}
this.positionCategory(category, true);
this.listenerSetter.add(emoticonsDropdown)('openAfterLayout', () => {
this.menuOnClickResult.setActiveStatic(category);
}, {once: true});
}));
const toggleRenderers = (ignore: boolean) => {
for(const id in this.categories) {
const category = this.categories[id];
const renderer = category.elements.renderer;
if(renderer) {
renderer.ignoreSettingDimensions = ignore;
if(!ignore) {
renderer.setDimensionsFromRect(undefined, true);
}
}
}
};
!this.isStandalone && this.listenerSetter.add(emoticonsDropdown)('opened', () => {
toggleRenderers(false);
});
!this.isStandalone && this.listenerSetter.add(emoticonsDropdown)('close', () => {
toggleRenderers(true);
});
this.listenerSetter.add(rootScope)('stickers_installed', (set) => {
if(!this.categories[set.id] && set.pFlags.emojis) {
this.renderStickerSet(set, true);
}
});
this.listenerSetter.add(rootScope)('stickers_deleted', (set) => {
const category = this.categories[set.id];
if(this.deleteCategory(category)) {
const {renderer} = category.elements;
if(renderer) {
renderer.middlewareHelper.clean();
}
}
});
!this.isStandalone && this.listenerSetter.add(rootScope)('emoji_recent', this.postponedEvent((emoji) => {
const category = this.categories[emoji.docId ? CUSTOM_EMOJI_RECENT_ID : EMOJI_RECENT_ID];
if(!category) {
return;
}
const verify: (item: EmojiTabItem) => boolean = emoji.docId ?
(item) => item.docId === emoji.docId :
(item) => item.emoji === emoji.emoji;
const found = findAndSplice(category.items, verify);
if(found) {
category.items.unshift(found);
if(this.isCategoryVisible(category)) {
const {renderer} = category.elements;
positionElementByIndex(found.element, category.elements.items, renderer ? 1 : 0, -1);
renderer?.forceRender();
}
} else {
this.addEmojiToCategory({
category,
emoji,
batch: false,
prepend: true
});
}
if(this.closeScrollTop === 0) {
this.menuOnClickResult.setActive(emoji.docId ? this.categories[EMOJI_RECENT_ID] : category);
}
}));
!this.isStandalone && this.listenerSetter.add(appImManager)('peer_changed', () => {
this.toggleCustomCategory();
});
this.toggleCustomCategory();
this.menuOnClickResult.setActive(recentCategory ?? recentCustomCategory);
});
attachClickEvent(this.content, this.onContentClick, {listenerSetter: this.listenerSetter});
attachStickerViewerListeners({listenTo: this.content, listenerSetter: this.listenerSetter});
return promise;
}
private renderStickerSet(set: StickerSet.stickerSet, prepend?: boolean) {
const category = this.createCategory(set, wrapEmojiText(set.title));
this.positionCategory(category, prepend);
const {container, menuTabPadding} = category.elements;
category.elements.items.classList.add('not-local');
category.elements.container.classList.add('is-premium-set');
category.elements.title.classList.add('tgico');
this.createRendererForCategory(category);
const promise = this.managers.appStickersManager.getStickerSet(set);
promise.then(({documents}) => {
documents.forEach((document) => {
this.addEmojiToCategory({
category,
emoji: {docId: document.id, emoji: (document as Document.document).stickerEmojiRaw},
batch: true
});
});
// if(this.isCategoryVisible(category)) {
// category.elements.items.append(...category.items.map(({element}) => element));
// }
this.onCategoryVisibility({target: category.elements.container, visible: this.isCategoryVisible(category)});
category.setCategoryItemsHeight();
container.classList.remove('hide');
});
wrapStickerSetThumb({
set,
container: menuTabPadding,
group: EMOTICONSSTICKERGROUP,
lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
width: 32,
height: 32,
autoplay: false
});
}
private get peerId() {
return appImManager.chat.peerId;
}
public getCustomCategory() {
return this.categories[CUSTOM_EMOJI_RECENT_ID];
}
private toggleCustomCategory() {
const category = this.categories[CUSTOM_EMOJI_RECENT_ID];
const hasPremium = rootScope.premium || this.peerId === rootScope.myId;
const canSeeCustomCategory = hasPremium || this.isStandalone;
super.toggleLocalCategory(category, !!category.items.length && canSeeCustomCategory);
this.content.classList.toggle('has-premium', hasPremium);
}
protected toggleLocalCategory(category: EmojiTabCategory, visible: boolean) {
if(category.id === CUSTOM_EMOJI_RECENT_ID) {
this.toggleCustomCategory();
return;
}
super.toggleLocalCategory(category, visible);
}
private createRendererForCategory(category: EmojiTabCategory) {
const renderer = CustomEmojiRendererElement.create({
animationGroup: EMOTICONSSTICKERGROUP,
customEmojiSize: mediaSizes.active.esgCustomEmoji
});
category.elements.renderer = renderer;
category.elements.items.append(renderer);
}
public addEmojiToCategory(options: {
category: EmojiTabCategory,
emoji?: ReturnType<typeof getEmojiFromElement>,
element?: HTMLElement,
batch?: boolean,
prepend?: boolean,
active?: boolean
}) {
const {category, emoji, batch, prepend} = options;
let element = options.element;
if(element) {
const spanEmoji = document.createElement('span');
spanEmoji.classList.add('super-emoji');
spanEmoji.append(element);
element = spanEmoji;
} else if(emoji.docId) {
const customEmojiElement = CustomEmojiElement.create(emoji.docId);
const span = document.createElement('span');
span.classList.add(/* 'emoji', */'super-emoji');
span.append(customEmojiElement);
element = span;
} else {
element = appendEmoji(emoji.emoji/* .replace(/[\ufe0f\u2640\u2642\u2695]/g, '') */, undefined, false/* , false */);
}
const item: typeof category['items'][0] = {
...(emoji || {emoji: undefined}),
element
};
if(
options.active || (
this.activeEmoji && (
item.docId ?
this.activeEmoji.docId === item.docId :
this.activeEmoji.emoji === item.emoji
)
)
) {
this.activeEmoji === emoji;
this.activeElements.push(item);
element.classList.add('active');
}
category.items[prepend ? 'unshift' : 'push'](item);
if(!batch && !this.spliceExceed(category)) {
this.onLocalCategoryUpdate(category);
}
}
// private addEmojisToCategory(category: EmojiTabCategory, emojis: string[], prepend?: boolean) {
// emojis.forEach((emoji) => {
// this.addEmojiToCategory(category, emoji, true, prepend);
// });
// this.onLocalCategoryUpdate(category);
// }
private onContentClick = (e: MouseEvent) => {
cancelEvent(e);
const {target} = e;
const container = findUpClassName(target, 'emoji-category');
const category = this.categoriesMap.get(container);
if(findUpClassName(target, 'category-title')) {
if(category.local) {
return;
}
new PopupStickers({id: category.set.id, access_hash: category.set.access_hash}, true).show();
return;
}
const emoji = getEmojiFromElement(target as HTMLElement);
if(!emoji) {
return;
}
if(
emoji.docId &&
!rootScope.premium && (
this.isStandalone ? category.id !== CUSTOM_EMOJI_RECENT_ID : this.peerId !== rootScope.myId
)
) {
const a = document.createElement('a');
a.onclick = () => {
appImManager.openPremiumBot();
hideToast();
};
toastNew({
langPackKey: 'CustomEmoji.PremiumAlert',
langPackArguments: [a]
});
return;
}
if(this.onClick) {
this.onClick({
...emoji,
element: findUpClassName(target, 'super-emoji').firstElementChild as HTMLElement
});
} else {
appImManager.chat.input.onEmojiSelected(emoji, false);
}
if(IS_TOUCH_SUPPORTED) {
blurActiveElement();
}
};
public setActive(emoji: ReturnType<typeof getEmojiFromElement>) {
if(
emoji === this.activeEmoji ||
emoji?.docId ? emoji.docId === this.activeEmoji?.docId : emoji?.emoji === this.activeEmoji?.emoji
) {
return;
}
this.activeEmoji = emoji;
this.activeElements.forEach((item) => {
item.element.classList.remove('active');
});
this.activeElements.length = 0;
this.categoriesMap.forEach((category) => {
category.items.forEach((item) => {
if(emoji.docId ? item.docId === emoji.docId : item.emoji === emoji.emoji) {
item.element.classList.add('active');
this.activeElements.push(item);
}
});
});
}
public onClose() {
this.closeScrollTop = this.scrollable.scrollTop;
}
}