97 lines
2.7 KiB
JavaScript
97 lines
2.7 KiB
JavaScript
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 suggestionPosition(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(evt) {
|
|
if (this.currentSuggestion === null)
|
|
return;
|
|
|
|
const value = this.$refs.tags.value;
|
|
const pos = this.inputCursor - this.query.length;
|
|
const tag = this.suggestions[this.currentSuggestion].display;
|
|
|
|
this.$refs.tags.value = mySplice(value, pos, this.query.length,
|
|
tag + ' ');
|
|
this.reset();
|
|
this.$refs.tags.focus();
|
|
|
|
// Don't switch focus if we're completing after a Tab press.
|
|
if (evt.type === "keydown" && evt.keyCode === 9)
|
|
evt.preventDefault();
|
|
},
|
|
|
|
selectCurrentSuggestion(evt) {
|
|
this.currentSuggestion = suggestionPosition(evt.currentTarget);
|
|
this.completeSuggestion(evt);
|
|
},
|
|
|
|
setCurrentSuggestion(evt, amount = 0) {
|
|
currentSuggestionElement(this.currentSuggestion)
|
|
?.classList.remove("suggestion-hover");
|
|
|
|
if (evt.type === 'mouseenter')
|
|
this.currentSuggestion = suggestionPosition(evt.currentTarget);
|
|
else {
|
|
const len = this.suggestions.length;
|
|
let cur = this.currentSuggestion;
|
|
|
|
if (cur === null)
|
|
cur = amount > 0 ? -1 : amount < 0 ? len : 0;
|
|
this.currentSuggestion = (cur + amount + len) % len;
|
|
}
|
|
|
|
currentSuggestionElement(this.currentSuggestion)
|
|
?.classList.add("suggestion-hover");
|
|
}
|
|
}));
|
|
});
|