mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 10:56:10 +01:00 
			
		
		
		
	Fix remaining typescript issues, enable tsc (#32840)
				
					
				
			Fixes 79 typescript errors. Discovered at least two bugs in `notifications.ts`, and I'm pretty sure this feature was at least partially broken and may still be, I don't really know how to test it. After this, only like ~10 typescript errors remain in the codebase but those are harder to solve. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		
							
								
								
									
										8
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								Makefile
									
									
									
									
									
								
							| @@ -377,12 +377,12 @@ lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig | ||||
| .PHONY: lint-js | ||||
| lint-js: node_modules | ||||
| 	npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) | ||||
| #	npx vue-tsc | ||||
| 	npx vue-tsc | ||||
|  | ||||
| .PHONY: lint-js-fix | ||||
| lint-js-fix: node_modules | ||||
| 	npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix | ||||
| #	npx vue-tsc | ||||
| 	npx vue-tsc | ||||
|  | ||||
| .PHONY: lint-css | ||||
| lint-css: node_modules | ||||
| @@ -451,10 +451,6 @@ lint-templates: .venv node_modules | ||||
| lint-yaml: .venv | ||||
| 	@poetry run yamllint . | ||||
|  | ||||
| .PHONY: tsc | ||||
| tsc: | ||||
| 	npx vue-tsc | ||||
|  | ||||
| .PHONY: watch | ||||
| watch: | ||||
| 	@bash tools/watch.sh | ||||
|   | ||||
							
								
								
									
										62
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										62
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -67,6 +67,7 @@ | ||||
|       "devDependencies": { | ||||
|         "@eslint-community/eslint-plugin-eslint-comments": "4.4.1", | ||||
|         "@playwright/test": "1.49.0", | ||||
|         "@silverwind/vue-tsc": "2.1.13", | ||||
|         "@stoplight/spectral-cli": "6.14.2", | ||||
|         "@stylistic/eslint-plugin-js": "2.11.0", | ||||
|         "@stylistic/stylelint-plugin": "3.1.1", | ||||
| @@ -111,8 +112,7 @@ | ||||
|         "type-fest": "4.30.0", | ||||
|         "updates": "16.4.0", | ||||
|         "vite-string-plugin": "1.3.4", | ||||
|         "vitest": "2.1.8", | ||||
|         "vue-tsc": "2.1.10" | ||||
|         "vitest": "2.1.8" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">= 18.0.0" | ||||
| @@ -3833,6 +3833,24 @@ | ||||
|       "hasInstallScript": true, | ||||
|       "license": "Apache-2.0" | ||||
|     }, | ||||
|     "node_modules/@silverwind/vue-tsc": { | ||||
|       "version": "2.1.13", | ||||
|       "resolved": "https://registry.npmjs.org/@silverwind/vue-tsc/-/vue-tsc-2.1.13.tgz", | ||||
|       "integrity": "sha512-ejFxz1KZiUGAESbC+eURnjqt0N95qkU9eZU7W15wgF9zV+v2FEu3ZLduuXTC7D/Sg6lL1R/QjPfUbxbAbBQOsw==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@volar/typescript": "~2.4.11", | ||||
|         "@vue/language-core": "2.1.10", | ||||
|         "semver": "^7.5.4" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "vue-tsc": "bin/vue-tsc.js" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "typescript": ">=5.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@silverwind/vue3-calendar-heatmap": { | ||||
|       "version": "2.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/@silverwind/vue3-calendar-heatmap/-/vue3-calendar-heatmap-2.0.6.tgz", | ||||
| @@ -5335,30 +5353,30 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@volar/language-core": { | ||||
|       "version": "2.4.10", | ||||
|       "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.10.tgz", | ||||
|       "integrity": "sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==", | ||||
|       "version": "2.4.11", | ||||
|       "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.11.tgz", | ||||
|       "integrity": "sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@volar/source-map": "2.4.10" | ||||
|         "@volar/source-map": "2.4.11" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@volar/source-map": { | ||||
|       "version": "2.4.10", | ||||
|       "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.10.tgz", | ||||
|       "integrity": "sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA==", | ||||
|       "version": "2.4.11", | ||||
|       "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.11.tgz", | ||||
|       "integrity": "sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ==", | ||||
|       "dev": true, | ||||
|       "license": "MIT" | ||||
|     }, | ||||
|     "node_modules/@volar/typescript": { | ||||
|       "version": "2.4.10", | ||||
|       "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.10.tgz", | ||||
|       "integrity": "sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==", | ||||
|       "version": "2.4.11", | ||||
|       "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.11.tgz", | ||||
|       "integrity": "sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@volar/language-core": "2.4.10", | ||||
|         "@volar/language-core": "2.4.11", | ||||
|         "path-browserify": "^1.0.1", | ||||
|         "vscode-uri": "^3.0.8" | ||||
|       } | ||||
| @@ -15780,24 +15798,6 @@ | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/vue-tsc": { | ||||
|       "version": "2.1.10", | ||||
|       "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.1.10.tgz", | ||||
|       "integrity": "sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==", | ||||
|       "dev": true, | ||||
|       "license": "MIT", | ||||
|       "dependencies": { | ||||
|         "@volar/typescript": "~2.4.8", | ||||
|         "@vue/language-core": "2.1.10", | ||||
|         "semver": "^7.5.4" | ||||
|       }, | ||||
|       "bin": { | ||||
|         "vue-tsc": "bin/vue-tsc.js" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "typescript": ">=5.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/watchpack": { | ||||
|       "version": "2.4.2", | ||||
|       "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", | ||||
|   | ||||
| @@ -66,6 +66,7 @@ | ||||
|   "devDependencies": { | ||||
|     "@eslint-community/eslint-plugin-eslint-comments": "4.4.1", | ||||
|     "@playwright/test": "1.49.0", | ||||
|     "@silverwind/vue-tsc": "2.1.13", | ||||
|     "@stoplight/spectral-cli": "6.14.2", | ||||
|     "@stylistic/eslint-plugin-js": "2.11.0", | ||||
|     "@stylistic/stylelint-plugin": "3.1.1", | ||||
| @@ -110,8 +111,7 @@ | ||||
|     "type-fest": "4.30.0", | ||||
|     "updates": "16.4.0", | ||||
|     "vite-string-plugin": "1.3.4", | ||||
|     "vitest": "2.1.8", | ||||
|     "vue-tsc": "2.1.10" | ||||
|     "vitest": "2.1.8" | ||||
|   }, | ||||
|   "browserslist": [ | ||||
|     "defaults" | ||||
|   | ||||
| @@ -7,7 +7,8 @@ | ||||
|   ], | ||||
|   "compilerOptions": { | ||||
|     "target": "es2020", | ||||
|     "module": "nodenext", | ||||
|     "module": "esnext", | ||||
|     "moduleResolution": "bundler", | ||||
|     "lib": ["dom", "dom.iterable", "dom.asynciterable", "esnext"], | ||||
|     "allowImportingTsExtensions": true, | ||||
|     "allowJs": true, | ||||
|   | ||||
| @@ -7,7 +7,7 @@ const reIssueSharpIndex = /^#(\d+)$/; // eg: "#123" | ||||
| const reIssueOwnerRepoIndex = /^([-.\w]+)\/([-.\w]+)#(\d+)$/;  // eg: "{owner}/{repo}#{index}" | ||||
|  | ||||
| // if the searchText can be parsed to an "issue goto link", return the link, otherwise return empty string | ||||
| export function parseIssueListQuickGotoLink(repoLink, searchText) { | ||||
| export function parseIssueListQuickGotoLink(repoLink: string, searchText: string) { | ||||
|   searchText = searchText.trim(); | ||||
|   let targetUrl = ''; | ||||
|   if (repoLink) { | ||||
| @@ -15,13 +15,12 @@ export function parseIssueListQuickGotoLink(repoLink, searchText) { | ||||
|     if (reIssueIndex.test(searchText)) { | ||||
|       targetUrl = `${repoLink}/issues/${searchText}`; | ||||
|     } else if (reIssueSharpIndex.test(searchText)) { | ||||
|       targetUrl = `${repoLink}/issues/${searchText.substr(1)}`; | ||||
|       targetUrl = `${repoLink}/issues/${searchText.substring(1)}`; | ||||
|     } | ||||
|   } else { | ||||
|     // try to parse it for a global search (eg: "owner/repo#123") | ||||
|     const matchIssueOwnerRepoIndex = searchText.match(reIssueOwnerRepoIndex); | ||||
|     if (matchIssueOwnerRepoIndex) { | ||||
|       const [_, owner, repo, index] = matchIssueOwnerRepoIndex; | ||||
|     const [_, owner, repo, index] = reIssueOwnerRepoIndex.exec(searchText) || []; | ||||
|     if (owner) { | ||||
|       targetUrl = `${appSubUrl}/${owner}/${repo}/issues/${index}`; | ||||
|     } | ||||
|   } | ||||
| @@ -33,7 +32,7 @@ export function initCommonIssueListQuickGoto() { | ||||
|   if (!goto) return; | ||||
|  | ||||
|   const form = goto.closest('form'); | ||||
|   const input = form.querySelector('input[name=q]'); | ||||
|   const input = form.querySelector<HTMLInputElement>('input[name=q]'); | ||||
|   const repoLink = goto.getAttribute('data-repo-link'); | ||||
|  | ||||
|   form.addEventListener('submit', (e) => { | ||||
|   | ||||
| @@ -283,8 +283,8 @@ export class ComboMarkdownEditor { | ||||
|     ]; | ||||
|   } | ||||
|  | ||||
|   parseEasyMDEToolbar(EasyMDE, actions) { | ||||
|     this.easyMDEToolbarActions = this.easyMDEToolbarActions || easyMDEToolbarActions(EasyMDE, this); | ||||
|   parseEasyMDEToolbar(easyMde: typeof EasyMDE, actions) { | ||||
|     this.easyMDEToolbarActions = this.easyMDEToolbarActions || easyMDEToolbarActions(easyMde, this); | ||||
|     const processed = []; | ||||
|     for (const action of actions) { | ||||
|       const actionButton = this.easyMDEToolbarActions[action]; | ||||
|   | ||||
| @@ -1,100 +1,102 @@ | ||||
| import {svg} from '../../svg.ts'; | ||||
| import type EasyMDE from 'easymde'; | ||||
| import type {ComboMarkdownEditor} from './ComboMarkdownEditor.ts'; | ||||
|  | ||||
| export function easyMDEToolbarActions(EasyMDE, editor) { | ||||
|   const actions = { | ||||
| export function easyMDEToolbarActions(easyMde: typeof EasyMDE, editor: ComboMarkdownEditor): Record<string, Partial<EasyMDE.ToolbarIcon | string>> { | ||||
|   const actions: Record<string, Partial<EasyMDE.ToolbarIcon> | string> = { | ||||
|     '|': '|', | ||||
|     'heading-1': { | ||||
|       action: EasyMDE.toggleHeading1, | ||||
|       action: easyMde.toggleHeading1, | ||||
|       icon: svg('octicon-heading'), | ||||
|       title: 'Heading 1', | ||||
|     }, | ||||
|     'heading-2': { | ||||
|       action: EasyMDE.toggleHeading2, | ||||
|       action: easyMde.toggleHeading2, | ||||
|       icon: svg('octicon-heading'), | ||||
|       title: 'Heading 2', | ||||
|     }, | ||||
|     'heading-3': { | ||||
|       action: EasyMDE.toggleHeading3, | ||||
|       action: easyMde.toggleHeading3, | ||||
|       icon: svg('octicon-heading'), | ||||
|       title: 'Heading 3', | ||||
|     }, | ||||
|     'heading-smaller': { | ||||
|       action: EasyMDE.toggleHeadingSmaller, | ||||
|       action: easyMde.toggleHeadingSmaller, | ||||
|       icon: svg('octicon-heading'), | ||||
|       title: 'Decrease Heading', | ||||
|     }, | ||||
|     'heading-bigger': { | ||||
|       action: EasyMDE.toggleHeadingBigger, | ||||
|       action: easyMde.toggleHeadingBigger, | ||||
|       icon: svg('octicon-heading'), | ||||
|       title: 'Increase Heading', | ||||
|     }, | ||||
|     'bold': { | ||||
|       action: EasyMDE.toggleBold, | ||||
|       action: easyMde.toggleBold, | ||||
|       icon: svg('octicon-bold'), | ||||
|       title: 'Bold', | ||||
|     }, | ||||
|     'italic': { | ||||
|       action: EasyMDE.toggleItalic, | ||||
|       action: easyMde.toggleItalic, | ||||
|       icon: svg('octicon-italic'), | ||||
|       title: 'Italic', | ||||
|     }, | ||||
|     'strikethrough': { | ||||
|       action: EasyMDE.toggleStrikethrough, | ||||
|       action: easyMde.toggleStrikethrough, | ||||
|       icon: svg('octicon-strikethrough'), | ||||
|       title: 'Strikethrough', | ||||
|     }, | ||||
|     'quote': { | ||||
|       action: EasyMDE.toggleBlockquote, | ||||
|       action: easyMde.toggleBlockquote, | ||||
|       icon: svg('octicon-quote'), | ||||
|       title: 'Quote', | ||||
|     }, | ||||
|     'code': { | ||||
|       action: EasyMDE.toggleCodeBlock, | ||||
|       action: easyMde.toggleCodeBlock, | ||||
|       icon: svg('octicon-code'), | ||||
|       title: 'Code', | ||||
|     }, | ||||
|     'link': { | ||||
|       action: EasyMDE.drawLink, | ||||
|       action: easyMde.drawLink, | ||||
|       icon: svg('octicon-link'), | ||||
|       title: 'Link', | ||||
|     }, | ||||
|     'unordered-list': { | ||||
|       action: EasyMDE.toggleUnorderedList, | ||||
|       action: easyMde.toggleUnorderedList, | ||||
|       icon: svg('octicon-list-unordered'), | ||||
|       title: 'Unordered List', | ||||
|     }, | ||||
|     'ordered-list': { | ||||
|       action: EasyMDE.toggleOrderedList, | ||||
|       action: easyMde.toggleOrderedList, | ||||
|       icon: svg('octicon-list-ordered'), | ||||
|       title: 'Ordered List', | ||||
|     }, | ||||
|     'image': { | ||||
|       action: EasyMDE.drawImage, | ||||
|       action: easyMde.drawImage, | ||||
|       icon: svg('octicon-image'), | ||||
|       title: 'Image', | ||||
|     }, | ||||
|     'table': { | ||||
|       action: EasyMDE.drawTable, | ||||
|       action: easyMde.drawTable, | ||||
|       icon: svg('octicon-table'), | ||||
|       title: 'Table', | ||||
|     }, | ||||
|     'horizontal-rule': { | ||||
|       action: EasyMDE.drawHorizontalRule, | ||||
|       action: easyMde.drawHorizontalRule, | ||||
|       icon: svg('octicon-horizontal-rule'), | ||||
|       title: 'Horizontal Rule', | ||||
|     }, | ||||
|     'preview': { | ||||
|       action: EasyMDE.togglePreview, | ||||
|       action: easyMde.togglePreview, | ||||
|       icon: svg('octicon-eye'), | ||||
|       title: 'Preview', | ||||
|     }, | ||||
|     'fullscreen': { | ||||
|       action: EasyMDE.toggleFullScreen, | ||||
|       action: easyMde.toggleFullScreen, | ||||
|       icon: svg('octicon-screen-full'), | ||||
|       title: 'Fullscreen', | ||||
|     }, | ||||
|     'side-by-side': { | ||||
|       action: EasyMDE.toggleSideBySide, | ||||
|       action: easyMde.toggleSideBySide, | ||||
|       icon: svg('octicon-columns'), | ||||
|       title: 'Side by Side', | ||||
|     }, | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import {fomanticQuery} from '../../modules/fomantic/base.ts'; | ||||
|  | ||||
| export function initCompReactionSelector(parent: ParentNode = document) { | ||||
|   for (const container of parent.querySelectorAll('.issue-content, .diff-file-body')) { | ||||
|     container.addEventListener('click', async (e) => { | ||||
|     container.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => { | ||||
|       // there are 2 places for the "reaction" buttons, one is the top-right reaction menu, one is the bottom of the comment | ||||
|       const target = e.target.closest('.comment-reaction-button'); | ||||
|       if (!target) return; | ||||
|   | ||||
| @@ -23,7 +23,7 @@ export function initCompWebHookEditor() { | ||||
|   } | ||||
|  | ||||
|   // some webhooks (like Gitea) allow to set the request method (GET/POST), and it would toggle the "Content Type" field | ||||
|   const httpMethodInput = document.querySelector('#http_method'); | ||||
|   const httpMethodInput = document.querySelector<HTMLInputElement>('#http_method'); | ||||
|   if (httpMethodInput) { | ||||
|     const updateContentType = function () { | ||||
|       const visible = httpMethodInput.value === 'POST'; | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import {GET, POST} from '../modules/fetch.ts'; | ||||
| import {showErrorToast} from '../modules/toast.ts'; | ||||
| import {createElementFromHTML, createElementFromAttrs} from '../utils/dom.ts'; | ||||
| import {isImageFile, isVideoFile} from '../utils.ts'; | ||||
| import type {DropzoneFile} from 'dropzone/index.js'; | ||||
|  | ||||
| const {csrfToken, i18n} = window.config; | ||||
|  | ||||
| @@ -15,14 +16,14 @@ export const DropzoneCustomEventRemovedFile = 'dropzone-custom-removed-file'; | ||||
| export const DropzoneCustomEventUploadDone = 'dropzone-custom-upload-done'; | ||||
|  | ||||
| async function createDropzone(el, opts) { | ||||
|   const [{Dropzone}] = await Promise.all([ | ||||
|   const [{default: Dropzone}] = await Promise.all([ | ||||
|     import(/* webpackChunkName: "dropzone" */'dropzone'), | ||||
|     import(/* webpackChunkName: "dropzone" */'dropzone/dist/dropzone.css'), | ||||
|   ]); | ||||
|   return new Dropzone(el, opts); | ||||
| } | ||||
|  | ||||
| export function generateMarkdownLinkForAttachment(file, {width, dppx} = {}) { | ||||
| export function generateMarkdownLinkForAttachment(file, {width, dppx}: {width?: number, dppx?: number} = {}) { | ||||
|   let fileMarkdown = `[${file.name}](/attachments/${file.uuid})`; | ||||
|   if (isImageFile(file)) { | ||||
|     fileMarkdown = `!${fileMarkdown}`; | ||||
| @@ -60,14 +61,14 @@ function addCopyLink(file) { | ||||
| /** | ||||
|  * @param {HTMLElement} dropzoneEl | ||||
|  */ | ||||
| export async function initDropzone(dropzoneEl) { | ||||
| export async function initDropzone(dropzoneEl: HTMLElement) { | ||||
|   const listAttachmentsUrl = dropzoneEl.closest('[data-attachment-url]')?.getAttribute('data-attachment-url'); | ||||
|   const removeAttachmentUrl = dropzoneEl.getAttribute('data-remove-url'); | ||||
|   const attachmentBaseLinkUrl = dropzoneEl.getAttribute('data-link-url'); | ||||
|  | ||||
|   let disableRemovedfileEvent = false; // when resetting the dropzone (removeAllFiles), disable the "removedfile" event | ||||
|   let fileUuidDict = {}; // to record: if a comment has been saved, then the uploaded files won't be deleted from server when clicking the Remove in the dropzone | ||||
|   const opts = { | ||||
|   const opts: Record<string, any> = { | ||||
|     url: dropzoneEl.getAttribute('data-upload-url'), | ||||
|     headers: {'X-Csrf-Token': csrfToken}, | ||||
|     acceptedFiles: ['*/*', ''].includes(dropzoneEl.getAttribute('data-accepts')) ? null : dropzoneEl.getAttribute('data-accepts'), | ||||
| @@ -88,7 +89,7 @@ export async function initDropzone(dropzoneEl) { | ||||
|   // "http://localhost:3000/owner/repo/issues/[object%20Event]" | ||||
|   // the reason is that the preview "callback(dataURL)" is assign to "img.onerror" then "thumbnail" uses the error object as the dataURL and generates '<img src="[object Event]">' | ||||
|   const dzInst = await createDropzone(dropzoneEl, opts); | ||||
|   dzInst.on('success', (file, resp) => { | ||||
|   dzInst.on('success', (file: DropzoneFile & {uuid: string}, resp: any) => { | ||||
|     file.uuid = resp.uuid; | ||||
|     fileUuidDict[file.uuid] = {submitted: false}; | ||||
|     const input = createElementFromAttrs('input', {name: 'files', type: 'hidden', id: `dropzone-file-${resp.uuid}`, value: resp.uuid}); | ||||
| @@ -97,7 +98,7 @@ export async function initDropzone(dropzoneEl) { | ||||
|     dzInst.emit(DropzoneCustomEventUploadDone, {file}); | ||||
|   }); | ||||
|  | ||||
|   dzInst.on('removedfile', async (file) => { | ||||
|   dzInst.on('removedfile', async (file: DropzoneFile & {uuid: string}) => { | ||||
|     if (disableRemovedfileEvent) return; | ||||
|  | ||||
|     dzInst.emit(DropzoneCustomEventRemovedFile, {fileUuid: file.uuid}); | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import emojis from '../../../assets/emoji.json'; | ||||
| import emojis from '../../../assets/emoji.json' with {type: 'json'}; | ||||
|  | ||||
| const {assetUrlPrefix, customEmojis} = window.config; | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,11 @@ const sourcesByUrl = {}; | ||||
| const sourcesByPort = {}; | ||||
|  | ||||
| class Source { | ||||
|   url: string; | ||||
|   eventSource: EventSource; | ||||
|   listening: Record<string, any>; | ||||
|   clients: Array<any>; | ||||
|  | ||||
|   constructor(url) { | ||||
|     this.url = url; | ||||
|     this.eventSource = new EventSource(url); | ||||
| @@ -67,7 +72,7 @@ class Source { | ||||
|   } | ||||
| } | ||||
|  | ||||
| self.addEventListener('connect', (e) => { | ||||
| self.addEventListener('connect', (e: Event & {ports: Array<any>}) => { | ||||
|   for (const port of e.ports) { | ||||
|     port.addEventListener('message', (event) => { | ||||
|       if (!self.EventSource) { | ||||
|   | ||||
| @@ -21,8 +21,8 @@ export function initHeatmap() { | ||||
|     // last heatmap tooltip localization attempt https://github.com/go-gitea/gitea/pull/24131/commits/a83761cbbae3c2e3b4bced71e680f44432073ac8 | ||||
|     const locale = { | ||||
|       heatMapLocale: { | ||||
|         months: new Array(12).fill().map((_, idx) => translateMonth(idx)), | ||||
|         days: new Array(7).fill().map((_, idx) => translateDay(idx)), | ||||
|         months: new Array(12).fill(undefined).map((_, idx) => translateMonth(idx)), | ||||
|         days: new Array(7).fill(undefined).map((_, idx) => translateDay(idx)), | ||||
|         on: ' - ', // no correct locale support for it, because in many languages the sentence is not "something on someday" | ||||
|         more: el.getAttribute('data-locale-more'), | ||||
|         less: el.getAttribute('data-locale-less'), | ||||
|   | ||||
| @@ -22,9 +22,9 @@ function initPreInstall() { | ||||
|     mssql: '127.0.0.1:1433', | ||||
|   }; | ||||
|  | ||||
|   const dbHost = document.querySelector('#db_host'); | ||||
|   const dbUser = document.querySelector('#db_user'); | ||||
|   const dbName = document.querySelector('#db_name'); | ||||
|   const dbHost = document.querySelector<HTMLInputElement>('#db_host'); | ||||
|   const dbUser = document.querySelector<HTMLInputElement>('#db_user'); | ||||
|   const dbName = document.querySelector<HTMLInputElement>('#db_name'); | ||||
|  | ||||
|   // Database type change detection. | ||||
|   document.querySelector('#db_type').addEventListener('change', function () { | ||||
| @@ -48,12 +48,12 @@ function initPreInstall() { | ||||
|   }); | ||||
|   document.querySelector('#db_type').dispatchEvent(new Event('change')); | ||||
|  | ||||
|   const appUrl = document.querySelector('#app_url'); | ||||
|   const appUrl = document.querySelector<HTMLInputElement>('#app_url'); | ||||
|   if (appUrl.value.includes('://localhost')) { | ||||
|     appUrl.value = window.location.href; | ||||
|   } | ||||
|  | ||||
|   const domain = document.querySelector('#domain'); | ||||
|   const domain = document.querySelector<HTMLInputElement>('#domain'); | ||||
|   if (domain.value.trim() === 'localhost') { | ||||
|     domain.value = window.location.hostname; | ||||
|   } | ||||
| @@ -61,43 +61,43 @@ function initPreInstall() { | ||||
|   // TODO: better handling of exclusive relations. | ||||
|   document.querySelector('#offline-mode input').addEventListener('change', function () { | ||||
|     if (this.checked) { | ||||
|       document.querySelector('#disable-gravatar input').checked = true; | ||||
|       document.querySelector('#federated-avatar-lookup input').checked = false; | ||||
|       document.querySelector<HTMLInputElement>('#disable-gravatar input').checked = true; | ||||
|       document.querySelector<HTMLInputElement>('#federated-avatar-lookup input').checked = false; | ||||
|     } | ||||
|   }); | ||||
|   document.querySelector('#disable-gravatar input').addEventListener('change', function () { | ||||
|     if (this.checked) { | ||||
|       document.querySelector('#federated-avatar-lookup input').checked = false; | ||||
|       document.querySelector<HTMLInputElement>('#federated-avatar-lookup input').checked = false; | ||||
|     } else { | ||||
|       document.querySelector('#offline-mode input').checked = false; | ||||
|       document.querySelector<HTMLInputElement>('#offline-mode input').checked = false; | ||||
|     } | ||||
|   }); | ||||
|   document.querySelector('#federated-avatar-lookup input').addEventListener('change', function () { | ||||
|     if (this.checked) { | ||||
|       document.querySelector('#disable-gravatar input').checked = false; | ||||
|       document.querySelector('#offline-mode input').checked = false; | ||||
|       document.querySelector<HTMLInputElement>('#disable-gravatar input').checked = false; | ||||
|       document.querySelector<HTMLInputElement>('#offline-mode input').checked = false; | ||||
|     } | ||||
|   }); | ||||
|   document.querySelector('#enable-openid-signin input').addEventListener('change', function () { | ||||
|     if (this.checked) { | ||||
|       if (!document.querySelector('#disable-registration input').checked) { | ||||
|         document.querySelector('#enable-openid-signup input').checked = true; | ||||
|       if (!document.querySelector<HTMLInputElement>('#disable-registration input').checked) { | ||||
|         document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = true; | ||||
|       } | ||||
|     } else { | ||||
|       document.querySelector('#enable-openid-signup input').checked = false; | ||||
|       document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = false; | ||||
|     } | ||||
|   }); | ||||
|   document.querySelector('#disable-registration input').addEventListener('change', function () { | ||||
|     if (this.checked) { | ||||
|       document.querySelector('#enable-captcha input').checked = false; | ||||
|       document.querySelector('#enable-openid-signup input').checked = false; | ||||
|       document.querySelector<HTMLInputElement>('#enable-captcha input').checked = false; | ||||
|       document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = false; | ||||
|     } else { | ||||
|       document.querySelector('#enable-openid-signup input').checked = true; | ||||
|       document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = true; | ||||
|     } | ||||
|   }); | ||||
|   document.querySelector('#enable-captcha input').addEventListener('change', function () { | ||||
|     if (this.checked) { | ||||
|       document.querySelector('#disable-registration input').checked = false; | ||||
|       document.querySelector<HTMLInputElement>('#disable-registration input').checked = false; | ||||
|     } | ||||
|   }); | ||||
| } | ||||
|   | ||||
| @@ -14,25 +14,25 @@ export function initNotificationsTable() { | ||||
|   window.addEventListener('pageshow', (e) => { | ||||
|     if (e.persisted) { // page was restored from bfcache | ||||
|       const table = document.querySelector('#notification_table'); | ||||
|       const unreadCountEl = document.querySelector('.notifications-unread-count'); | ||||
|       const unreadCountEl = document.querySelector<HTMLElement>('.notifications-unread-count'); | ||||
|       let unreadCount = parseInt(unreadCountEl.textContent); | ||||
|       for (const item of table.querySelectorAll('.notifications-item[data-remove="true"]')) { | ||||
|         item.remove(); | ||||
|         unreadCount -= 1; | ||||
|       } | ||||
|       unreadCountEl.textContent = unreadCount; | ||||
|       unreadCountEl.textContent = String(unreadCount); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   // mark clicked unread links for deletion on bfcache restore | ||||
|   for (const link of table.querySelectorAll('.notifications-item[data-status="1"] .notifications-link')) { | ||||
|     link.addEventListener('click', (e) => { | ||||
|     link.addEventListener('click', (e : MouseEvent & {target: HTMLElement}) => { | ||||
|       e.target.closest('.notifications-item').setAttribute('data-remove', 'true'); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function receiveUpdateCount(event) { | ||||
| async function receiveUpdateCount(event: MessageEvent) { | ||||
|   try { | ||||
|     const data = JSON.parse(event.data); | ||||
|  | ||||
| @@ -50,7 +50,7 @@ export function initNotificationCount() { | ||||
|   if (!document.querySelector('.notification_count')) return; | ||||
|  | ||||
|   let usingPeriodicPoller = false; | ||||
|   const startPeriodicPoller = (timeout, lastCount) => { | ||||
|   const startPeriodicPoller = (timeout: number, lastCount?: number) => { | ||||
|     if (timeout <= 0 || !Number.isFinite(timeout)) return; | ||||
|     usingPeriodicPoller = true; | ||||
|     lastCount = lastCount ?? getCurrentCount(); | ||||
| @@ -72,13 +72,13 @@ export function initNotificationCount() { | ||||
|       type: 'start', | ||||
|       url: `${window.location.origin}${appSubUrl}/user/events`, | ||||
|     }); | ||||
|     worker.port.addEventListener('message', (event) => { | ||||
|     worker.port.addEventListener('message', (event: MessageEvent) => { | ||||
|       if (!event.data || !event.data.type) { | ||||
|         console.error('unknown worker message event', event); | ||||
|         return; | ||||
|       } | ||||
|       if (event.data.type === 'notification-count') { | ||||
|         const _promise = receiveUpdateCount(event.data); | ||||
|         receiveUpdateCount(event); // no await | ||||
|       } else if (event.data.type === 'no-event-source') { | ||||
|         // browser doesn't support EventSource, falling back to periodic poller | ||||
|         if (!usingPeriodicPoller) startPeriodicPoller(notificationSettings.MinTimeout); | ||||
| @@ -118,10 +118,10 @@ export function initNotificationCount() { | ||||
| } | ||||
|  | ||||
| function getCurrentCount() { | ||||
|   return document.querySelector('.notification_count').textContent; | ||||
|   return Number(document.querySelector('.notification_count').textContent ?? '0'); | ||||
| } | ||||
|  | ||||
| async function updateNotificationCountWithCallback(callback, timeout, lastCount) { | ||||
| async function updateNotificationCountWithCallback(callback: (timeout: number, newCount: number) => void, timeout: number, lastCount: number) { | ||||
|   const currentCount = getCurrentCount(); | ||||
|   if (lastCount !== currentCount) { | ||||
|     callback(notificationSettings.MinTimeout, currentCount); | ||||
| @@ -149,10 +149,9 @@ async function updateNotificationTable() { | ||||
|   if (notificationDiv) { | ||||
|     try { | ||||
|       const params = new URLSearchParams(window.location.search); | ||||
|       params.set('div-only', true); | ||||
|       params.set('sequence-number', ++notificationSequenceNumber); | ||||
|       const url = `${appSubUrl}/notifications?${params.toString()}`; | ||||
|       const response = await GET(url); | ||||
|       params.set('div-only', String(true)); | ||||
|       params.set('sequence-number', String(++notificationSequenceNumber)); | ||||
|       const response = await GET(`${appSubUrl}/notifications?${params.toString()}`); | ||||
|  | ||||
|       if (!response.ok) { | ||||
|         throw new Error('Failed to fetch notification table'); | ||||
| @@ -169,7 +168,7 @@ async function updateNotificationTable() { | ||||
|   } | ||||
| } | ||||
|  | ||||
| async function updateNotificationCount() { | ||||
| async function updateNotificationCount(): Promise<number> { | ||||
|   try { | ||||
|     const response = await GET(`${appSubUrl}/notifications/new`); | ||||
|  | ||||
| @@ -185,9 +184,9 @@ async function updateNotificationCount() { | ||||
|       el.textContent = `${data.new}`; | ||||
|     } | ||||
|  | ||||
|     return `${data.new}`; | ||||
|     return data.new as number; | ||||
|   } catch (error) { | ||||
|     console.error(error); | ||||
|     return '0'; | ||||
|     return 0; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| export function initOAuth2SettingsDisableCheckbox() { | ||||
|   for (const e of document.querySelectorAll('.disable-setting')) e.addEventListener('change', ({target}) => { | ||||
|     document.querySelector(e.getAttribute('data-target')).classList.toggle('disabled', target.checked); | ||||
|   }); | ||||
|   for (const el of document.querySelectorAll('.disable-setting')) { | ||||
|     el.addEventListener('change', (e: Event & {target: HTMLInputElement}) => { | ||||
|       document.querySelector(e.target.getAttribute('data-target')).classList.toggle('disabled', e.target.checked); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -34,7 +34,7 @@ export function countAndUpdateViewedFiles() { | ||||
| export function initViewedCheckboxListenerFor() { | ||||
|   for (const form of document.querySelectorAll(`${viewedCheckboxSelector}:not([data-has-viewed-checkbox-listener="true"])`)) { | ||||
|     // To prevent double addition of listeners | ||||
|     form.setAttribute('data-has-viewed-checkbox-listener', true); | ||||
|     form.setAttribute('data-has-viewed-checkbox-listener', String(true)); | ||||
|  | ||||
|     // The checkbox consists of a div containing the real checkbox with its label and the CSRF token, | ||||
|     // hence the actual checkbox first has to be found | ||||
| @@ -67,7 +67,7 @@ export function initViewedCheckboxListenerFor() { | ||||
|       // Unfortunately, actual forms cause too many problems, hence another approach is needed | ||||
|       const files = {}; | ||||
|       files[fileName] = this.checked; | ||||
|       const data = {files}; | ||||
|       const data: Record<string, any> = {files}; | ||||
|       const headCommitSHA = form.getAttribute('data-headcommit'); | ||||
|       if (headCommitSHA) data.headCommitSHA = headCommitSHA; | ||||
|       POST(form.getAttribute('data-link'), {data}); | ||||
|   | ||||
| @@ -35,7 +35,7 @@ function initEditPreviewTab(elForm: HTMLFormElement) { | ||||
| } | ||||
|  | ||||
| export function initRepoEditor() { | ||||
|   const dropzoneUpload = document.querySelector('.page-content.repository.editor.upload .dropzone'); | ||||
|   const dropzoneUpload = document.querySelector<HTMLElement>('.page-content.repository.editor.upload .dropzone'); | ||||
|   if (dropzoneUpload) initDropzone(dropzoneUpload); | ||||
|  | ||||
|   const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area'); | ||||
|   | ||||
| @@ -5,9 +5,10 @@ export function initRepositorySearch() { | ||||
|   repositorySearchForm.addEventListener('change', (e: Event & {target: HTMLFormElement}) => { | ||||
|     e.preventDefault(); | ||||
|  | ||||
|     const formData = new FormData(repositorySearchForm); | ||||
|     const params = new URLSearchParams(formData); | ||||
|  | ||||
|     const params = new URLSearchParams(); | ||||
|     for (const [key, value] of new FormData(repositorySearchForm).entries()) { | ||||
|       params.set(key, value.toString()); | ||||
|     } | ||||
|     if (e.target.name === 'clear-filter') { | ||||
|       params.delete('archived'); | ||||
|       params.delete('fork'); | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import {beforeEach, describe, expect, test, vi} from 'vitest'; | ||||
| import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts'; | ||||
| import {POST} from '../modules/fetch.ts'; | ||||
| import {createSortable} from '../modules/sortable.ts'; | ||||
| import type {SortableEvent} from 'sortablejs'; | ||||
|  | ||||
| vi.mock('../modules/fetch.ts', () => ({ | ||||
|   POST: vi.fn(), | ||||
| @@ -54,8 +55,8 @@ describe('Repository Branch Settings', () => { | ||||
|     vi.mocked(POST).mockResolvedValue({ok: true} as Response); | ||||
|  | ||||
|     // Mock createSortable to capture and execute the onEnd callback | ||||
|     vi.mocked(createSortable).mockImplementation((_el, options) => { | ||||
|       options.onEnd(); | ||||
|     vi.mocked(createSortable).mockImplementation(async (_el: Element, options) => { | ||||
|       options.onEnd(new Event('SortableEvent') as SortableEvent); | ||||
|       return {destroy: vi.fn()}; | ||||
|     }); | ||||
|  | ||||
|   | ||||
| @@ -51,6 +51,7 @@ function makeCollections({mentions, emoji}) { | ||||
| export async function attachTribute(element, {mentions, emoji}) { | ||||
|   const {default: Tribute} = await import(/* webpackChunkName: "tribute" */'tributejs'); | ||||
|   const collections = makeCollections({mentions, emoji}); | ||||
|   // @ts-expect-error TS2351: This expression is not constructable (strange, why) | ||||
|   const tribute = new Tribute({collection: collections, noMatchTemplate: ''}); | ||||
|   tribute.attach(element); | ||||
|   return tribute; | ||||
|   | ||||
							
								
								
									
										15
									
								
								web_src/js/globals.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								web_src/js/globals.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -8,6 +8,17 @@ declare module '*.css' { | ||||
|   export default value; | ||||
| } | ||||
|  | ||||
| declare module '*.vue' { | ||||
|   import type {DefineComponent} from 'vue'; | ||||
|   const component: DefineComponent<unknown, unknown, any>; | ||||
|   export default component; | ||||
|   // List of named exports from vue components, used to make `tsc` output clean. | ||||
|   // To actually lint .vue files, `vue-tsc` is used because `tsc` can not parse them. | ||||
|   export function initRepoBranchTagSelector(selector: string): void; | ||||
|   export function initDashboardRepoList(): void; | ||||
|   export function initRepositoryActionView(): void; | ||||
| } | ||||
|  | ||||
| declare let __webpack_public_path__: string; | ||||
|  | ||||
| declare module 'htmx.org/dist/htmx.esm.js' { | ||||
| @@ -16,8 +27,8 @@ declare module 'htmx.org/dist/htmx.esm.js' { | ||||
| } | ||||
|  | ||||
| declare module 'uint8-to-base64' { | ||||
|   export function encode(arrayBuffer: ArrayBuffer): string; | ||||
|   export function decode(base64str: string): ArrayBuffer; | ||||
|   export function encode(arrayBuffer: Uint8Array): string; | ||||
|   export function decode(base64str: string): Uint8Array; | ||||
| } | ||||
|  | ||||
| declare module 'swagger-ui-dist/swagger-ui-es-bundle.js' { | ||||
|   | ||||
| @@ -16,7 +16,6 @@ export function createTippy(target: Element, opts: TippyOpts = {}): Instance { | ||||
|   // because we should use our own wrapper functions to handle them, do not let the user override them | ||||
|   const {onHide, onShow, onDestroy, role, theme, arrow, ...other} = opts; | ||||
|  | ||||
|   // @ts-expect-error: wrong type derived by typescript | ||||
|   const instance: Instance = tippy(target, { | ||||
|     appendTo: document.body, | ||||
|     animation: false, | ||||
|   | ||||
| @@ -134,16 +134,16 @@ export function toAbsoluteUrl(url: string): string { | ||||
|   return `${window.location.origin}${url}`; | ||||
| } | ||||
|  | ||||
| // Encode an ArrayBuffer into a URLEncoded base64 string. | ||||
| export function encodeURLEncodedBase64(arrayBuffer: ArrayBuffer): string { | ||||
|   return encode(arrayBuffer) | ||||
| // Encode an Uint8Array into a URLEncoded base64 string. | ||||
| export function encodeURLEncodedBase64(uint8Array: Uint8Array): string { | ||||
|   return encode(uint8Array) | ||||
|     .replace(/\+/g, '-') | ||||
|     .replace(/\//g, '_') | ||||
|     .replace(/=/g, ''); | ||||
| } | ||||
|  | ||||
| // Decode a URLEncoded base64 to an ArrayBuffer. | ||||
| export function decodeURLEncodedBase64(base64url: string): ArrayBuffer { | ||||
| // Decode a URLEncoded base64 to an Uint8Array. | ||||
| export function decodeURLEncodedBase64(base64url: string): Uint8Array { | ||||
|   return decode(base64url | ||||
|     .replace(/_/g, '/') | ||||
|     .replace(/-/g, '+')); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user