function getCurrentWordFromCursor(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); } document.addEventListener('alpine:init', () => { Alpine.data('tagsSuggestions', (initialSearch = '') => ({ items: [], activeItem: null, search: initialSearch, cursor: 0, currentWord: '', open: false, get sortedItems() { return this.items.toSorted((a, b) => b.count - a.count); }, async fetchSuggestions() { const currentWord = getCurrentWordFromCursor(this.search, this.cursor); if (!currentWord) return; if (currentWord === this.currentWord) { /* Show previous items, if any. */ this.open = true; return; } this.currentWord = currentWord; const params = new URLSearchParams({q: currentWord}); const response = await fetch(`${endpoints.search}?${params}`); if (!response.ok) { this.items = []; return; } this.items = await response.json(); this.open = true; }, fetchSuggestionsOnInput(evt) { this.cursor = evt.target.selectionStart; this.fetchSuggestions(); }, autocompleteSuggestion() { if (this.items.length === 0) return; const pos = Math.max(this.cursor - this.currentWord.length, 0); const tag = this.items[this.activeItem ?? 0].display; this.activeItem = null; this.search = mySplice(this.search, pos, this.currentWord.length, tag + ' '); this.cursor = this.$refs.search.selectionStart = this.$refs.search.selectionEnd = pos + tag.length + 1; this.currentWord = ''; this.open = false; this.$refs.search.focus(); }, autocompleteItem(evt) { if (this.currentWord !== '') { this.autocompleteSuggestion(); evt.preventDefault(); } }, getListItemIndexOf(el) { return [...this.$refs.suggestions.querySelectorAll('li')].indexOf(el); }, autocompletePointedItem(evt) { this.activeItem = this.getListItemIndexOf(evt.currentTarget); this.autocompleteSuggestion(); }, setHoveredItem(evt) { this.activeItem = this.getListItemIndexOf(evt.currentTarget); }, previousItem(evt) { const len = this.items.length; let item = this.activeItem === null ? -1 : this.activeItem - 1; item = item % len; if (item < 0) item += len; this.activeItem = item; }, nextItem(evt) { const len = this.items.length; let item = this.activeItem === null ? 0 : this.activeItem + 1; item = item % len; this.activeItem = item; }, })); });