115 lines
3 KiB
JavaScript
115 lines
3 KiB
JavaScript
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.$refs.search.value,
|
|
this.cursor);
|
|
if (!currentWord)
|
|
return;
|
|
if (currentWord === this.currentWord) {
|
|
/* Show previous items, if any. */
|
|
this.open = this.items.length > 0;
|
|
return;
|
|
}
|
|
this.currentWord = currentWord;
|
|
|
|
const params = new URLSearchParams({q: currentWord});
|
|
const response = await fetch(`${endpoints.search}?${params}`);
|
|
|
|
if (!response.ok) {
|
|
this.items = [];
|
|
this.open = false;
|
|
return;
|
|
}
|
|
|
|
this.activeItem = null;
|
|
this.items = await response.json();
|
|
this.open = this.items.length > 0;
|
|
},
|
|
|
|
async fetchSuggestionsOnInput(evt) {
|
|
this.cursor = evt.target.selectionStart;
|
|
await 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.items = [];
|
|
this.activeItem = null;
|
|
this.$refs.search.value = mySplice(this.$refs.search.value, pos,
|
|
this.currentWord.length, tag + ' ');
|
|
this.cursor = pos + tag.length + 1;
|
|
this.currentWord = '';
|
|
this.open = false;
|
|
|
|
this.$refs.search.focus();
|
|
this.$refs.search.setSelectionRange(this.cursor, this.cursor);
|
|
},
|
|
|
|
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);
|
|
},
|
|
|
|
moveActiveItem(evt, amount, defaultValue) {
|
|
const len = this.items.length;
|
|
if (len === 0)
|
|
return;
|
|
|
|
let item = this.activeItem === null ?
|
|
defaultValue : (this.activeItem + amount) % len;
|
|
if (item < 0)
|
|
item += len;
|
|
|
|
this.activeItem = item;
|
|
},
|
|
|
|
async moveActiveItemBackward(evt) {
|
|
await this.fetchSuggestionsOnInput(evt);
|
|
this.moveActiveItem(evt, -1, -1);
|
|
},
|
|
|
|
async moveActiveItemForward(evt) {
|
|
await this.fetchSuggestionsOnInput(evt);
|
|
this.moveActiveItem(evt, 1, 0);
|
|
},
|
|
}));
|
|
});
|