From 73d7f6867ae34d102206f685bc1d7bd7ae746dda Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 27 Apr 2026 02:08:02 +0200 Subject: [PATCH] Address Copilot review on attachSearchBox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hide() also cancels the pending debounced search so a fetch scheduled before blur/Escape/outside-click does not fire afterwards and re-show the dropdown on a field the user has already left - use urlQueryEscape for the {query} substitution to keep escaping symmetric with the Go backend (matches Go's url.QueryEscape: space → +, strict RFC 3986 for !'()*); both decoders still accept %20 Co-Authored-By: Claude (Opus 4.7) --- web_src/js/modules/search.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web_src/js/modules/search.ts b/web_src/js/modules/search.ts index f187f907ea..8cf7eec676 100644 --- a/web_src/js/modules/search.ts +++ b/web_src/js/modules/search.ts @@ -1,6 +1,7 @@ import {debounce} from 'throttle-debounce'; import {GET} from './fetch.ts'; import {html, htmlRaw} from '../utils/html.ts'; +import {urlQueryEscape} from '../utils/url.ts'; export type SearchResult = { title: string; @@ -34,8 +35,10 @@ export function attachSearchBox(container: HTMLElement, url: string } const itemResults = new Map(); let fetchController: AbortController | null = null; + let search: ReturnType Promise>> | null = null; const hide = () => { + search?.cancel(); fetchController?.abort(); resultsEl.style.display = 'none'; resultsEl.replaceChildren(); @@ -59,12 +62,12 @@ export function attachSearchBox(container: HTMLElement, url: string hide(); }; - const search = debounce(200, async (query: string) => { + search = debounce(200, async (query: string) => { fetchController?.abort(); if (query.length < minCharacters) return hide(); const ctrl = (fetchController = new AbortController()); try { - const response = await GET(url.replaceAll('{query}', encodeURIComponent(query)), {signal: ctrl.signal}); + const response = await GET(url.replaceAll('{query}', urlQueryEscape(query)), {signal: ctrl.signal}); if (!response.ok) return hide(); const results = parse(await response.json(), query); // hide() ran (signal aborted) or a newer keystroke landed before the response did