forked from forktown/watch-party
faster emoji search (kinda)
This commit is contained in:
parent
d8d22ed99e
commit
a6a856c6a5
2 changed files with 89 additions and 51 deletions
|
@ -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");
|
||||||
|
|
|
@ -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]];
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue