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