mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 10:56:10 +01:00 
			
		
		
		
	Make issue suggestion work for all editors (#33340)
And do not handle special keys when the text-expander popup exists
This commit is contained in:
		| @@ -184,8 +184,13 @@ function handleNewline(textarea: HTMLTextAreaElement, e: Event) { | |||||||
|   triggerEditorContentChanged(textarea); |   triggerEditorContentChanged(textarea); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function isTextExpanderShown(textarea: HTMLElement): boolean { | ||||||
|  |   return Boolean(textarea.closest('text-expander')?.querySelector('.suggestions')); | ||||||
|  | } | ||||||
|  |  | ||||||
| export function initTextareaMarkdown(textarea) { | export function initTextareaMarkdown(textarea) { | ||||||
|   textarea.addEventListener('keydown', (e) => { |   textarea.addEventListener('keydown', (e) => { | ||||||
|  |     if (isTextExpanderShown(textarea)) return; | ||||||
|     if (e.key === 'Tab' && !e.ctrlKey && !e.metaKey && !e.altKey) { |     if (e.key === 'Tab' && !e.ctrlKey && !e.metaKey && !e.altKey) { | ||||||
|       // use Tab/Shift-Tab to indent/unindent the selected lines |       // use Tab/Shift-Tab to indent/unindent the selected lines | ||||||
|       handleIndentSelection(textarea, e); |       handleIndentSelection(textarea, e); | ||||||
|   | |||||||
| @@ -1,14 +1,19 @@ | |||||||
| import {matchEmoji, matchMention, matchIssue} from '../../utils/match.ts'; | import {matchEmoji, matchMention, matchIssue} from '../../utils/match.ts'; | ||||||
| import {emojiString} from '../emoji.ts'; | import {emojiString} from '../emoji.ts'; | ||||||
| import {svg} from '../../svg.ts'; | import {svg} from '../../svg.ts'; | ||||||
| import {parseIssueHref, parseIssueNewHref} from '../../utils.ts'; | import {parseIssueHref, parseRepoOwnerPathInfo} from '../../utils.ts'; | ||||||
| import {createElementFromAttrs, createElementFromHTML} from '../../utils/dom.ts'; | import {createElementFromAttrs, createElementFromHTML} from '../../utils/dom.ts'; | ||||||
| import {getIssueColor, getIssueIcon} from '../issue.ts'; | import {getIssueColor, getIssueIcon} from '../issue.ts'; | ||||||
| import {debounce} from 'perfect-debounce'; | import {debounce} from 'perfect-debounce'; | ||||||
|  |  | ||||||
| const debouncedSuggestIssues = debounce((key: string, text: string) => new Promise<{matched:boolean; fragment?: HTMLElement}>(async (resolve) => { | const debouncedSuggestIssues = debounce((key: string, text: string) => new Promise<{matched:boolean; fragment?: HTMLElement}>(async (resolve) => { | ||||||
|   let issuePathInfo = parseIssueHref(window.location.href); |   const issuePathInfo = parseIssueHref(window.location.href); | ||||||
|   if (!issuePathInfo.ownerName) issuePathInfo = parseIssueNewHref(window.location.href); |   if (!issuePathInfo.ownerName) { | ||||||
|  |     const repoOwnerPathInfo = parseRepoOwnerPathInfo(window.location.pathname); | ||||||
|  |     issuePathInfo.ownerName = repoOwnerPathInfo.ownerName; | ||||||
|  |     issuePathInfo.repoName = repoOwnerPathInfo.repoName; | ||||||
|  |     // 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 resolve({matched: false}); | ||||||
|  |  | ||||||
|   const matches = await matchIssue(issuePathInfo.ownerName, issuePathInfo.repoName, issuePathInfo.indexString, text); |   const matches = await matchIssue(issuePathInfo.ownerName, issuePathInfo.repoName, issuePathInfo.indexString, text); | ||||||
|   | |||||||
| @@ -30,6 +30,11 @@ export type RequestOpts = { | |||||||
|   data?: RequestData, |   data?: RequestData, | ||||||
| } & RequestInit; | } & RequestInit; | ||||||
|  |  | ||||||
|  | export type RepoOwnerPathInfo = { | ||||||
|  |   ownerName: string, | ||||||
|  |   repoName: string, | ||||||
|  | } | ||||||
|  |  | ||||||
| export type IssuePathInfo = { | export type IssuePathInfo = { | ||||||
|   ownerName: string, |   ownerName: string, | ||||||
|   repoName: string, |   repoName: string, | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { | import { | ||||||
|   basename, extname, isObject, stripTags, parseIssueHref, |   basename, extname, isObject, stripTags, parseIssueHref, | ||||||
|   parseUrl, translateMonth, translateDay, blobToDataURI, |   parseUrl, translateMonth, translateDay, blobToDataURI, | ||||||
|   toAbsoluteUrl, encodeURLEncodedBase64, decodeURLEncodedBase64, isImageFile, isVideoFile, parseIssueNewHref, |   toAbsoluteUrl, encodeURLEncodedBase64, decodeURLEncodedBase64, isImageFile, isVideoFile, parseRepoOwnerPathInfo, | ||||||
| } from './utils.ts'; | } from './utils.ts'; | ||||||
|  |  | ||||||
| test('basename', () => { | test('basename', () => { | ||||||
| @@ -45,12 +45,14 @@ test('parseIssueHref', () => { | |||||||
|   expect(parseIssueHref('')).toEqual({ownerName: undefined, repoName: undefined, type: undefined, index: undefined}); |   expect(parseIssueHref('')).toEqual({ownerName: undefined, repoName: undefined, type: undefined, index: undefined}); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| test('parseIssueNewHref', () => { | test('parseRepoOwnerPathInfo', () => { | ||||||
|   expect(parseIssueNewHref('/owner/repo/issues/new')).toEqual({ownerName: 'owner', repoName: 'repo', pathType: 'issues'}); |   expect(parseRepoOwnerPathInfo('/owner/repo/issues/new')).toEqual({ownerName: 'owner', repoName: 'repo'}); | ||||||
|   expect(parseIssueNewHref('/owner/repo/issues/new?query')).toEqual({ownerName: 'owner', repoName: 'repo', pathType: 'issues'}); |   expect(parseRepoOwnerPathInfo('/owner/repo/releases')).toEqual({ownerName: 'owner', repoName: 'repo'}); | ||||||
|   expect(parseIssueNewHref('/sub/owner/repo/issues/new#hash')).toEqual({ownerName: 'owner', repoName: 'repo', pathType: 'issues'}); |   expect(parseRepoOwnerPathInfo('/other')).toEqual({}); | ||||||
|   expect(parseIssueNewHref('/sub/owner/repo/compare/feature/branch-1...fix/branch-2')).toEqual({ownerName: 'owner', repoName: 'repo', pathType: 'pulls'}); |   window.config.appSubUrl = '/sub'; | ||||||
|   expect(parseIssueNewHref('/other')).toEqual({}); |   expect(parseRepoOwnerPathInfo('/sub/owner/repo/issues/new')).toEqual({ownerName: 'owner', repoName: 'repo'}); | ||||||
|  |   expect(parseRepoOwnerPathInfo('/sub/owner/repo/compare/feature/branch-1...fix/branch-2')).toEqual({ownerName: 'owner', repoName: 'repo'}); | ||||||
|  |   window.config.appSubUrl = ''; | ||||||
| }); | }); | ||||||
|  |  | ||||||
| test('parseUrl', () => { | test('parseUrl', () => { | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import {decode, encode} from 'uint8-to-base64'; | import {decode, encode} from 'uint8-to-base64'; | ||||||
| import type {IssuePageInfo, IssuePathInfo} from './types.ts'; | import type {IssuePageInfo, IssuePathInfo, RepoOwnerPathInfo} from './types.ts'; | ||||||
|  |  | ||||||
| // transform /path/to/file.ext to file.ext | // transform /path/to/file.ext to file.ext | ||||||
| export function basename(path: string): string { | export function basename(path: string): string { | ||||||
| @@ -32,16 +32,17 @@ export function stripTags(text: string): string { | |||||||
| } | } | ||||||
|  |  | ||||||
| export function parseIssueHref(href: string): IssuePathInfo { | export function parseIssueHref(href: string): IssuePathInfo { | ||||||
|  |   // FIXME: it should use pathname and trim the appSubUrl ahead | ||||||
|   const path = (href || '').replace(/[#?].*$/, ''); |   const path = (href || '').replace(/[#?].*$/, ''); | ||||||
|   const [_, ownerName, repoName, pathType, indexString] = /([^/]+)\/([^/]+)\/(issues|pulls)\/([0-9]+)/.exec(path) || []; |   const [_, ownerName, repoName, pathType, indexString] = /([^/]+)\/([^/]+)\/(issues|pulls)\/([0-9]+)/.exec(path) || []; | ||||||
|   return {ownerName, repoName, pathType, indexString}; |   return {ownerName, repoName, pathType, indexString}; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function parseIssueNewHref(href: string): IssuePathInfo { | export function parseRepoOwnerPathInfo(pathname: string): RepoOwnerPathInfo { | ||||||
|   const path = (href || '').replace(/[#?].*$/, ''); |   const appSubUrl = window.config.appSubUrl; | ||||||
|   const [_, ownerName, repoName, pathTypeField] = /([^/]+)\/([^/]+)\/(issues\/new|compare\/.+\.\.\.)/.exec(path) || []; |   if (appSubUrl && pathname.startsWith(appSubUrl)) pathname = pathname.substring(appSubUrl.length); | ||||||
|   const pathType = pathTypeField ? (pathTypeField.startsWith('issues/new') ? 'issues' : 'pulls') : undefined; |   const [_, ownerName, repoName] = /([^/]+)\/([^/]+)/.exec(pathname) || []; | ||||||
|   return {ownerName, repoName, pathType}; |   return {ownerName, repoName}; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function parseIssuePageInfo(): IssuePageInfo { | export function parseIssuePageInfo(): IssuePageInfo { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user