From a6a856c6a57c2d03216e3fd2b81c7e497f0816dc Mon Sep 17 00:00:00 2001 From: easrng Date: Fri, 18 Feb 2022 13:39:53 -0500 Subject: [PATCH] faster emoji search (kinda) --- frontend/lib/chat.mjs | 91 ++++++++++++++++++++++------------------- frontend/lib/emojis.mjs | 49 ++++++++++++++++++---- 2 files changed, 89 insertions(+), 51 deletions(-) diff --git a/frontend/lib/chat.mjs b/frontend/lib/chat.mjs index 3547778..0575e27 100644 --- a/frontend/lib/chat.mjs +++ b/frontend/lib/chat.mjs @@ -3,7 +3,7 @@ import { setVideoTime, setPlaying, } from "./watch-session.mjs?v=19ef791"; -import { emojify, emojis } from "./emojis.mjs?v=19ef791"; +import { emojify, findEmojis } from "./emojis.mjs?v=19ef791"; function setCaretPosition(elem, caretPos) { if (elem.createTextRange) { @@ -26,14 +26,16 @@ const setupChatboxEvents = (socket) => { const emojiAutocomplete = chatForm.querySelector("#emoji-autocomplete"); oldChatForm.replaceWith(chatForm); - let autocompleting = false; + let autocompleting = false, + showListTimer; const replaceMessage = (message) => () => { messageInput.value = message; autocomplete(); }; - async function autocomplete() { + async function autocomplete(fromListTimeout) { if (autocompleting) return; + clearInterval(showListTimer); emojiAutocomplete.textContent = ""; autocompleting = true; let text = messageInput.value.slice(0, messageInput.selectionStart); @@ -41,56 +43,59 @@ const setupChatboxEvents = (socket) => { if (!match || match[1]) return (autocompleting = false); // We don't need to autocomplete. const prefix = text.slice(0, match.index); const search = text.slice(match.index + 1); + if (search.length < 1 && !fromListTimeout) { + autocompleting = false; + showListTimer = setTimeout(() => autocomplete(true), 1000); + return; + } const suffix = messageInput.value.slice(messageInput.selectionStart); + let selected; const select = (button) => { - const selected = document.querySelector(".emoji-option.selected"); if (selected) selected.classList.remove("selected"); + selected = button; button.classList.add("selected"); }; emojiAutocomplete.append( - ...(await emojis) - .filter(([name]) => name.toLowerCase().startsWith(search.toLowerCase())) - .map(([name, replaceWith, ext], i) => { - const button = Object.assign(document.createElement("button"), { - className: "emoji-option" + (i === 0 ? " selected" : ""), - onmousedown: (e) => e.preventDefault(), - onclick: () => { - messageInput.value = prefix + replaceWith + " " + suffix; - setCaretPosition( - messageInput, - (prefix + " " + replaceWith).length - ); - }, - onmouseover: () => select(button), - onfocus: () => select(button), - type: "button", - title: name, - }); - button.append( - replaceWith[0] !== ":" - ? Object.assign(document.createElement("span"), { - textContent: replaceWith, - className: "emoji", - }) - : Object.assign(new Image(), { - loading: "lazy", - src: `/emojis/${name}${ext}`, - className: "emoji", - }), - Object.assign(document.createElement("span"), { - textContent: name, - className: "emoji-name", - }) - ); - return button; - }) + ...(await findEmojis(search)).map(([name, replaceWith, ext], i) => { + const button = Object.assign(document.createElement("button"), { + className: "emoji-option", + onmousedown: (e) => e.preventDefault(), + onclick: () => { + messageInput.value = prefix + replaceWith + " " + suffix; + setCaretPosition(messageInput, (prefix + " " + replaceWith).length); + }, + onmouseover: () => select(button), + onfocus: () => select(button), + type: "button", + title: name, + }); + button.append( + replaceWith[0] !== ":" + ? Object.assign(document.createElement("span"), { + textContent: replaceWith, + className: "emoji", + }) + : Object.assign(new Image(), { + loading: "lazy", + src: `/emojis/${name}${ext}`, + className: "emoji", + }), + Object.assign(document.createElement("span"), { + textContent: name, + className: "emoji-name", + }) + ); + return button; + }) ); - if (emojiAutocomplete.children[0]) + if (emojiAutocomplete.children[0]) { emojiAutocomplete.children[0].scrollIntoView(); + select(emojiAutocomplete.children[0]); + } autocompleting = false; } - messageInput.addEventListener("input", autocomplete); - messageInput.addEventListener("selectionchange", autocomplete); + messageInput.addEventListener("input", () => autocomplete()); + messageInput.addEventListener("selectionchange", () => autocomplete()); messageInput.addEventListener("keydown", (event) => { if (event.key == "ArrowUp" || event.key == "ArrowDown") { let selected = document.querySelector(".emoji-option.selected"); diff --git a/frontend/lib/emojis.mjs b/frontend/lib/emojis.mjs index 33a98af..4905a19 100644 --- a/frontend/lib/emojis.mjs +++ b/frontend/lib/emojis.mjs @@ -1,11 +1,11 @@ export async function emojify(text) { - const emojiList = await emojis; + await emojisLoaded; let last = 0; let nodes = []; text.replace(/:([^\s:]+):/g, (match, name, index) => { if (last <= index) nodes.push(document.createTextNode(text.slice(last, index))); - let emoji = emojiList.find((e) => e[0] == name); + let emoji = emojis[name.toLowerCase()[0]].find((e) => e[0] == name); if (!emoji) { nodes.push(document.createTextNode(match)); } else { @@ -26,11 +26,44 @@ export async function emojify(text) { if (last < text.length) nodes.push(document.createTextNode(text.slice(last))); return nodes; } -export const emojis = Promise.all([ +const emojis = {}; + +export const emojisLoaded = Promise.all([ fetch("/emojis") .then((e) => e.json()) - .then((e) => - e.map((e) => [e.slice(0, -4), ":" + e.slice(0, -4) + ":", e.slice(-4)]) - ), - fetch("/emojis/unicode.json").then((e) => e.json()), -]).then((e) => e.flat(1)); + .then((a) => { + for (let e of a) { + const name = e.slice(0, -4), + lower = name.toLowerCase(); + emojis[lower[0]] = emojis[lower[0]] || []; + emojis[lower[0]].push([name, ":" + name + ":", e.slice(-4), lower]); + } + }), + fetch("/emojis/unicode.json") + .then((e) => e.json()) + .then((a) => { + for (let e of a) { + emojis[e[0][0]] = emojis[e[0][0]] || []; + emojis[e[0][0]].push([e[0], e[1], null, e[0]]); + } + }), +]); + +export async function findEmojis(search) { + await emojisLoaded; + let groups = [[], []]; + if (search.length < 1) { + for (let letter in emojis) + for (let emoji of emojis[letter]) { + (emoji[1][0] === ":" ? groups[0] : groups[1]).push(emoji); + } + } else { + search = search.toLowerCase(); + for (let emoji of emojis[search[0]]) { + if (search.length == 1 || emoji[3].startsWith(search)) { + (emoji[1][0] === ":" ? groups[0] : groups[1]).push(emoji); + } + } + } + return [...groups[0], ...groups[1]]; +}