faster emoji search (kinda)

This commit is contained in:
easrng 2022-02-18 13:39:53 -05:00
parent d8d22ed99e
commit a6a856c6a5
2 changed files with 89 additions and 51 deletions

View file

@ -3,7 +3,7 @@ import {
setVideoTime, setVideoTime,
setPlaying, setPlaying,
} from "./watch-session.mjs?v=19ef791"; } 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) { function setCaretPosition(elem, caretPos) {
if (elem.createTextRange) { if (elem.createTextRange) {
@ -26,14 +26,16 @@ const setupChatboxEvents = (socket) => {
const emojiAutocomplete = chatForm.querySelector("#emoji-autocomplete"); const emojiAutocomplete = chatForm.querySelector("#emoji-autocomplete");
oldChatForm.replaceWith(chatForm); oldChatForm.replaceWith(chatForm);
let autocompleting = false; let autocompleting = false,
showListTimer;
const replaceMessage = (message) => () => { const replaceMessage = (message) => () => {
messageInput.value = message; messageInput.value = message;
autocomplete(); autocomplete();
}; };
async function autocomplete() { async function autocomplete(fromListTimeout) {
if (autocompleting) return; if (autocompleting) return;
clearInterval(showListTimer);
emojiAutocomplete.textContent = ""; emojiAutocomplete.textContent = "";
autocompleting = true; autocompleting = true;
let text = messageInput.value.slice(0, messageInput.selectionStart); 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. if (!match || match[1]) return (autocompleting = false); // We don't need to autocomplete.
const prefix = text.slice(0, match.index); const prefix = text.slice(0, match.index);
const search = text.slice(match.index + 1); 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); const suffix = messageInput.value.slice(messageInput.selectionStart);
let selected;
const select = (button) => { const select = (button) => {
const selected = document.querySelector(".emoji-option.selected");
if (selected) selected.classList.remove("selected"); if (selected) selected.classList.remove("selected");
selected = button;
button.classList.add("selected"); button.classList.add("selected");
}; };
emojiAutocomplete.append( emojiAutocomplete.append(
...(await emojis) ...(await findEmojis(search)).map(([name, replaceWith, ext], i) => {
.filter(([name]) => name.toLowerCase().startsWith(search.toLowerCase())) const button = Object.assign(document.createElement("button"), {
.map(([name, replaceWith, ext], i) => { className: "emoji-option",
const button = Object.assign(document.createElement("button"), { onmousedown: (e) => e.preventDefault(),
className: "emoji-option" + (i === 0 ? " selected" : ""), onclick: () => {
onmousedown: (e) => e.preventDefault(), messageInput.value = prefix + replaceWith + " " + suffix;
onclick: () => { setCaretPosition(messageInput, (prefix + " " + replaceWith).length);
messageInput.value = prefix + replaceWith + " " + suffix; },
setCaretPosition( onmouseover: () => select(button),
messageInput, onfocus: () => select(button),
(prefix + " " + replaceWith).length type: "button",
); title: name,
}, });
onmouseover: () => select(button), button.append(
onfocus: () => select(button), replaceWith[0] !== ":"
type: "button", ? Object.assign(document.createElement("span"), {
title: name, textContent: replaceWith,
}); className: "emoji",
button.append( })
replaceWith[0] !== ":" : Object.assign(new Image(), {
? Object.assign(document.createElement("span"), { loading: "lazy",
textContent: replaceWith, src: `/emojis/${name}${ext}`,
className: "emoji", className: "emoji",
}) }),
: Object.assign(new Image(), { Object.assign(document.createElement("span"), {
loading: "lazy", textContent: name,
src: `/emojis/${name}${ext}`, className: "emoji-name",
className: "emoji", })
}), );
Object.assign(document.createElement("span"), { return button;
textContent: name, })
className: "emoji-name",
})
);
return button;
})
); );
if (emojiAutocomplete.children[0]) if (emojiAutocomplete.children[0]) {
emojiAutocomplete.children[0].scrollIntoView(); emojiAutocomplete.children[0].scrollIntoView();
select(emojiAutocomplete.children[0]);
}
autocompleting = false; autocompleting = false;
} }
messageInput.addEventListener("input", autocomplete); messageInput.addEventListener("input", () => autocomplete());
messageInput.addEventListener("selectionchange", autocomplete); messageInput.addEventListener("selectionchange", () => autocomplete());
messageInput.addEventListener("keydown", (event) => { messageInput.addEventListener("keydown", (event) => {
if (event.key == "ArrowUp" || event.key == "ArrowDown") { if (event.key == "ArrowUp" || event.key == "ArrowDown") {
let selected = document.querySelector(".emoji-option.selected"); let selected = document.querySelector(".emoji-option.selected");

View file

@ -1,11 +1,11 @@
export async function emojify(text) { export async function emojify(text) {
const emojiList = await emojis; await emojisLoaded;
let last = 0; let last = 0;
let nodes = []; let nodes = [];
text.replace(/:([^\s:]+):/g, (match, name, index) => { text.replace(/:([^\s:]+):/g, (match, name, index) => {
if (last <= index) if (last <= index)
nodes.push(document.createTextNode(text.slice(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) { if (!emoji) {
nodes.push(document.createTextNode(match)); nodes.push(document.createTextNode(match));
} else { } else {
@ -26,11 +26,44 @@ export async function emojify(text) {
if (last < text.length) nodes.push(document.createTextNode(text.slice(last))); if (last < text.length) nodes.push(document.createTextNode(text.slice(last)));
return nodes; return nodes;
} }
export const emojis = Promise.all([ const emojis = {};
export const emojisLoaded = Promise.all([
fetch("/emojis") fetch("/emojis")
.then((e) => e.json()) .then((e) => e.json())
.then((e) => .then((a) => {
e.map((e) => [e.slice(0, -4), ":" + e.slice(0, -4) + ":", e.slice(-4)]) for (let e of a) {
), const name = e.slice(0, -4),
fetch("/emojis/unicode.json").then((e) => e.json()), lower = name.toLowerCase();
]).then((e) => e.flat(1)); 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]];
}