From 37e80730eced938dbd75241d2d1b8184be583e83 Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 24 Jul 2026 21:25:38 +0200 Subject: [PATCH] fix: abort superseded issue suggestion requests (#38620) Fix regression from https://github.com/go-gitea/gitea/pull/38610 `perfect-debounce` allowed only one request in flight; `debounce` does not, so a slow older request can resolve after a newer one and leave a stale suggestion menu. Superseded requests are now aborted. --- web_src/js/features/comp/TextExpander.ts | 21 +++++++++++++++------ web_src/js/utils/match.ts | 4 ++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/web_src/js/features/comp/TextExpander.ts b/web_src/js/features/comp/TextExpander.ts index a01598086e..2eb79fc8ec 100644 --- a/web_src/js/features/comp/TextExpander.ts +++ b/web_src/js/features/comp/TextExpander.ts @@ -4,11 +4,12 @@ import {svg} from '../../svg.ts'; import {parseIssueHref, parseRepoOwnerPathInfo} from '../../utils.ts'; import {createElementFromAttrs, createElementFromHTML} from '../../utils/dom.ts'; import {getIssueColorClass, getIssueIcon} from '../issue.ts'; +import {errorName} from '../../modules/errors.ts'; import {debounce} from '../../utils/func.ts'; import type TextExpanderElement from '@github/text-expander-element'; import type {TextExpanderChangeEvent, TextExpanderResult} from '@github/text-expander-element'; -async function fetchIssueSuggestions(key: string, text: string): Promise { +async function fetchIssueSuggestions(key: string, text: string, signal: AbortSignal): Promise { const issuePathInfo = parseIssueHref(window.location.href); if (!issuePathInfo.ownerName) { const repoOwnerPathInfo = parseRepoOwnerPathInfo(window.location.pathname); @@ -18,7 +19,7 @@ async function fetchIssueSuggestions(key: string, text: string): Promise lineStart; }; + let suggestionsController = new AbortController(); const debouncedIssueSuggestions = debounce(async (key: string, text: string): Promise => { // https://github.com/github/text-expander-element/issues/71 // Upstream bug: when using "multiword+promise", TextExpander will get wrong "key" position. @@ -58,10 +60,17 @@ export function initTextExpander(expander: TextExpanderElement) { // check the input before the request, to avoid emitting empty query to backend (still related to the upstream bug) if (!shouldShowIssueSuggestions()) return {matched: false}; // await sleep(Math.random() * 1000); // help to reproduce the text-expander bug - const ret = await fetchIssueSuggestions(key, text); - // check the input again to avoid text-expander using incorrect position (upstream bug) - if (!shouldShowIssueSuggestions()) return {matched: false}; - return ret; + suggestionsController.abort(); // only the newest request may answer + suggestionsController = new AbortController(); + try { + const ret = await fetchIssueSuggestions(key, text, suggestionsController.signal); + // check the input again to avoid text-expander using incorrect position (upstream bug) + if (!shouldShowIssueSuggestions()) return {matched: false}; + return ret; + } catch (err) { + if (errorName(err) !== 'AbortError') throw err; + return {matched: false}; + } }, 300); // to match onInputDebounce delay expander.addEventListener('text-expander-change', (e: TextExpanderChangeEvent) => { diff --git a/web_src/js/utils/match.ts b/web_src/js/utils/match.ts index ac8ff16c56..6d25f3d141 100644 --- a/web_src/js/utils/match.ts +++ b/web_src/js/utils/match.ts @@ -71,8 +71,8 @@ export async function matchMention(mentionsUrl: string, queryText: string): Prom return sortAndReduce(results); } -export async function matchIssue(owner: string, repo: string, issueIndexStr: string, query: string): Promise { - const res = await GET(`${window.config.appSubUrl}/${owner}/${repo}/issues/suggestions?q=${encodeURIComponent(query)}`); +export async function matchIssue(owner: string, repo: string, issueIndexStr: string, query: string, signal: AbortSignal): Promise { + const res = await GET(`${window.config.appSubUrl}/${owner}/${repo}/issues/suggestions?q=${encodeURIComponent(query)}`, {signal}); const issues: Issue[] = await res.json(); const issueNumber = parseInt(issueIndexStr);