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.
This commit is contained in:
silverwind
2026-07-24 21:25:38 +02:00
committed by GitHub
parent 9ad80e1eba
commit 37e80730ec
2 changed files with 17 additions and 8 deletions

View File

@@ -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<TextExpanderResult> {
async function fetchIssueSuggestions(key: string, text: string, signal: AbortSignal): Promise<TextExpanderResult> {
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<TextExp
}
if (!issuePathInfo.ownerName) return {matched: false};
const matches = await matchIssue(issuePathInfo.ownerName, issuePathInfo.repoName, issuePathInfo.indexString, text);
const matches = await matchIssue(issuePathInfo.ownerName, issuePathInfo.repoName, issuePathInfo.indexString, text, signal);
if (!matches.length) return {matched: false};
const ul = createElementFromAttrs('ul', {class: 'suggestions'});
@@ -48,6 +49,7 @@ export function initTextExpander(expander: TextExpanderElement) {
return keyStart > lineStart;
};
let suggestionsController = new AbortController();
const debouncedIssueSuggestions = debounce(async (key: string, text: string): Promise<TextExpanderResult> => {
// 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) => {

View File

@@ -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<Issue[]> {
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<Issue[]> {
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);