Created
October 1, 2025 00:42
-
-
Save alpaylan/9c3e80a1f7ff83bb860c4dd9e74b84bf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <title>Autocomplete</title> | |
| <body> | |
| <input id="autocomplete" type="text"> | |
| <div id="suggestions" style="border: 1px solid #ccc; display: none;"></div> | |
| </body> | |
| <script> | |
| let suggestions = null; | |
| const unicodeList = fetch("https://raw.githubusercontent.com/muan/emojilib/refs/heads/main/dist/emoji-en-US.json").then(r => r.json()).then(data => { | |
| const list = []; | |
| for (const [key, value] of Object.entries(data)) { | |
| list.push({ emoji: key, keys: value }); | |
| } | |
| return list; | |
| }); | |
| console.log(unicodeList); | |
| function getTokens(val) { | |
| const tokens = []; | |
| let tokenStart = 0; | |
| for (let i = 0; i < val.length; i++) { | |
| if (val[i] === " ") { | |
| if (i > tokenStart) // avoid empty tokens | |
| { | |
| tokens.push({ text: val.slice(tokenStart, i), start: tokenStart, end: i }); | |
| } | |
| tokenStart = i + 1; // skip the space | |
| } | |
| } | |
| // push the last token | |
| tokens.push({ text: val.slice(tokenStart), start: tokenStart, end: val.length }); | |
| return tokens; | |
| } | |
| function getCaretToken(tokens, caret) { | |
| for (const token of tokens) { | |
| if (caret >= token.start && caret <= token.end) { | |
| return token; | |
| } | |
| } | |
| return null; | |
| } | |
| function getUnicodeSuggestions(token, caret) { | |
| if (!token) return null; | |
| const tokenCaret = caret - token.start; | |
| if (tokenCaret < 1) return null; // need at least ":x" | |
| if (token.text[0] !== ":") return null; // not a unicode token | |
| const query = token.text.slice(1, tokenCaret).toLowerCase(); | |
| if (query.length === 0) return null; // need at least ":x" | |
| // filter the unicode list | |
| const suggestions = unicodeList.then(list => list.filter(u => u.keys.some(k => k.toLowerCase().includes(query)) || u.emoji === query).slice(0, 10)); | |
| return suggestions; | |
| } | |
| const textInput = document.getElementById("autocomplete"); | |
| textInput.addEventListener("input", (e) => { | |
| const val = e.target.value; | |
| const caret = e.target.selectionStart; | |
| // tokenize val | |
| const tokens = getTokens(val); | |
| // find the token that the caret is in | |
| const token = getCaretToken(tokens, caret); | |
| // check if the token is a unicode token | |
| if (token.text[token.start] !== ":") { | |
| suggestions = null; | |
| } | |
| suggestions = getUnicodeSuggestions(token, caret); | |
| if (suggestions !== null) { | |
| suggestions.then(s => { | |
| renderSuggestions(s); | |
| }); | |
| } else { | |
| console.log("no suggestions"); | |
| } | |
| }) | |
| function renderSuggestions(suggestions) { | |
| const suggestionsDiv = document.getElementById("suggestions"); | |
| if (suggestions.length === 0) { | |
| suggestionsDiv.style.display = "none"; | |
| suggestionsDiv.innerHTML = ""; | |
| return; | |
| } | |
| suggestionsDiv.style.display = "block"; | |
| suggestionsDiv.innerHTML = ""; | |
| for (const suggestion of suggestions) { | |
| const div = document.createElement("div"); | |
| div.textContent = `${suggestion.emoji} - ${suggestion.keys.join(", ")}`; | |
| div.style.padding = "4px"; | |
| div.style.cursor = "pointer"; | |
| div.addEventListener("click", () => { | |
| insertSuggestion(suggestion); | |
| suggestionsDiv.style.display = "none"; | |
| suggestionsDiv.innerHTML = ""; | |
| }); | |
| suggestionsDiv.appendChild(div); | |
| } | |
| } | |
| function insertSuggestion(suggestion) { | |
| const textInput = document.getElementById("autocomplete"); | |
| const val = textInput.value; | |
| const caret = textInput.selectionStart; | |
| const tokens = getTokens(val); | |
| const token = getCaretToken(tokens, caret); | |
| if (!token) return; | |
| const beforeToken = val.slice(0, token.start); | |
| const afterToken = val.slice(token.end); | |
| const newVal = beforeToken + suggestion.emoji + afterToken; | |
| textInput.value = newVal; | |
| const newCaretPos = beforeToken.length + suggestion.emoji.length; | |
| textInput.setSelectionRange(newCaretPos, newCaretPos); | |
| textInput.focus(); | |
| } | |
| </script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment