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