mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	| @@ -7,7 +7,20 @@ import {getIssueColor, getIssueIcon} from '../issue.ts'; | |||||||
| import {debounce} from 'perfect-debounce'; | import {debounce} from 'perfect-debounce'; | ||||||
| import type TextExpanderElement from '@github/text-expander-element'; | import type TextExpanderElement from '@github/text-expander-element'; | ||||||
|  |  | ||||||
| const debouncedSuggestIssues = debounce((key: string, text: string) => new Promise<{matched:boolean; fragment?: HTMLElement}>(async (resolve) => { | type TextExpanderProvideResult = { | ||||||
|  |   matched: boolean, | ||||||
|  |   fragment?: HTMLElement, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TextExpanderChangeEvent = Event & { | ||||||
|  |   detail?: { | ||||||
|  |     key: string, | ||||||
|  |     text: string, | ||||||
|  |     provide: (result: TextExpanderProvideResult | Promise<TextExpanderProvideResult>) => void, | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function fetchIssueSuggestions(key: string, text: string): Promise<TextExpanderProvideResult> { | ||||||
|   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); | ||||||
| @@ -15,10 +28,10 @@ const debouncedSuggestIssues = debounce((key: string, text: string) => new Promi | |||||||
|     issuePathInfo.repoName = repoOwnerPathInfo.repoName; |     issuePathInfo.repoName = repoOwnerPathInfo.repoName; | ||||||
|     // then no issuePathInfo.indexString here, it is only used to exclude the current issue when "matchIssue" |     // then no issuePathInfo.indexString here, it is only used to exclude the current issue when "matchIssue" | ||||||
|   } |   } | ||||||
|   if (!issuePathInfo.ownerName) return resolve({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); | ||||||
|   if (!matches.length) return resolve({matched: false}); |   if (!matches.length) return {matched: false}; | ||||||
|  |  | ||||||
|   const ul = createElementFromAttrs('ul', {class: 'suggestions'}); |   const ul = createElementFromAttrs('ul', {class: 'suggestions'}); | ||||||
|   for (const issue of matches) { |   for (const issue of matches) { | ||||||
| @@ -30,11 +43,40 @@ const debouncedSuggestIssues = debounce((key: string, text: string) => new Promi | |||||||
|     ); |     ); | ||||||
|     ul.append(li); |     ul.append(li); | ||||||
|   } |   } | ||||||
|   resolve({matched: true, fragment: ul}); |   return {matched: true, fragment: ul}; | ||||||
| }), 100); | } | ||||||
|  |  | ||||||
| export function initTextExpander(expander: TextExpanderElement) { | export function initTextExpander(expander: TextExpanderElement) { | ||||||
|   expander?.addEventListener('text-expander-change', ({detail: {key, provide, text}}: Record<string, any>) => { |   if (!expander) return; | ||||||
|  |  | ||||||
|  |   const textarea = expander.querySelector<HTMLTextAreaElement>('textarea'); | ||||||
|  |  | ||||||
|  |   // help to fix the text-expander "multiword+promise" bug: do not show the popup when there is no "#" before current line | ||||||
|  |   const shouldShowIssueSuggestions = () => { | ||||||
|  |     const posVal = textarea.value.substring(0, textarea.selectionStart); | ||||||
|  |     const lineStart = posVal.lastIndexOf('\n'); | ||||||
|  |     const keyStart = posVal.lastIndexOf('#'); | ||||||
|  |     return keyStart > lineStart; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const debouncedIssueSuggestions = debounce(async (key: string, text: string): Promise<TextExpanderProvideResult> => { | ||||||
|  |     // https://github.com/github/text-expander-element/issues/71 | ||||||
|  |     // Upstream bug: when using "multiword+promise", TextExpander will get wrong "key" position. | ||||||
|  |     // To reproduce, comment out the "shouldShowIssueSuggestions" check, use the "await sleep" below, | ||||||
|  |     // then use content "close #20\nclose #20\nclose #20" (3 lines), keep changing the last line `#20` part from the end (including removing the `#`) | ||||||
|  |     // There will be a JS error: Uncaught (in promise) IndexSizeError: Failed to execute 'setStart' on 'Range': The offset 28 is larger than the node's length (27). | ||||||
|  |  | ||||||
|  |     // 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; | ||||||
|  |   }, 300); // to match onInputDebounce delay | ||||||
|  |  | ||||||
|  |   expander.addEventListener('text-expander-change', (e: TextExpanderChangeEvent) => { | ||||||
|  |     const {key, text, provide} = e.detail; | ||||||
|     if (key === ':') { |     if (key === ':') { | ||||||
|       const matches = matchEmoji(text); |       const matches = matchEmoji(text); | ||||||
|       if (!matches.length) return provide({matched: false}); |       if (!matches.length) return provide({matched: false}); | ||||||
| @@ -82,10 +124,11 @@ export function initTextExpander(expander: TextExpanderElement) { | |||||||
|  |  | ||||||
|       provide({matched: true, fragment: ul}); |       provide({matched: true, fragment: ul}); | ||||||
|     } else if (key === '#') { |     } else if (key === '#') { | ||||||
|       provide(debouncedSuggestIssues(key, text)); |       provide(debouncedIssueSuggestions(key, text)); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|   expander?.addEventListener('text-expander-value', ({detail}: Record<string, any>) => { |  | ||||||
|  |   expander.addEventListener('text-expander-value', ({detail}: Record<string, any>) => { | ||||||
|     if (detail?.item) { |     if (detail?.item) { | ||||||
|       // add a space after @mentions and #issue as it's likely the user wants one |       // add a space after @mentions and #issue as it's likely the user wants one | ||||||
|       const suffix = ['@', '#'].includes(detail.key) ? ' ' : ''; |       const suffix = ['@', '#'].includes(detail.key) ? ' ' : ''; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 wxiaoguang
					wxiaoguang