diff --git a/assets/js/autocomplete.ts b/assets/js/autocomplete.ts index aceb13f4..31939ead 100644 --- a/assets/js/autocomplete.ts +++ b/assets/js/autocomplete.ts @@ -9,7 +9,9 @@ import { TermContext } from './query/lex'; import { $$ } from './utils/dom'; import { fetchLocalAutocomplete, fetchSuggestions, SuggestionsPopup, TermSuggestion } from './utils/suggestions'; -let inputField: HTMLInputElement | null = null, +type InputFieldElement = HTMLInputElement | HTMLTextAreaElement; + +let inputField: InputFieldElement | null = null, originalTerm: string | undefined, originalQuery: string | undefined, selectedTerm: TermContext | null = null; @@ -105,19 +107,36 @@ function keydownHandler(event: KeyboardEvent) { } } -function findSelectedTerm(targetInput: HTMLInputElement, searchQuery: string): TermContext | null { +function findSelectedTerm(targetInput: InputFieldElement, searchQuery: string): TermContext | null { if (targetInput.selectionStart === null || targetInput.selectionEnd === null) return null; const selectionIndex = Math.min(targetInput.selectionStart, targetInput.selectionEnd); - const terms = getTermContexts(searchQuery); - return terms.find(([range]) => range[0] < selectionIndex && range[1] >= selectionIndex) ?? null; + // Multi-line textarea elements should treat each line as the different search queries. Here we're looking for the + // actively edited line and use it instead of the whole value. + const activeLineStart = searchQuery.slice(0, selectionIndex).lastIndexOf('\n') + 1; + const lengthAfterSelectionIndex = Math.max(searchQuery.slice(selectionIndex).indexOf('\n'), 0); + const targetQuery = searchQuery.slice(activeLineStart, selectionIndex + lengthAfterSelectionIndex); + + const terms = getTermContexts(targetQuery); + const searchIndex = selectionIndex - activeLineStart; + const term = terms.find(([range]) => range[0] < searchIndex && range[1] >= searchIndex) ?? null; + + // Converting line-specific indexes back to absolute ones. + if (term) { + const [range] = term; + + range[0] += activeLineStart; + range[1] += activeLineStart; + } + + return term; } function toggleSearchAutocomplete() { const enable = store.get('enable_search_ac'); - for (const searchField of $$('input[data-ac-mode=search]')) { + for (const searchField of $$(':is(input, textarea)[data-ac-mode=search]')) { if (enable) { searchField.autocomplete = 'off'; } else { @@ -144,13 +163,13 @@ function listenAutocomplete() { loadAutocompleteFromEvent(event); window.clearTimeout(serverSideSuggestionsTimeout); - if (!(event.target instanceof HTMLInputElement)) return; + if (!(event.target instanceof HTMLInputElement) && !(event.target instanceof HTMLTextAreaElement)) return; const targetedInput = event.target; if (!targetedInput.dataset.ac) return; - targetedInput.addEventListener('keydown', keydownHandler); + targetedInput.addEventListener('keydown', keydownHandler as EventListener); if (localAc !== null) { inputField = targetedInput; @@ -212,7 +231,7 @@ function listenAutocomplete() { }); function loadAutocompleteFromEvent(event: Event) { - if (!(event.target instanceof HTMLInputElement)) return; + if (!(event.target instanceof HTMLInputElement) && !(event.target instanceof HTMLTextAreaElement)) return; if (!isLocalLoading && event.target.dataset.ac) { isLocalLoading = true; diff --git a/lib/philomena_web/templates/filter/_form.html.slime b/lib/philomena_web/templates/filter/_form.html.slime index e46cda9a..2482f71f 100644 --- a/lib/philomena_web/templates/filter/_form.html.slime +++ b/lib/philomena_web/templates/filter/_form.html.slime @@ -26,7 +26,7 @@ .field = label f, :spoilered_complex_str, "Complex Spoiler Filter" br - = textarea f, :spoilered_complex_str, class: "input input--wide", autocapitalize: "none" + = textarea f, :spoilered_complex_str, class: "input input--wide", autocapitalize: "none", data: [ac: "true", ac_min_length: 3, ac_mode: "search"] br = error_tag f, :spoilered_complex_str .fieldlabel @@ -51,7 +51,7 @@ .field = label f, :hidden_complex_str, "Complex Hide Filter" br - = textarea f, :hidden_complex_str, class: "input input--wide", autocapitalize: "none" + = textarea f, :hidden_complex_str, class: "input input--wide", autocapitalize: "none", data: [ac: "true", ac_min_length: 3, ac_mode: "search"] br = error_tag f, :hidden_complex_str .fieldlabel