function getCurrentWord(s, cursor) { return s.substring(s.lastIndexOf(' ', cursor - 1) + 1, cursor); } function mySplice(s, idx, del, n) { return s.substring(0, idx) + n + s.substring(idx + del); } function suggestionIndex(el) { // el.parentNode is ul#tags-suggestion, which include a template. That's why // a -1 is needed here. const idx = [...el.parentNode.children].indexOf(el); return idx <= 0 ? null : idx - 1; } function currentSuggestionElement(idx) { if (idx === null) return null; return document.querySelector("#tags-suggestions").children[idx + 1]; } document.addEventListener('alpine:init', () => { Alpine.data('tagsSuggestions', () => ({ suggestions: [], currentSuggestion: null, inputCursor: 0, query: '', reset() { this.suggestions = []; this.currentSuggestion = null; this.inputCursor = 0; this.query = ''; }, async fetchSuggestions(evt) { this.inputCursor = evt.target.selectionStart; this.query = getCurrentWord(evt.target.value, this.inputCursor); if (!this.query) { this.reset(); return; } const params = new URLSearchParams({q: this.query}); const response = await fetch(`${endpoints.search}?${params}`); if (!response.ok) { this.reset(); return; } this.suggestions = await response.json(); }, completeSuggestion() { if (this.currentSuggestion === null) return; const value = this.$refs.tags.value; const pos = Math.max(this.inputCursor - this.query.length, 0); const tag = this.suggestions[this.currentSuggestion].display; const newValue = mySplice(value, pos, this.query.length, tag + ' '); this.reset(); this.$refs.tags.value = newValue; this.inputCursor = this.$refs.tags.selectionStart = this.$refs.tags.selectionEnd = pos + tag.length + 1; this.$refs.tags.focus(); }, completeClickedSuggestion(evt) { this.currentSuggestion = suggestionIndex(evt.currentTarget); this.completeSuggestion(); }, setCurrentSuggestion(idx) { const len = this.suggestions.length; if (len === 0) return; currentSuggestionElement(this.currentSuggestion) ?.classList.remove("suggestion-hover"); idx = idx % len; if (idx < 0) idx += len; this.currentSuggestion = idx; currentSuggestionElement(this.currentSuggestion) ?.classList.add("suggestion-hover"); }, setPointerSuggestion(evt) { this.setCurrentSuggestion(suggestionIndex(evt.currentTarget)); }, moveCurrentSuggestion(amount, defaultValue) { this.setCurrentSuggestion(this.currentSuggestion === null ? defaultValue : this.currentSuggestion + amount); }, nextSuggestion(evt) { this.moveCurrentSuggestion(1, 0); }, previousSuggestion(evt) { this.moveCurrentSuggestion(-1, -1); }, })); });