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:
@@ -4,11 +4,12 @@ import {svg} from '../../svg.ts';
|
|||||||
import {parseIssueHref, parseRepoOwnerPathInfo} from '../../utils.ts';
|
import {parseIssueHref, parseRepoOwnerPathInfo} from '../../utils.ts';
|
||||||
import {createElementFromAttrs, createElementFromHTML} from '../../utils/dom.ts';
|
import {createElementFromAttrs, createElementFromHTML} from '../../utils/dom.ts';
|
||||||
import {getIssueColorClass, getIssueIcon} from '../issue.ts';
|
import {getIssueColorClass, getIssueIcon} from '../issue.ts';
|
||||||
|
import {errorName} from '../../modules/errors.ts';
|
||||||
import {debounce} from '../../utils/func.ts';
|
import {debounce} from '../../utils/func.ts';
|
||||||
import type TextExpanderElement from '@github/text-expander-element';
|
import type TextExpanderElement from '@github/text-expander-element';
|
||||||
import type {TextExpanderChangeEvent, TextExpanderResult} 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);
|
const issuePathInfo = parseIssueHref(window.location.href);
|
||||||
if (!issuePathInfo.ownerName) {
|
if (!issuePathInfo.ownerName) {
|
||||||
const repoOwnerPathInfo = parseRepoOwnerPathInfo(window.location.pathname);
|
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};
|
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};
|
if (!matches.length) return {matched: false};
|
||||||
|
|
||||||
const ul = createElementFromAttrs('ul', {class: 'suggestions'});
|
const ul = createElementFromAttrs('ul', {class: 'suggestions'});
|
||||||
@@ -48,6 +49,7 @@ export function initTextExpander(expander: TextExpanderElement) {
|
|||||||
return keyStart > lineStart;
|
return keyStart > lineStart;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let suggestionsController = new AbortController();
|
||||||
const debouncedIssueSuggestions = debounce(async (key: string, text: string): Promise<TextExpanderResult> => {
|
const debouncedIssueSuggestions = debounce(async (key: string, text: string): Promise<TextExpanderResult> => {
|
||||||
// https://github.com/github/text-expander-element/issues/71
|
// https://github.com/github/text-expander-element/issues/71
|
||||||
// Upstream bug: when using "multiword+promise", TextExpander will get wrong "key" position.
|
// 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)
|
// check the input before the request, to avoid emitting empty query to backend (still related to the upstream bug)
|
||||||
if (!shouldShowIssueSuggestions()) return {matched: false};
|
if (!shouldShowIssueSuggestions()) return {matched: false};
|
||||||
// await sleep(Math.random() * 1000); // help to reproduce the text-expander bug
|
// await sleep(Math.random() * 1000); // help to reproduce the text-expander bug
|
||||||
const ret = await fetchIssueSuggestions(key, text);
|
suggestionsController.abort(); // only the newest request may answer
|
||||||
// check the input again to avoid text-expander using incorrect position (upstream bug)
|
suggestionsController = new AbortController();
|
||||||
if (!shouldShowIssueSuggestions()) return {matched: false};
|
try {
|
||||||
return ret;
|
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
|
}, 300); // to match onInputDebounce delay
|
||||||
|
|
||||||
expander.addEventListener('text-expander-change', (e: TextExpanderChangeEvent) => {
|
expander.addEventListener('text-expander-change', (e: TextExpanderChangeEvent) => {
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ export async function matchMention(mentionsUrl: string, queryText: string): Prom
|
|||||||
return sortAndReduce(results);
|
return sortAndReduce(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function matchIssue(owner: string, repo: string, issueIndexStr: string, query: string): Promise<Issue[]> {
|
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)}`);
|
const res = await GET(`${window.config.appSubUrl}/${owner}/${repo}/issues/suggestions?q=${encodeURIComponent(query)}`, {signal});
|
||||||
|
|
||||||
const issues: Issue[] = await res.json();
|
const issues: Issue[] = await res.json();
|
||||||
const issueNumber = parseInt(issueIndexStr);
|
const issueNumber = parseInt(issueIndexStr);
|
||||||
|
|||||||
Reference in New Issue
Block a user