capy cant sneed
parent
e5ea709a0e
commit
0256deda29
|
@ -26,419 +26,419 @@
|
|||
*/
|
||||
|
||||
class EmojiEngine {
|
||||
_res;
|
||||
/** @type {Promise<void>} */
|
||||
loaded = new Promise(res => this._res = res);
|
||||
hasLoaded = false;
|
||||
_res;
|
||||
/** @type {Promise<void>} */
|
||||
loaded = new Promise(res => this._res = res);
|
||||
hasLoaded = false;
|
||||
|
||||
/** @type {EmojiTags} */
|
||||
tags = {};
|
||||
/** @type {EmojiTags} */
|
||||
tags = {};
|
||||
|
||||
/** @type {EmojiKinds} */
|
||||
kinds = {};
|
||||
/** @type {EmojiKinds} */
|
||||
kinds = {};
|
||||
|
||||
// Memoize this value so we don't have to recompute it.
|
||||
_tag_entries;
|
||||
// Memoize this value so we don't have to recompute it.
|
||||
_tag_entries;
|
||||
|
||||
/** @type {{[index: string]: HTMLDivElement}} */
|
||||
emojiDom = {};
|
||||
/** @type {{[index: string]: HTMLDivElement}} */
|
||||
emojiDom = {};
|
||||
|
||||
/** @type {{[index: string]: number}} */
|
||||
emojiNameCount = {};
|
||||
/** @type {{[index: string]: number}} */
|
||||
emojiNameCount = {};
|
||||
|
||||
/** @type {(name: string) => void} */
|
||||
onInsert;
|
||||
/** @type {(name: string) => void} */
|
||||
onInsert;
|
||||
|
||||
init = async () => {
|
||||
if (this.hasLoaded) {
|
||||
return;
|
||||
}
|
||||
init = async () => {
|
||||
if (this.hasLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
this.loadTags(),
|
||||
this.loadKinds(),
|
||||
]);
|
||||
await Promise.all([
|
||||
this.loadTags(),
|
||||
this.loadKinds(),
|
||||
]);
|
||||
|
||||
this._tag_entries = Object.entries(this.tags);
|
||||
this._tag_entries = Object.entries(this.tags);
|
||||
|
||||
this._res();
|
||||
this.hasLoaded = true;
|
||||
}
|
||||
this._res();
|
||||
this.hasLoaded = true;
|
||||
}
|
||||
|
||||
loadTags = async () => {
|
||||
this.tags = await (await fetch('/emoji_tags.json')).json();
|
||||
}
|
||||
loadTags = async () => {
|
||||
this.tags = await (await fetch('/emoji_tags.json')).json();
|
||||
}
|
||||
|
||||
loadKinds = async () => {
|
||||
this.kinds = await (await fetch('/emoji_kinds.json')).json();
|
||||
}
|
||||
loadKinds = async () => {
|
||||
this.kinds = await (await fetch('/emoji_kinds.json')).json();
|
||||
}
|
||||
|
||||
search = async (query, maxLength = Infinity) => {
|
||||
await this.loaded;
|
||||
search = async (query, maxLength = Infinity) => {
|
||||
await this.loaded;
|
||||
|
||||
const resultsSet = new Set();
|
||||
const results = [];
|
||||
for (const [tag, entries] of this._tag_entries) {
|
||||
if (!tag.includes(query)) {
|
||||
continue;
|
||||
}
|
||||
const resultsSet = new Set();
|
||||
const results = [];
|
||||
for (const [tag, entries] of this._tag_entries) {
|
||||
if (!tag.includes(query)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [name, count] of entries) {
|
||||
if (resultsSet.has(name)) {
|
||||
continue;
|
||||
} else if (count < results[maxLength - 1]?.[1]) {
|
||||
// All the other emojis in this tag have less uses. We can stop here.
|
||||
break;
|
||||
}
|
||||
for (const [name, count] of entries) {
|
||||
if (resultsSet.has(name)) {
|
||||
continue;
|
||||
} else if (count < results[maxLength - 1]?.[1]) {
|
||||
// All the other emojis in this tag have less uses. We can stop here.
|
||||
break;
|
||||
}
|
||||
|
||||
resultsSet.add(name);
|
||||
// Insert into the array sorted.
|
||||
let i = results.length;
|
||||
while (i > 0 && count > results[i - 1][1]) {
|
||||
i--;
|
||||
}
|
||||
results.splice(i, 0, [name, count]);
|
||||
if (results.length >= maxLength) {
|
||||
const [name] = results.pop();
|
||||
resultsSet.delete(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
resultsSet.add(name);
|
||||
// Insert into the array sorted.
|
||||
let i = results.length;
|
||||
while (i > 0 && count > results[i - 1][1]) {
|
||||
i--;
|
||||
}
|
||||
results.splice(i, 0, [name, count]);
|
||||
if (results.length >= maxLength) {
|
||||
const [name] = results.pop();
|
||||
resultsSet.delete(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results.map(([name]) => name);
|
||||
}
|
||||
return results.map(([name]) => name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a dom element for a list of emojis in quick dropdown.
|
||||
* @param {string[]} emojiNames
|
||||
*/
|
||||
getQuickDoms = (emojiNames) => {
|
||||
return emojiNames.map(this.getQuickDom);
|
||||
}
|
||||
/**
|
||||
* Get a dom element for a list of emojis in quick dropdown.
|
||||
* @param {string[]} emojiNames
|
||||
*/
|
||||
getQuickDoms = (emojiNames) => {
|
||||
return emojiNames.map(this.getQuickDom);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} emojiName
|
||||
* @returns DOM element for an emoji quick dropdown.
|
||||
*/
|
||||
getQuickDom = (emojiName) => {
|
||||
if (this.emojiDom[emojiName]) {
|
||||
return this.emojiDom[emojiName];
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {*} emojiName
|
||||
* @returns DOM element for an emoji quick dropdown.
|
||||
*/
|
||||
getQuickDom = (emojiName) => {
|
||||
if (this.emojiDom[emojiName]) {
|
||||
return this.emojiDom[emojiName];
|
||||
}
|
||||
|
||||
const emojiEl = document.createElement('button');
|
||||
emojiEl.classList.add('speed-modal-option', 'emoji-option');
|
||||
emojiEl.addEventListener('click', (e) => {
|
||||
this.onInsert(emojiName);
|
||||
});
|
||||
const emojiEl = document.createElement('button');
|
||||
emojiEl.classList.add('speed-modal-option', 'emoji-option');
|
||||
emojiEl.addEventListener('click', (e) => {
|
||||
this.onInsert(emojiName);
|
||||
});
|
||||
|
||||
const emojiImgEl = document.createElement('img');
|
||||
emojiImgEl.classList.add('speed-modal-image', 'emoji-option-image');
|
||||
emojiImgEl.src = emojiEngine.src(emojiName);
|
||||
emojiEl.appendChild(emojiImgEl);
|
||||
const emojiImgEl = document.createElement('img');
|
||||
emojiImgEl.classList.add('speed-modal-image', 'emoji-option-image');
|
||||
emojiImgEl.src = emojiEngine.src(emojiName);
|
||||
emojiEl.appendChild(emojiImgEl);
|
||||
|
||||
const emojiNameEl = document.createElement('span');
|
||||
emojiNameEl.textContent = emojiName;
|
||||
emojiEl.appendChild(emojiNameEl);
|
||||
const emojiNameEl = document.createElement('span');
|
||||
emojiNameEl.textContent = emojiName;
|
||||
emojiEl.appendChild(emojiNameEl);
|
||||
|
||||
this.emojiDom[emojiName] = emojiEl;
|
||||
return emojiEl;
|
||||
}
|
||||
this.emojiDom[emojiName] = emojiEl;
|
||||
return emojiEl;
|
||||
}
|
||||
|
||||
src = (name) => {
|
||||
return `${SITE_FULL_IMAGES}/e/${name}.webp`
|
||||
}
|
||||
src = (name) => {
|
||||
return `${SITE_FULL_IMAGES}/e/${name}.webp`
|
||||
}
|
||||
}
|
||||
|
||||
const emojiEngine = new EmojiEngine();
|
||||
|
||||
// Quick emoji dropdown & emoji insertion
|
||||
{
|
||||
const emojiDropdownEl = document.createElement('div');
|
||||
emojiDropdownEl.classList.add('speed-carot-modal');
|
||||
/** @type {null | HTMLTextAreaElement} */
|
||||
let inputEl = null;
|
||||
let visible = false;
|
||||
let typingEmojiCanceled = false;
|
||||
let firstDomEl = null;
|
||||
let firstEmojiName = null;
|
||||
let caretPos = 0;
|
||||
const emojiDropdownEl = document.createElement('div');
|
||||
emojiDropdownEl.classList.add('speed-carot-modal');
|
||||
/** @type {null | HTMLTextAreaElement} */
|
||||
let inputEl = null;
|
||||
let visible = false;
|
||||
let typingEmojiCanceled = false;
|
||||
let firstDomEl = null;
|
||||
let firstEmojiName = null;
|
||||
let caretPos = 0;
|
||||
|
||||
// Used by onclick attrib of the smile button
|
||||
window.openEmojiModal = (id) => {
|
||||
inputEl = document.getElementById(id);
|
||||
initEmojiModal();
|
||||
}
|
||||
// Used by onclick attrib of the smile button
|
||||
window.openEmojiModal = (id) => {
|
||||
inputEl = document.getElementById(id);
|
||||
initEmojiModal();
|
||||
}
|
||||
|
||||
emojiEngine.onInsert = (name) => {
|
||||
if (!inputEl) {
|
||||
return;
|
||||
}
|
||||
const match = matchTypingEmoji();
|
||||
if (match) {
|
||||
// We are inserting an emoji which we are typing.
|
||||
inputEl.value = `${inputEl.value.slice(0, match.index)}:${name}:${inputEl.value.slice(match.index + name.length)} `;
|
||||
// Draw the focus back to this element.
|
||||
inputEl.focus();
|
||||
} else {
|
||||
// We are inserting a new emoji.
|
||||
const start = inputEl.value.slice(0, caretPos);
|
||||
const end = inputEl.value.slice(caretPos);
|
||||
const insert = `:${name}:${end.length === 0 ? ' ' : ''}`;
|
||||
inputEl.value = `${start}${insert}${end}`;
|
||||
caretPos += insert.length;
|
||||
inputEl.setSelectionRange(caretPos, caretPos);
|
||||
}
|
||||
emojiEngine.onInsert = (name) => {
|
||||
if (!inputEl) {
|
||||
return;
|
||||
}
|
||||
const match = matchTypingEmoji();
|
||||
if (match) {
|
||||
// We are inserting an emoji which we are typing.
|
||||
inputEl.value = `${inputEl.value.slice(0, match.index)}:${name}:${inputEl.value.slice(match.index + name.length)} `;
|
||||
// Draw the focus back to this element.
|
||||
inputEl.focus();
|
||||
} else {
|
||||
// We are inserting a new emoji.
|
||||
const start = inputEl.value.slice(0, caretPos);
|
||||
const end = inputEl.value.slice(caretPos);
|
||||
const insert = `:${name}:${end.length === 0 ? ' ' : ''}`;
|
||||
inputEl.value = `${start}${insert}${end}`;
|
||||
caretPos += insert.length;
|
||||
inputEl.setSelectionRange(caretPos, caretPos);
|
||||
}
|
||||
|
||||
typingEmojiCanceled = false;
|
||||
update();
|
||||
typingEmojiCanceled = false;
|
||||
update();
|
||||
|
||||
// This updates the preview.
|
||||
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
// This updates the preview.
|
||||
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
|
||||
// Update the favorite count.
|
||||
if (name in favoriteEmojis) {
|
||||
favoriteEmojis[name]++;
|
||||
} else {
|
||||
favoriteEmojis[name] = 1;
|
||||
}
|
||||
localStorage.setItem("favorite_emojis", JSON.stringify(favoriteEmojis));
|
||||
}
|
||||
// Update the favorite count.
|
||||
if (name in favoriteEmojis) {
|
||||
favoriteEmojis[name]++;
|
||||
} else {
|
||||
favoriteEmojis[name] = 1;
|
||||
}
|
||||
localStorage.setItem("favorite_emojis", JSON.stringify(favoriteEmojis));
|
||||
}
|
||||
|
||||
const inputCanTakeEmojis = (el = inputEl) => {
|
||||
return el?.dataset && 'emojis' in el.dataset;
|
||||
}
|
||||
const inputCanTakeEmojis = (el = inputEl) => {
|
||||
return el?.dataset && 'emojis' in el.dataset;
|
||||
}
|
||||
|
||||
const matchTypingEmoji = () => {
|
||||
return inputEl?.value.substring(0, inputEl.selectionEnd).match(/:([\w!#]+)$/);
|
||||
}
|
||||
const matchTypingEmoji = () => {
|
||||
return inputEl?.value.substring(0, inputEl.selectionEnd).match(/:([\w!#]+)$/);
|
||||
}
|
||||
|
||||
const getTypingEmoji = () => {
|
||||
return matchTypingEmoji()?.[1] ?? null;
|
||||
}
|
||||
const getTypingEmoji = () => {
|
||||
return matchTypingEmoji()?.[1] ?? null;
|
||||
}
|
||||
|
||||
const isTypingEmoji = () => {
|
||||
return inputCanTakeEmojis() && getTypingEmoji();
|
||||
}
|
||||
const isTypingEmoji = () => {
|
||||
return inputCanTakeEmojis() && getTypingEmoji();
|
||||
}
|
||||
|
||||
const endTypingEmoji = () => {
|
||||
typingEmojiCanceled = false;
|
||||
}
|
||||
const endTypingEmoji = () => {
|
||||
typingEmojiCanceled = false;
|
||||
}
|
||||
|
||||
const update = async () => {
|
||||
const typing = isTypingEmoji();
|
||||
visible = typing && !typingEmojiCanceled;
|
||||
if (!visible) {
|
||||
emojiDropdownEl.parentElement?.removeChild(emojiDropdownEl);
|
||||
return;
|
||||
}
|
||||
const oldFirst = firstDomEl;
|
||||
document.body.appendChild(emojiDropdownEl);
|
||||
const search = await emojiEngine.search(getTypingEmoji(), 15);
|
||||
firstEmojiName = search[0];
|
||||
const domEls = emojiEngine.getQuickDoms(search);
|
||||
firstDomEl = domEls[0];
|
||||
if (oldFirst !== firstDomEl) {
|
||||
oldFirst?.classList.remove('selected');
|
||||
firstDomEl.classList.add('selected');
|
||||
}
|
||||
emojiDropdownEl.replaceChildren(...domEls);
|
||||
const { left, bottom } = getCaretPos(inputEl);
|
||||
// Using transform instead of top/left is faster.
|
||||
emojiDropdownEl.style.transform = `translate(${left}px, ${bottom}px)`;
|
||||
}
|
||||
const update = async () => {
|
||||
const typing = isTypingEmoji();
|
||||
visible = typing && !typingEmojiCanceled;
|
||||
if (!visible) {
|
||||
emojiDropdownEl.parentElement?.removeChild(emojiDropdownEl);
|
||||
return;
|
||||
}
|
||||
const oldFirst = firstDomEl;
|
||||
document.body.appendChild(emojiDropdownEl);
|
||||
const search = await emojiEngine.search(getTypingEmoji(), 15);
|
||||
firstEmojiName = search[0];
|
||||
const domEls = emojiEngine.getQuickDoms(search);
|
||||
firstDomEl = domEls[0];
|
||||
if (oldFirst !== firstDomEl) {
|
||||
oldFirst?.classList.remove('selected');
|
||||
firstDomEl.classList.add('selected');
|
||||
}
|
||||
emojiDropdownEl.replaceChildren(...domEls);
|
||||
const { left, bottom } = getCaretPos(inputEl);
|
||||
// Using transform instead of top/left is faster.
|
||||
emojiDropdownEl.style.transform = `translate(${left}px, ${bottom}px)`;
|
||||
}
|
||||
|
||||
// Add a listener when we start typing.
|
||||
/**
|
||||
* @param {FocusEvent} e
|
||||
*/
|
||||
const onKeyStart = (e) => {
|
||||
if (inputCanTakeEmojis(e.target)) {
|
||||
inputEl = e.target;
|
||||
emojiEngine.init();
|
||||
window.removeEventListener('keydown', onKeyStart);
|
||||
}
|
||||
}
|
||||
// Add a listener when we start typing.
|
||||
/**
|
||||
* @param {FocusEvent} e
|
||||
*/
|
||||
const onKeyStart = (e) => {
|
||||
if (inputCanTakeEmojis(e.target)) {
|
||||
inputEl = e.target;
|
||||
emojiEngine.init();
|
||||
window.removeEventListener('keydown', onKeyStart);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', onKeyStart);
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
const isFocused = document.activeElement === inputEl
|
||||
if (e.key === 'Escape') {
|
||||
typingEmojiCanceled = true;
|
||||
update();
|
||||
} else if (e.key === 'Enter' && isFocused) {
|
||||
emojiEngine.onInsert(firstEmojiName);
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'Tab' && isFocused) {
|
||||
firstDomEl.focus();
|
||||
firstDomEl.classList.remove("selected");
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
window.addEventListener('keydown', onKeyStart);
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
const isFocused = document.activeElement === inputEl
|
||||
if (e.key === 'Escape') {
|
||||
typingEmojiCanceled = true;
|
||||
update();
|
||||
} else if (e.key === 'Enter' && isFocused) {
|
||||
emojiEngine.onInsert(firstEmojiName);
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'Tab' && isFocused) {
|
||||
firstDomEl.focus();
|
||||
firstDomEl.classList.remove("selected");
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
['input', 'click', 'focus'].forEach((event) => {
|
||||
window.addEventListener(event, (e) => {
|
||||
if (inputCanTakeEmojis(e.target)) {
|
||||
inputEl = e.target;
|
||||
caretPos = inputEl.selectionEnd;
|
||||
}
|
||||
update();
|
||||
if (!isTypingEmoji()) {
|
||||
endTypingEmoji();
|
||||
}
|
||||
});
|
||||
});
|
||||
['input', 'click', 'focus'].forEach((event) => {
|
||||
window.addEventListener(event, (e) => {
|
||||
if (inputCanTakeEmojis(e.target)) {
|
||||
inputEl = e.target;
|
||||
caretPos = inputEl.selectionEnd;
|
||||
}
|
||||
update();
|
||||
if (!isTypingEmoji()) {
|
||||
endTypingEmoji();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {{ [name: string]: number }} */
|
||||
const favoriteEmojis = JSON.parse(localStorage.getItem("favorite_emojis")) || {};
|
||||
|
||||
const initEmojiModal = (() => {
|
||||
let hasInit = false;
|
||||
|
||||
return async () => {
|
||||
if (hasInit) {
|
||||
return;
|
||||
}
|
||||
hasInit = true;
|
||||
let hasInit = false;
|
||||
|
||||
return async () => {
|
||||
if (hasInit) {
|
||||
return;
|
||||
}
|
||||
hasInit = true;
|
||||
|
||||
await emojiEngine.init();
|
||||
await emojiEngine.init();
|
||||
|
||||
document.getElementById('emojis-work').style.display = 'none';
|
||||
document.getElementById('emojis-work').style.display = 'none';
|
||||
|
||||
/** @type {{ [tabName: string]: HTMLDivElement }} */
|
||||
const tabContentEls = {}
|
||||
/** @type {{ [tabName: string]: HTMLDivElement }} */
|
||||
const tabContentEls = {}
|
||||
|
||||
/** @type {(kind: string, el: HTMLButtonElement) => void} */
|
||||
const addTabClickListener = (kind, el) => {
|
||||
el.addEventListener('click', (e) => {
|
||||
setTab(kind);
|
||||
});
|
||||
}
|
||||
/** @type {(kind: string, el: HTMLButtonElement) => void} */
|
||||
const addTabClickListener = (kind, el) => {
|
||||
el.addEventListener('click', (e) => {
|
||||
setTab(kind);
|
||||
});
|
||||
}
|
||||
|
||||
const favorites = Object.entries(favoriteEmojis).sort((a, b) => b[1] - a[1]);
|
||||
/** @type {{ [name: string]: HTMLButtonElement }} */
|
||||
const favoriteClones = {};
|
||||
const favorites = Object.entries(favoriteEmojis).sort((a, b) => b[1] - a[1]);
|
||||
/** @type {{ [name: string]: HTMLButtonElement }} */
|
||||
const favoriteClones = {};
|
||||
|
||||
const favoriteContentEl = (() => {
|
||||
const content = document.createElement('div');
|
||||
tabContentEls['favorite'] = content;
|
||||
return content;
|
||||
})();
|
||||
const favoriteContentEl = (() => {
|
||||
const content = document.createElement('div');
|
||||
tabContentEls['favorite'] = content;
|
||||
return content;
|
||||
})();
|
||||
|
||||
let currentTab = 'favorite';
|
||||
const setTab = (kind) => {
|
||||
currentTab = kind;
|
||||
tabContent.replaceChildren(tabContentEls[kind]);
|
||||
}
|
||||
let currentTab = 'favorite';
|
||||
const setTab = (kind) => {
|
||||
currentTab = kind;
|
||||
tabContent.replaceChildren(tabContentEls[kind]);
|
||||
}
|
||||
|
||||
const emojiModal = document.getElementById('emojiModal');
|
||||
const emojiTabsEl = document.getElementById('emoji-modal-tabs');
|
||||
const tabContent = document.getElementById('emoji-tab-content');
|
||||
/** @type {HTMLInputElement} */
|
||||
const searchInputEl = document.getElementById('emoji_search');
|
||||
searchInputEl.disabled = false;
|
||||
const searchResultsContainerEl = document.createElement('div');
|
||||
let isSearching = false;
|
||||
/** @type {{ [index: string ]: HTMLButtonElement }} */
|
||||
const searchResultsEl = {};
|
||||
const favoriteTabEl = document.getElementById('emoji-modal-tabs-favorite');
|
||||
addTabClickListener('favorite', favoriteTabEl);
|
||||
const emojiModal = document.getElementById('emojiModal');
|
||||
const emojiTabsEl = document.getElementById('emoji-modal-tabs');
|
||||
const tabContent = document.getElementById('emoji-tab-content');
|
||||
/** @type {HTMLInputElement} */
|
||||
const searchInputEl = document.getElementById('emoji_search');
|
||||
searchInputEl.disabled = false;
|
||||
const searchResultsContainerEl = document.createElement('div');
|
||||
let isSearching = false;
|
||||
/** @type {{ [index: string ]: HTMLButtonElement }} */
|
||||
const searchResultsEl = {};
|
||||
const favoriteTabEl = document.getElementById('emoji-modal-tabs-favorite');
|
||||
addTabClickListener('favorite', favoriteTabEl);
|
||||
|
||||
window.emojiSearch = async () => {
|
||||
if (searchInputEl.value.length === 0 && isSearching) {
|
||||
isSearching = false;
|
||||
setTab(currentTab);
|
||||
} else if (searchInputEl.value.length > 0 && !isSearching) {
|
||||
isSearching = true;
|
||||
tabContent.replaceChildren(searchResultsContainerEl);
|
||||
}
|
||||
window.emojiSearch = async () => {
|
||||
if (searchInputEl.value.length === 0 && isSearching) {
|
||||
isSearching = false;
|
||||
setTab(currentTab);
|
||||
} else if (searchInputEl.value.length > 0 && !isSearching) {
|
||||
isSearching = true;
|
||||
tabContent.replaceChildren(searchResultsContainerEl);
|
||||
}
|
||||
|
||||
if (isSearching) {
|
||||
const query = searchInputEl.value;
|
||||
requestIdleCallback(() => {
|
||||
emojiEngine.search(query).then((results) => {
|
||||
requestIdleCallback(() => {
|
||||
searchResultsContainerEl.replaceChildren(...results.map((name) => searchResultsEl[name]));
|
||||
}, { timeout: 100 });
|
||||
});
|
||||
}, { timeout: 100 });
|
||||
}
|
||||
}
|
||||
if (isSearching) {
|
||||
const query = searchInputEl.value;
|
||||
requestIdleCallback(() => {
|
||||
emojiEngine.search(query).then((results) => {
|
||||
requestIdleCallback(() => {
|
||||
searchResultsContainerEl.replaceChildren(...results.map((name) => searchResultsEl[name]));
|
||||
}, { timeout: 100 });
|
||||
});
|
||||
}, { timeout: 100 });
|
||||
}
|
||||
}
|
||||
|
||||
const promises = Object.entries(emojiEngine.kinds).map(([kind, emojis]) => new Promise((res) => {
|
||||
const tabEl = (() => {
|
||||
const tab = document.createElement('li');
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.classList.add('nav-link', 'emojitab');
|
||||
button.dataset.bsToggle = 'tab';
|
||||
button.textContent = kind;
|
||||
tab.appendChild(button);
|
||||
emojiTabsEl.appendChild(tab);
|
||||
addTabClickListener(kind, tab);
|
||||
return tab;
|
||||
})();
|
||||
const promises = Object.entries(emojiEngine.kinds).map(([kind, emojis]) => new Promise((res) => {
|
||||
const tabEl = (() => {
|
||||
const tab = document.createElement('li');
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.classList.add('nav-link', 'emojitab');
|
||||
button.dataset.bsToggle = 'tab';
|
||||
button.textContent = kind;
|
||||
tab.appendChild(button);
|
||||
emojiTabsEl.appendChild(tab);
|
||||
addTabClickListener(kind, tab);
|
||||
return tab;
|
||||
})();
|
||||
|
||||
const tabContentEl = (() => {
|
||||
const tabContent = document.createElement('div');
|
||||
return tabContent;
|
||||
})();
|
||||
const tabContentEl = (() => {
|
||||
const tabContent = document.createElement('div');
|
||||
return tabContent;
|
||||
})();
|
||||
|
||||
tabContentEls[kind] = tabContentEl;
|
||||
tabContentEls[kind] = tabContentEl;
|
||||
|
||||
const tick = () => {
|
||||
for (const [name, count] of emojis) {
|
||||
const buttonEl = document.createElement('button');
|
||||
buttonEl.type = 'button';
|
||||
buttonEl.classList.add('btn', 'm-1', 'px-0', 'emoji2');
|
||||
buttonEl.title = `${name} (${count})`;
|
||||
const tick = () => {
|
||||
for (const [name, count] of emojis) {
|
||||
const buttonEl = document.createElement('button');
|
||||
buttonEl.type = 'button';
|
||||
buttonEl.classList.add('btn', 'm-1', 'px-0', 'emoji2');
|
||||
buttonEl.title = `${name} (${count})`;
|
||||
|
||||
const imgEl = document.createElement('img');
|
||||
imgEl.loading = 'lazy';
|
||||
imgEl.src = emojiEngine.src(name);
|
||||
imgEl.alt = name;
|
||||
buttonEl.appendChild(imgEl);
|
||||
const imgEl = document.createElement('img');
|
||||
imgEl.loading = 'lazy';
|
||||
imgEl.src = emojiEngine.src(name);
|
||||
imgEl.alt = name;
|
||||
buttonEl.appendChild(imgEl);
|
||||
|
||||
const searchClone = buttonEl.cloneNode(true);
|
||||
const els = [buttonEl, searchClone];
|
||||
const searchClone = buttonEl.cloneNode(true);
|
||||
const els = [buttonEl, searchClone];
|
||||
|
||||
if (name in favoriteEmojis) {
|
||||
const favoriteClone = buttonEl.cloneNode(true);
|
||||
favoriteClone.title = `${name} (${favoriteEmojis[name]})`;
|
||||
els.push(favoriteClone);
|
||||
favoriteClones[name] = favoriteClone;
|
||||
}
|
||||
|
||||
els.forEach((el) => {
|
||||
el.addEventListener('click', (e) => {
|
||||
emojiEngine.onInsert(name);
|
||||
});
|
||||
});
|
||||
if (name in favoriteEmojis) {
|
||||
const favoriteClone = buttonEl.cloneNode(true);
|
||||
favoriteClone.title = `${name} (${favoriteEmojis[name]})`;
|
||||
els.push(favoriteClone);
|
||||
favoriteClones[name] = favoriteClone;
|
||||
}
|
||||
|
||||
els.forEach((el) => {
|
||||
el.addEventListener('click', (e) => {
|
||||
emojiEngine.onInsert(name);
|
||||
});
|
||||
});
|
||||
|
||||
tabContentEl.appendChild(buttonEl);
|
||||
searchResultsEl[name] = searchClone;
|
||||
}
|
||||
tabContentEl.appendChild(buttonEl);
|
||||
searchResultsEl[name] = searchClone;
|
||||
}
|
||||
|
||||
res();
|
||||
res();
|
||||
|
||||
}
|
||||
requestIdleCallback(tick, { timeout: 250 });
|
||||
}));
|
||||
}
|
||||
requestIdleCallback(tick, { timeout: 250 });
|
||||
}));
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
for (const [name] of favorites) {
|
||||
if (!(name in favoriteClones)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
favoriteContentEl.appendChild(favoriteClones[name]);
|
||||
}
|
||||
});
|
||||
Promise.all(promises).then(() => {
|
||||
for (const [name] of favorites) {
|
||||
if (!(name in favoriteClones)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
favoriteContentEl.appendChild(favoriteClones[name]);
|
||||
}
|
||||
});
|
||||
|
||||
setTab(currentTab);
|
||||
}
|
||||
setTab(currentTab);
|
||||
}
|
||||
})();
|
Loading…
Reference in New Issue