mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 10:56:10 +01:00 
			
		
		
		
	Enable Typescript strictFunctionTypes (#32911)
				
					
				
			1. Enable [strictFunctionTypes](https://www.typescriptlang.org/tsconfig/#strictFunctionTypes) 2. Introduce `DOMEvent` helper type which sets `e.target`. Surely not totally correct with that `Partial` but seems to work. 3. Various type-related refactors, change objects in `eventsource.sharedworker.ts` to `Map`.
This commit is contained in:
		| @@ -22,6 +22,7 @@ | |||||||
|     "verbatimModuleSyntax": true, |     "verbatimModuleSyntax": true, | ||||||
|     "stripInternal": true, |     "stripInternal": true, | ||||||
|     "strict": false, |     "strict": false, | ||||||
|  |     "strictFunctionTypes": true, | ||||||
|     "noUnusedLocals": true, |     "noUnusedLocals": true, | ||||||
|     "noUnusedParameters": true, |     "noUnusedParameters": true, | ||||||
|     "noPropertyAccessFromIndexSignature": false, |     "noPropertyAccessFromIndexSignature": false, | ||||||
|   | |||||||
| @@ -25,10 +25,9 @@ const body = computed(() => { | |||||||
| const root = ref<HTMLElement | null>(null); | const root = ref<HTMLElement | null>(null); | ||||||
|  |  | ||||||
| onMounted(() => { | onMounted(() => { | ||||||
|   root.value.addEventListener('ce-load-context-popup', (e: CustomEvent) => { |   root.value.addEventListener('ce-load-context-popup', (e: CustomEventInit<IssuePathInfo>) => { | ||||||
|     const data: IssuePathInfo = e.detail; |  | ||||||
|     if (!loading.value && issue.value === null) { |     if (!loading.value && issue.value === null) { | ||||||
|       load(data); |       load(e.detail); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import {showTemporaryTooltip} from '../modules/tippy.ts'; | import {showTemporaryTooltip} from '../modules/tippy.ts'; | ||||||
| import {toAbsoluteUrl} from '../utils.ts'; | import {toAbsoluteUrl} from '../utils.ts'; | ||||||
| import {clippie} from 'clippie'; | import {clippie} from 'clippie'; | ||||||
|  | import type {DOMEvent} from '../utils/dom.ts'; | ||||||
|  |  | ||||||
| const {copy_success, copy_error} = window.config.i18n; | const {copy_success, copy_error} = window.config.i18n; | ||||||
|  |  | ||||||
| @@ -9,7 +10,7 @@ const {copy_success, copy_error} = window.config.i18n; | |||||||
| // - data-clipboard-target: Holds a selector for a <input> or <textarea> whose content is copied | // - data-clipboard-target: Holds a selector for a <input> or <textarea> whose content is copied | ||||||
| // - data-clipboard-text-type: When set to 'url' will convert relative to absolute urls | // - data-clipboard-text-type: When set to 'url' will convert relative to absolute urls | ||||||
| export function initGlobalCopyToClipboardListener() { | export function initGlobalCopyToClipboardListener() { | ||||||
|   document.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => { |   document.addEventListener('click', async (e: DOMEvent<MouseEvent>) => { | ||||||
|     const target = e.target.closest('[data-clipboard-text], [data-clipboard-target]'); |     const target = e.target.closest('[data-clipboard-text], [data-clipboard-target]'); | ||||||
|     if (!target) return; |     if (!target) return; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import {createTippy} from '../modules/tippy.ts'; | import {createTippy} from '../modules/tippy.ts'; | ||||||
|  | import type {DOMEvent} from '../utils/dom.ts'; | ||||||
|  |  | ||||||
| export async function initColorPickers() { | export async function initColorPickers() { | ||||||
|   const els = document.querySelectorAll<HTMLElement>('.js-color-picker-input'); |   const els = document.querySelectorAll<HTMLElement>('.js-color-picker-input'); | ||||||
| @@ -37,7 +38,7 @@ function initPicker(el: HTMLElement): void { | |||||||
|     updateSquare(square, e.detail.value); |     updateSquare(square, e.detail.value); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   input.addEventListener('input', (e: Event & {target: HTMLInputElement}) => { |   input.addEventListener('input', (e: DOMEvent<Event, HTMLInputElement>) => { | ||||||
|     updateSquare(square, e.target.value); |     updateSquare(square, e.target.value); | ||||||
|     updatePicker(picker, e.target.value); |     updatePicker(picker, e.target.value); | ||||||
|   }); |   }); | ||||||
| @@ -55,8 +56,8 @@ function initPicker(el: HTMLElement): void { | |||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   // init precolors |   // init precolors | ||||||
|   for (const colorEl of el.querySelectorAll('.precolors .color')) { |   for (const colorEl of el.querySelectorAll<HTMLElement>('.precolors .color')) { | ||||||
|     colorEl.addEventListener('click', (e: MouseEvent & {target: HTMLAnchorElement}) => { |     colorEl.addEventListener('click', (e: DOMEvent<MouseEvent, HTMLAnchorElement>) => { | ||||||
|       const newValue = e.target.getAttribute('data-color-hex'); |       const newValue = e.target.getAttribute('data-color-hex'); | ||||||
|       input.value = newValue; |       input.value = newValue; | ||||||
|       input.dispatchEvent(new Event('input', {bubbles: true})); |       input.dispatchEvent(new Event('input', {bubbles: true})); | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import {applyAreYouSure, initAreYouSure} from '../vendor/jquery.are-you-sure.ts'; | import {applyAreYouSure, initAreYouSure} from '../vendor/jquery.are-you-sure.ts'; | ||||||
| import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.ts'; | import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.ts'; | ||||||
| import {queryElems} from '../utils/dom.ts'; | import {queryElems, type DOMEvent} from '../utils/dom.ts'; | ||||||
| import {initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts'; | import {initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts'; | ||||||
|  |  | ||||||
| export function initGlobalFormDirtyLeaveConfirm() { | export function initGlobalFormDirtyLeaveConfirm() { | ||||||
| @@ -13,7 +13,7 @@ export function initGlobalFormDirtyLeaveConfirm() { | |||||||
| } | } | ||||||
|  |  | ||||||
| export function initGlobalEnterQuickSubmit() { | export function initGlobalEnterQuickSubmit() { | ||||||
|   document.addEventListener('keydown', (e: KeyboardEvent & {target: HTMLElement}) => { |   document.addEventListener('keydown', (e: DOMEvent<KeyboardEvent>) => { | ||||||
|     if (e.key !== 'Enter') return; |     if (e.key !== 'Enter') return; | ||||||
|     const hasCtrlOrMeta = ((e.ctrlKey || e.metaKey) && !e.altKey); |     const hasCtrlOrMeta = ((e.ctrlKey || e.metaKey) && !e.altKey); | ||||||
|     if (hasCtrlOrMeta && e.target.matches('textarea')) { |     if (hasCtrlOrMeta && e.target.matches('textarea')) { | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import {showElem} from '../../utils/dom.ts'; | import {showElem, type DOMEvent} from '../../utils/dom.ts'; | ||||||
|  |  | ||||||
| type CropperOpts = { | type CropperOpts = { | ||||||
|   container: HTMLElement, |   container: HTMLElement, | ||||||
| @@ -26,7 +26,7 @@ export async function initCompCropper({container, fileInput, imageSource}: Cropp | |||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   fileInput.addEventListener('input', (e: Event & {target: HTMLInputElement}) => { |   fileInput.addEventListener('input', (e: DOMEvent<Event, HTMLInputElement>) => { | ||||||
|     const files = e.target.files; |     const files = e.target.files; | ||||||
|     if (files?.length > 0) { |     if (files?.length > 0) { | ||||||
|       currentFileName = files[0].name; |       currentFileName = files[0].name; | ||||||
|   | |||||||
| @@ -1,9 +1,10 @@ | |||||||
| import {POST} from '../../modules/fetch.ts'; | import {POST} from '../../modules/fetch.ts'; | ||||||
| import {fomanticQuery} from '../../modules/fomantic/base.ts'; | import {fomanticQuery} from '../../modules/fomantic/base.ts'; | ||||||
|  | import type {DOMEvent} from '../../utils/dom.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<HTMLElement>('.issue-content, .diff-file-body')) { | ||||||
|     container.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => { |     container.addEventListener('click', async (e: DOMEvent<MouseEvent>) => { | ||||||
|       // 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; | ||||||
|   | |||||||
| @@ -1,13 +1,10 @@ | |||||||
| const sourcesByUrl = {}; |  | ||||||
| const sourcesByPort = {}; |  | ||||||
|  |  | ||||||
| class Source { | class Source { | ||||||
|   url: string; |   url: string; | ||||||
|   eventSource: EventSource; |   eventSource: EventSource; | ||||||
|   listening: Record<string, any>; |   listening: Record<string, boolean>; | ||||||
|   clients: Array<any>; |   clients: Array<MessagePort>; | ||||||
|  |  | ||||||
|   constructor(url) { |   constructor(url: string) { | ||||||
|     this.url = url; |     this.url = url; | ||||||
|     this.eventSource = new EventSource(url); |     this.eventSource = new EventSource(url); | ||||||
|     this.listening = {}; |     this.listening = {}; | ||||||
| @@ -20,7 +17,7 @@ class Source { | |||||||
|     this.listen('error'); |     this.listen('error'); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   register(port) { |   register(port: MessagePort) { | ||||||
|     if (this.clients.includes(port)) return; |     if (this.clients.includes(port)) return; | ||||||
|  |  | ||||||
|     this.clients.push(port); |     this.clients.push(port); | ||||||
| @@ -31,7 +28,7 @@ class Source { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   deregister(port) { |   deregister(port: MessagePort) { | ||||||
|     const portIdx = this.clients.indexOf(port); |     const portIdx = this.clients.indexOf(port); | ||||||
|     if (portIdx < 0) { |     if (portIdx < 0) { | ||||||
|       return this.clients.length; |       return this.clients.length; | ||||||
| @@ -47,7 +44,7 @@ class Source { | |||||||
|     this.eventSource = null; |     this.eventSource = null; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   listen(eventType) { |   listen(eventType: string) { | ||||||
|     if (this.listening[eventType]) return; |     if (this.listening[eventType]) return; | ||||||
|     this.listening[eventType] = true; |     this.listening[eventType] = true; | ||||||
|     this.eventSource.addEventListener(eventType, (event) => { |     this.eventSource.addEventListener(eventType, (event) => { | ||||||
| @@ -58,13 +55,13 @@ class Source { | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   notifyClients(event) { |   notifyClients(event: {type: string, data: any}) { | ||||||
|     for (const client of this.clients) { |     for (const client of this.clients) { | ||||||
|       client.postMessage(event); |       client.postMessage(event); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   status(port) { |   status(port: MessagePort) { | ||||||
|     port.postMessage({ |     port.postMessage({ | ||||||
|       type: 'status', |       type: 'status', | ||||||
|       message: `url: ${this.url} readyState: ${this.eventSource.readyState}`, |       message: `url: ${this.url} readyState: ${this.eventSource.readyState}`, | ||||||
| @@ -72,7 +69,11 @@ class Source { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| self.addEventListener('connect', (e: Event & {ports: Array<any>}) => { | const sourcesByUrl: Map<string, Source | null> = new Map(); | ||||||
|  | const sourcesByPort: Map<MessagePort, Source | null> = new Map(); | ||||||
|  |  | ||||||
|  | // @ts-expect-error: typescript bug? | ||||||
|  | self.addEventListener('connect', (e: MessageEvent) => { | ||||||
|   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) { | ||||||
| @@ -84,14 +85,14 @@ self.addEventListener('connect', (e: Event & {ports: Array<any>}) => { | |||||||
|       } |       } | ||||||
|       if (event.data.type === 'start') { |       if (event.data.type === 'start') { | ||||||
|         const url = event.data.url; |         const url = event.data.url; | ||||||
|         if (sourcesByUrl[url]) { |         if (sourcesByUrl.get(url)) { | ||||||
|           // we have a Source registered to this url |           // we have a Source registered to this url | ||||||
|           const source = sourcesByUrl[url]; |           const source = sourcesByUrl.get(url); | ||||||
|           source.register(port); |           source.register(port); | ||||||
|           sourcesByPort[port] = source; |           sourcesByPort.set(port, source); | ||||||
|           return; |           return; | ||||||
|         } |         } | ||||||
|         let source = sourcesByPort[port]; |         let source = sourcesByPort.get(port); | ||||||
|         if (source) { |         if (source) { | ||||||
|           if (source.eventSource && source.url === url) return; |           if (source.eventSource && source.url === url) return; | ||||||
|  |  | ||||||
| @@ -101,30 +102,30 @@ self.addEventListener('connect', (e: Event & {ports: Array<any>}) => { | |||||||
|           // Clean-up |           // Clean-up | ||||||
|           if (count === 0) { |           if (count === 0) { | ||||||
|             source.close(); |             source.close(); | ||||||
|             sourcesByUrl[source.url] = null; |             sourcesByUrl.set(source.url, null); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|         // Create a new Source |         // Create a new Source | ||||||
|         source = new Source(url); |         source = new Source(url); | ||||||
|         source.register(port); |         source.register(port); | ||||||
|         sourcesByUrl[url] = source; |         sourcesByUrl.set(url, source); | ||||||
|         sourcesByPort[port] = source; |         sourcesByPort.set(port, source); | ||||||
|       } else if (event.data.type === 'listen') { |       } else if (event.data.type === 'listen') { | ||||||
|         const source = sourcesByPort[port]; |         const source = sourcesByPort.get(port); | ||||||
|         source.listen(event.data.eventType); |         source.listen(event.data.eventType); | ||||||
|       } else if (event.data.type === 'close') { |       } else if (event.data.type === 'close') { | ||||||
|         const source = sourcesByPort[port]; |         const source = sourcesByPort.get(port); | ||||||
|  |  | ||||||
|         if (!source) return; |         if (!source) return; | ||||||
|  |  | ||||||
|         const count = source.deregister(port); |         const count = source.deregister(port); | ||||||
|         if (count === 0) { |         if (count === 0) { | ||||||
|           source.close(); |           source.close(); | ||||||
|           sourcesByUrl[source.url] = null; |           sourcesByUrl.set(source.url, null); | ||||||
|           sourcesByPort[port] = null; |           sourcesByPort.set(port, null); | ||||||
|         } |         } | ||||||
|       } else if (event.data.type === 'status') { |       } else if (event.data.type === 'status') { | ||||||
|         const source = sourcesByPort[port]; |         const source = sourcesByPort.get(port); | ||||||
|         if (!source) { |         if (!source) { | ||||||
|           port.postMessage({ |           port.postMessage({ | ||||||
|             type: 'status', |             type: 'status', | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import $ from 'jquery'; | import $ from 'jquery'; | ||||||
| import {GET} from '../modules/fetch.ts'; | import {GET} from '../modules/fetch.ts'; | ||||||
| import {toggleElem} from '../utils/dom.ts'; | import {toggleElem, type DOMEvent} from '../utils/dom.ts'; | ||||||
| import {logoutFromWorker} from '../modules/worker.ts'; | import {logoutFromWorker} from '../modules/worker.ts'; | ||||||
|  |  | ||||||
| const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config; | const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config; | ||||||
| @@ -25,8 +25,8 @@ export function initNotificationsTable() { | |||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   // 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<HTMLAnchorElement>('.notifications-item[data-status="1"] .notifications-link')) { | ||||||
|     link.addEventListener('click', (e : MouseEvent & {target: HTMLElement}) => { |     link.addEventListener('click', (e: DOMEvent<MouseEvent>) => { | ||||||
|       e.target.closest('.notifications-item').setAttribute('data-remove', 'true'); |       e.target.closest('.notifications-item').setAttribute('data-remove', 'true'); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
|  | import type {DOMEvent} from '../utils/dom.ts'; | ||||||
|  |  | ||||||
| export function initOAuth2SettingsDisableCheckbox() { | export function initOAuth2SettingsDisableCheckbox() { | ||||||
|   for (const el of document.querySelectorAll('.disable-setting')) { |   for (const el of document.querySelectorAll<HTMLInputElement>('.disable-setting')) { | ||||||
|     el.addEventListener('change', (e: Event & {target: HTMLInputElement}) => { |     el.addEventListener('change', (e: DOMEvent<Event, HTMLInputElement>) => { | ||||||
|       document.querySelector(e.target.getAttribute('data-target')).classList.toggle('disabled', e.target.checked); |       document.querySelector(e.target.getAttribute('data-target')).classList.toggle('disabled', e.target.checked); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| import {hideElem, showElem} from '../utils/dom.ts'; | import {hideElem, showElem, type DOMEvent} from '../utils/dom.ts'; | ||||||
| import {GET} from '../modules/fetch.ts'; | import {GET} from '../modules/fetch.ts'; | ||||||
| import {fomanticQuery} from '../modules/fomantic/base.ts'; | import {fomanticQuery} from '../modules/fomantic/base.ts'; | ||||||
|  |  | ||||||
| export function initRepoGraphGit() { | export function initRepoGraphGit() { | ||||||
|   const graphContainer = document.querySelector('#git-graph-container'); |   const graphContainer = document.querySelector<HTMLElement>('#git-graph-container'); | ||||||
|   if (!graphContainer) return; |   if (!graphContainer) return; | ||||||
|  |  | ||||||
|   document.querySelector('#flow-color-monochrome')?.addEventListener('click', () => { |   document.querySelector('#flow-color-monochrome')?.addEventListener('click', () => { | ||||||
| @@ -87,7 +87,7 @@ export function initRepoGraphGit() { | |||||||
|   fomanticQuery(flowSelectRefsDropdown).dropdown({ |   fomanticQuery(flowSelectRefsDropdown).dropdown({ | ||||||
|     clearable: true, |     clearable: true, | ||||||
|     fullTextSeach: 'exact', |     fullTextSeach: 'exact', | ||||||
|     onRemove(toRemove) { |     onRemove(toRemove: string) { | ||||||
|       if (toRemove === '...flow-hide-pr-refs') { |       if (toRemove === '...flow-hide-pr-refs') { | ||||||
|         params.delete('hide-pr-refs'); |         params.delete('hide-pr-refs'); | ||||||
|       } else { |       } else { | ||||||
| @@ -101,7 +101,7 @@ export function initRepoGraphGit() { | |||||||
|       } |       } | ||||||
|       updateGraph(); |       updateGraph(); | ||||||
|     }, |     }, | ||||||
|     onAdd(toAdd) { |     onAdd(toAdd: string) { | ||||||
|       if (toAdd === '...flow-hide-pr-refs') { |       if (toAdd === '...flow-hide-pr-refs') { | ||||||
|         params.set('hide-pr-refs', 'true'); |         params.set('hide-pr-refs', 'true'); | ||||||
|       } else { |       } else { | ||||||
| @@ -111,7 +111,7 @@ export function initRepoGraphGit() { | |||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   graphContainer.addEventListener('mouseenter', (e: MouseEvent & {target: HTMLElement}) => { |   graphContainer.addEventListener('mouseenter', (e: DOMEvent<MouseEvent>) => { | ||||||
|     if (e.target.matches('#rev-list li')) { |     if (e.target.matches('#rev-list li')) { | ||||||
|       const flow = e.target.getAttribute('data-flow'); |       const flow = e.target.getAttribute('data-flow'); | ||||||
|       if (flow === '0') return; |       if (flow === '0') return; | ||||||
| @@ -132,7 +132,7 @@ export function initRepoGraphGit() { | |||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   graphContainer.addEventListener('mouseleave', (e: MouseEvent & {target: HTMLElement}) => { |   graphContainer.addEventListener('mouseleave', (e: DOMEvent<MouseEvent>) => { | ||||||
|     if (e.target.matches('#rev-list li')) { |     if (e.target.matches('#rev-list li')) { | ||||||
|       const flow = e.target.getAttribute('data-flow'); |       const flow = e.target.getAttribute('data-flow'); | ||||||
|       if (flow === '0') return; |       if (flow === '0') return; | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import {stripTags} from '../utils.ts'; | import {stripTags} from '../utils.ts'; | ||||||
| import {hideElem, queryElemChildren, showElem} from '../utils/dom.ts'; | import {hideElem, queryElemChildren, showElem, type DOMEvent} from '../utils/dom.ts'; | ||||||
| import {POST} from '../modules/fetch.ts'; | import {POST} from '../modules/fetch.ts'; | ||||||
| import {showErrorToast, type Toast} from '../modules/toast.ts'; | import {showErrorToast, type Toast} from '../modules/toast.ts'; | ||||||
| import {fomanticQuery} from '../modules/fomantic/base.ts'; | import {fomanticQuery} from '../modules/fomantic/base.ts'; | ||||||
| @@ -28,7 +28,7 @@ export function initRepoTopicBar() { | |||||||
|     mgrBtn.focus(); |     mgrBtn.focus(); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   document.querySelector('#save_topic').addEventListener('click', async (e: MouseEvent & {target: HTMLButtonElement}) => { |   document.querySelector<HTMLButtonElement>('#save_topic').addEventListener('click', async (e: DOMEvent<MouseEvent, HTMLButtonElement>) => { | ||||||
|     lastErrorToast?.hideToast(); |     lastErrorToast?.hideToast(); | ||||||
|     const topics = editDiv.querySelector<HTMLInputElement>('input[name=topics]').value; |     const topics = editDiv.querySelector<HTMLInputElement>('input[name=topics]').value; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import { | |||||||
|   queryElems, |   queryElems, | ||||||
|   showElem, |   showElem, | ||||||
|   toggleElem, |   toggleElem, | ||||||
|  |   type DOMEvent, | ||||||
| } from '../utils/dom.ts'; | } from '../utils/dom.ts'; | ||||||
| import {setFileFolding} from './file-fold.ts'; | import {setFileFolding} from './file-fold.ts'; | ||||||
| import {ComboMarkdownEditor, getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts'; | import {ComboMarkdownEditor, getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts'; | ||||||
| @@ -53,7 +54,7 @@ export function initRepoIssueSidebarList() { | |||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| function initRepoIssueLabelFilter(elDropdown: Element) { | function initRepoIssueLabelFilter(elDropdown: HTMLElement) { | ||||||
|   const url = new URL(window.location.href); |   const url = new URL(window.location.href); | ||||||
|   const showArchivedLabels = url.searchParams.get('archived_labels') === 'true'; |   const showArchivedLabels = url.searchParams.get('archived_labels') === 'true'; | ||||||
|   const queryLabels = url.searchParams.get('labels') || ''; |   const queryLabels = url.searchParams.get('labels') || ''; | ||||||
| @@ -125,7 +126,7 @@ export function initRepoIssueFilterItemLabel() { | |||||||
|  |  | ||||||
| export function initRepoIssueCommentDelete() { | export function initRepoIssueCommentDelete() { | ||||||
|   // Delete comment |   // Delete comment | ||||||
|   document.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => { |   document.addEventListener('click', async (e: DOMEvent<MouseEvent>) => { | ||||||
|     if (!e.target.matches('.delete-comment')) return; |     if (!e.target.matches('.delete-comment')) return; | ||||||
|     e.preventDefault(); |     e.preventDefault(); | ||||||
|  |  | ||||||
| @@ -200,7 +201,7 @@ export function initRepoIssueDependencyDelete() { | |||||||
|  |  | ||||||
| export function initRepoIssueCodeCommentCancel() { | export function initRepoIssueCodeCommentCancel() { | ||||||
|   // Cancel inline code comment |   // Cancel inline code comment | ||||||
|   document.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => { |   document.addEventListener('click', (e: DOMEvent<MouseEvent>) => { | ||||||
|     if (!e.target.matches('.cancel-code-comment')) return; |     if (!e.target.matches('.cancel-code-comment')) return; | ||||||
|  |  | ||||||
|     const form = e.target.closest('form'); |     const form = e.target.closest('form'); | ||||||
| @@ -222,7 +223,7 @@ export function initRepoPullRequestUpdate() { | |||||||
|     e.preventDefault(); |     e.preventDefault(); | ||||||
|     const redirect = this.getAttribute('data-redirect'); |     const redirect = this.getAttribute('data-redirect'); | ||||||
|     this.classList.add('is-loading'); |     this.classList.add('is-loading'); | ||||||
|     let response; |     let response: Response; | ||||||
|     try { |     try { | ||||||
|       response = await POST(this.getAttribute('data-do')); |       response = await POST(this.getAttribute('data-do')); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
| @@ -230,7 +231,7 @@ export function initRepoPullRequestUpdate() { | |||||||
|     } finally { |     } finally { | ||||||
|       this.classList.remove('is-loading'); |       this.classList.remove('is-loading'); | ||||||
|     } |     } | ||||||
|     let data; |     let data: Record<string, any>; | ||||||
|     try { |     try { | ||||||
|       data = await response?.json(); // the response is probably not a JSON |       data = await response?.json(); // the response is probably not a JSON | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
| @@ -341,7 +342,7 @@ export function initRepoIssueWipTitle() { | |||||||
| export function initRepoIssueComments() { | export function initRepoIssueComments() { | ||||||
|   if (!$('.repository.view.issue .timeline').length) return; |   if (!$('.repository.view.issue .timeline').length) return; | ||||||
|  |  | ||||||
|   document.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => { |   document.addEventListener('click', (e: DOMEvent<MouseEvent>) => { | ||||||
|     const urlTarget = document.querySelector(':target'); |     const urlTarget = document.querySelector(':target'); | ||||||
|     if (!urlTarget) return; |     if (!urlTarget) return; | ||||||
|  |  | ||||||
| @@ -589,7 +590,7 @@ export function initRepoIssueTitleEdit() { | |||||||
| } | } | ||||||
|  |  | ||||||
| export function initRepoIssueBranchSelect() { | export function initRepoIssueBranchSelect() { | ||||||
|   document.querySelector('#branch-select')?.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => { |   document.querySelector<HTMLElement>('#branch-select')?.addEventListener('click', (e: DOMEvent<MouseEvent>) => { | ||||||
|     const el = e.target.closest('.item[data-branch]'); |     const el = e.target.closest('.item[data-branch]'); | ||||||
|     if (!el) return; |     if (!el) return; | ||||||
|     const pullTargetBranch = document.querySelector('#pull-target-branch'); |     const pullTargetBranch = document.querySelector('#pull-target-branch'); | ||||||
| @@ -628,10 +629,10 @@ function initIssueTemplateCommentEditors($commentForm) { | |||||||
|   // * new issue with issue template |   // * new issue with issue template | ||||||
|   const $comboFields = $commentForm.find('.combo-editor-dropzone'); |   const $comboFields = $commentForm.find('.combo-editor-dropzone'); | ||||||
|  |  | ||||||
|   const initCombo = async (elCombo) => { |   const initCombo = async (elCombo: HTMLElement) => { | ||||||
|     const $formField = $(elCombo.querySelector('.form-field-real')); |     const $formField = $(elCombo.querySelector('.form-field-real')); | ||||||
|     const dropzoneContainer = elCombo.querySelector('.form-field-dropzone'); |     const dropzoneContainer = elCombo.querySelector<HTMLElement>('.form-field-dropzone'); | ||||||
|     const markdownEditor = elCombo.querySelector('.combo-markdown-editor'); |     const markdownEditor = elCombo.querySelector<HTMLElement>('.combo-markdown-editor'); | ||||||
|  |  | ||||||
|     const editor = await initComboMarkdownEditor(markdownEditor); |     const editor = await initComboMarkdownEditor(markdownEditor); | ||||||
|     editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, () => $formField.val(editor.value())); |     editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, () => $formField.val(editor.value())); | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import {hideElem, showElem} from '../utils/dom.ts'; | import {hideElem, showElem, type DOMEvent} from '../utils/dom.ts'; | ||||||
|  |  | ||||||
| export function initRepoRelease() { | export function initRepoRelease() { | ||||||
|   document.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => { |   document.addEventListener('click', (e: DOMEvent<MouseEvent>) => { | ||||||
|     if (e.target.matches('.remove-rel-attach')) { |     if (e.target.matches('.remove-rel-attach')) { | ||||||
|       const uuid = e.target.getAttribute('data-uuid'); |       const uuid = e.target.getAttribute('data-uuid'); | ||||||
|       const id = e.target.getAttribute('data-id'); |       const id = e.target.getAttribute('data-id'); | ||||||
| @@ -42,7 +42,7 @@ function initTagNameEditor() { | |||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|   hideTargetInput(tagNameInput); // update on page load because the input may have a value |   hideTargetInput(tagNameInput); // update on page load because the input may have a value | ||||||
|   tagNameInput.addEventListener('input', (e: InputEvent & {target: HTMLInputElement}) => { |   tagNameInput.addEventListener('input', (e) => { | ||||||
|     hideTargetInput(e.target); |     hideTargetInput(e.target as HTMLInputElement); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,8 +1,10 @@ | |||||||
|  | import type {DOMEvent} from '../utils/dom.ts'; | ||||||
|  |  | ||||||
| export function initRepositorySearch() { | export function initRepositorySearch() { | ||||||
|   const repositorySearchForm = document.querySelector<HTMLFormElement>('#repo-search-form'); |   const repositorySearchForm = document.querySelector<HTMLFormElement>('#repo-search-form'); | ||||||
|   if (!repositorySearchForm) return; |   if (!repositorySearchForm) return; | ||||||
|  |  | ||||||
|   repositorySearchForm.addEventListener('change', (e: Event & {target: HTMLFormElement}) => { |   repositorySearchForm.addEventListener('change', (e: DOMEvent<Event, HTMLInputElement>) => { | ||||||
|     e.preventDefault(); |     e.preventDefault(); | ||||||
|  |  | ||||||
|     const params = new URLSearchParams(); |     const params = new URLSearchParams(); | ||||||
|   | |||||||
| @@ -122,7 +122,7 @@ function initRepoSettingsBranches() { | |||||||
| function initRepoSettingsOptions() { | function initRepoSettingsOptions() { | ||||||
|   if ($('.repository.settings.options').length > 0) { |   if ($('.repository.settings.options').length > 0) { | ||||||
|     // Enable or select internal/external wiki system and issue tracker. |     // Enable or select internal/external wiki system and issue tracker. | ||||||
|     $('.enable-system').on('change', function (this: HTMLInputElement) { |     $('.enable-system').on('change', function (this: HTMLInputElement) { // eslint-disable-line @typescript-eslint/no-deprecated | ||||||
|       if (this.checked) { |       if (this.checked) { | ||||||
|         $($(this).data('target')).removeClass('disabled'); |         $($(this).data('target')).removeClass('disabled'); | ||||||
|         if (!$(this).data('context')) $($(this).data('context')).addClass('disabled'); |         if (!$(this).data('context')) $($(this).data('context')).addClass('disabled'); | ||||||
| @@ -131,7 +131,7 @@ function initRepoSettingsOptions() { | |||||||
|         if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled'); |         if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled'); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|     $('.enable-system-radio').on('change', function (this: HTMLInputElement) { |     $('.enable-system-radio').on('change', function (this: HTMLInputElement) { // eslint-disable-line @typescript-eslint/no-deprecated | ||||||
|       if (this.value === 'false') { |       if (this.value === 'false') { | ||||||
|         $($(this).data('target')).addClass('disabled'); |         $($(this).data('target')).addClass('disabled'); | ||||||
|         if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled'); |         if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled'); | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ async function initRepoWikiFormEditor() { | |||||||
|   let editor: ComboMarkdownEditor; |   let editor: ComboMarkdownEditor; | ||||||
|  |  | ||||||
|   let renderRequesting = false; |   let renderRequesting = false; | ||||||
|   let lastContent; |   let lastContent: string = ''; | ||||||
|   const renderEasyMDEPreview = async function () { |   const renderEasyMDEPreview = async function () { | ||||||
|     if (renderRequesting) return; |     if (renderRequesting) return; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,13 +9,13 @@ window.htmx.config.requestClass = 'is-loading'; | |||||||
| window.htmx.config.scrollIntoViewOnBoost = false; | window.htmx.config.scrollIntoViewOnBoost = false; | ||||||
|  |  | ||||||
| // https://htmx.org/events/#htmx:sendError | // https://htmx.org/events/#htmx:sendError | ||||||
| document.body.addEventListener('htmx:sendError', (event: HtmxEvent) => { | document.body.addEventListener('htmx:sendError', (event: Partial<HtmxEvent>) => { | ||||||
|   // TODO: add translations |   // TODO: add translations | ||||||
|   showErrorToast(`Network error when calling ${event.detail.requestConfig.path}`); |   showErrorToast(`Network error when calling ${event.detail.requestConfig.path}`); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // https://htmx.org/events/#htmx:responseError | // https://htmx.org/events/#htmx:responseError | ||||||
| document.body.addEventListener('htmx:responseError', (event: HtmxEvent) => { | document.body.addEventListener('htmx:responseError', (event: Partial<HtmxEvent>) => { | ||||||
|   // TODO: add translations |   // TODO: add translations | ||||||
|   showErrorToast(`Error ${event.detail.xhr.status} when calling ${event.detail.requestConfig.path}`); |   showErrorToast(`Error ${event.detail.xhr.status} when calling ${event.detail.requestConfig.path}`); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ type ProcessorContext = { | |||||||
|  |  | ||||||
| function prepareProcessors(ctx:ProcessorContext): Processors { | function prepareProcessors(ctx:ProcessorContext): Processors { | ||||||
|   const processors = { |   const processors = { | ||||||
|     H1(el: HTMLHeadingElement) { |     H1(el: HTMLElement) { | ||||||
|       const level = parseInt(el.tagName.slice(1)); |       const level = parseInt(el.tagName.slice(1)); | ||||||
|       el.textContent = `${'#'.repeat(level)} ${el.textContent.trim()}`; |       el.textContent = `${'#'.repeat(level)} ${el.textContent.trim()}`; | ||||||
|     }, |     }, | ||||||
| @@ -25,7 +25,7 @@ function prepareProcessors(ctx:ProcessorContext): Processors { | |||||||
|     DEL(el: HTMLElement) { |     DEL(el: HTMLElement) { | ||||||
|       return `~~${el.textContent}~~`; |       return `~~${el.textContent}~~`; | ||||||
|     }, |     }, | ||||||
|     A(el: HTMLAnchorElement) { |     A(el: HTMLElement) { | ||||||
|       const text = el.textContent || 'link'; |       const text = el.textContent || 'link'; | ||||||
|       const href = el.getAttribute('href'); |       const href = el.getAttribute('href'); | ||||||
|       if (/^https?:/.test(text) && text === href) { |       if (/^https?:/.test(text) && text === href) { | ||||||
| @@ -33,7 +33,7 @@ function prepareProcessors(ctx:ProcessorContext): Processors { | |||||||
|       } |       } | ||||||
|       return href ? `[${text}](${href})` : text; |       return href ? `[${text}](${href})` : text; | ||||||
|     }, |     }, | ||||||
|     IMG(el: HTMLImageElement) { |     IMG(el: HTMLElement) { | ||||||
|       const alt = el.getAttribute('alt') || 'image'; |       const alt = el.getAttribute('alt') || 'image'; | ||||||
|       const src = el.getAttribute('src'); |       const src = el.getAttribute('src'); | ||||||
|       const widthAttr = el.hasAttribute('width') ? ` width="${htmlEscape(el.getAttribute('width') || '')}"` : ''; |       const widthAttr = el.hasAttribute('width') ? ` width="${htmlEscape(el.getAttribute('width') || '')}"` : ''; | ||||||
| @@ -43,7 +43,7 @@ function prepareProcessors(ctx:ProcessorContext): Processors { | |||||||
|       } |       } | ||||||
|       return ``; |       return ``; | ||||||
|     }, |     }, | ||||||
|     P(el: HTMLParagraphElement) { |     P(el: HTMLElement) { | ||||||
|       el.textContent = `${el.textContent}\n`; |       el.textContent = `${el.textContent}\n`; | ||||||
|     }, |     }, | ||||||
|     BLOCKQUOTE(el: HTMLElement) { |     BLOCKQUOTE(el: HTMLElement) { | ||||||
| @@ -54,14 +54,14 @@ function prepareProcessors(ctx:ProcessorContext): Processors { | |||||||
|       el.textContent = `${preNewLine}${el.textContent}\n`; |       el.textContent = `${preNewLine}${el.textContent}\n`; | ||||||
|     }, |     }, | ||||||
|     LI(el: HTMLElement) { |     LI(el: HTMLElement) { | ||||||
|       const parent = el.parentNode; |       const parent = el.parentNode as HTMLElement; | ||||||
|       const bullet = (parent as HTMLElement).tagName === 'OL' ? `1. ` : '* '; |       const bullet = parent.tagName === 'OL' ? `1. ` : '* '; | ||||||
|       const nestingIdentLevel = Math.max(0, ctx.listNestingLevel - 1); |       const nestingIdentLevel = Math.max(0, ctx.listNestingLevel - 1); | ||||||
|       el.textContent = `${' '.repeat(nestingIdentLevel * 4)}${bullet}${el.textContent}${ctx.elementIsLast ? '' : '\n'}`; |       el.textContent = `${' '.repeat(nestingIdentLevel * 4)}${bullet}${el.textContent}${ctx.elementIsLast ? '' : '\n'}`; | ||||||
|       return el; |       return el; | ||||||
|     }, |     }, | ||||||
|     INPUT(el: HTMLInputElement) { |     INPUT(el: HTMLElement) { | ||||||
|       return el.checked ? '[x] ' : '[ ] '; |       return (el as HTMLInputElement).checked ? '[x] ' : '[ ] '; | ||||||
|     }, |     }, | ||||||
|     CODE(el: HTMLElement) { |     CODE(el: HTMLElement) { | ||||||
|       const text = el.textContent; |       const text = el.textContent; | ||||||
|   | |||||||
| @@ -121,14 +121,14 @@ function switchTitleToTooltip(target: Element): void { | |||||||
|  * Some browsers like PaleMoon don't support "addEventListener('mouseenter', capture)" |  * Some browsers like PaleMoon don't support "addEventListener('mouseenter', capture)" | ||||||
|  * The tippy by default uses "mouseenter" event to show, so we use "mouseover" event to switch to tippy |  * The tippy by default uses "mouseenter" event to show, so we use "mouseover" event to switch to tippy | ||||||
|  */ |  */ | ||||||
| function lazyTooltipOnMouseHover(e: MouseEvent): void { | function lazyTooltipOnMouseHover(e: Event): void { | ||||||
|   e.target.removeEventListener('mouseover', lazyTooltipOnMouseHover, true); |   e.target.removeEventListener('mouseover', lazyTooltipOnMouseHover, true); | ||||||
|   attachTooltip(this); |   attachTooltip(this); | ||||||
| } | } | ||||||
|  |  | ||||||
| // Activate the tooltip for current element. | // Activate the tooltip for current element. | ||||||
| // If the element has no aria-label, use the tooltip content as aria-label. | // If the element has no aria-label, use the tooltip content as aria-label. | ||||||
| function attachLazyTooltip(el: Element): void { | function attachLazyTooltip(el: HTMLElement): void { | ||||||
|   el.addEventListener('mouseover', lazyTooltipOnMouseHover, {capture: true}); |   el.addEventListener('mouseover', lazyTooltipOnMouseHover, {capture: true}); | ||||||
|  |  | ||||||
|   // meanwhile, if the element has no aria-label, use the tooltip content as aria-label |   // meanwhile, if the element has no aria-label, use the tooltip content as aria-label | ||||||
| @@ -141,8 +141,8 @@ function attachLazyTooltip(el: Element): void { | |||||||
| } | } | ||||||
|  |  | ||||||
| // Activate the tooltip for all children elements. | // Activate the tooltip for all children elements. | ||||||
| function attachChildrenLazyTooltip(target: Element): void { | function attachChildrenLazyTooltip(target: HTMLElement): void { | ||||||
|   for (const el of target.querySelectorAll<Element>('[data-tooltip-content]')) { |   for (const el of target.querySelectorAll<HTMLElement>('[data-tooltip-content]')) { | ||||||
|     attachLazyTooltip(el); |     attachLazyTooltip(el); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -160,7 +160,7 @@ export function initGlobalTooltips(): void { | |||||||
|     for (const mutation of [...mutationList, ...pending]) { |     for (const mutation of [...mutationList, ...pending]) { | ||||||
|       if (mutation.type === 'childList') { |       if (mutation.type === 'childList') { | ||||||
|         // mainly for Vue components and AJAX rendered elements |         // mainly for Vue components and AJAX rendered elements | ||||||
|         for (const el of mutation.addedNodes as NodeListOf<Element>) { |         for (const el of mutation.addedNodes as NodeListOf<HTMLElement>) { | ||||||
|           if (!isDocumentFragmentOrElementNode(el)) continue; |           if (!isDocumentFragmentOrElementNode(el)) continue; | ||||||
|           attachChildrenLazyTooltip(el); |           attachChildrenLazyTooltip(el); | ||||||
|           if (el.hasAttribute('data-tooltip-content')) { |           if (el.hasAttribute('data-tooltip-content')) { | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ type ArrayLikeIterable<T> = ArrayLike<T> & Iterable<T>; // for NodeListOf and Ar | |||||||
| type ElementArg = Element | string | ArrayLikeIterable<Element> | ReturnType<typeof $>; | type ElementArg = Element | string | ArrayLikeIterable<Element> | ReturnType<typeof $>; | ||||||
| type ElementsCallback<T extends Element> = (el: T) => Promisable<any>; | type ElementsCallback<T extends Element> = (el: T) => Promisable<any>; | ||||||
| type ElementsCallbackWithArgs = (el: Element, ...args: any[]) => Promisable<any>; | type ElementsCallbackWithArgs = (el: Element, ...args: any[]) => Promisable<any>; | ||||||
|  | export type DOMEvent<E extends Event, T extends Element = HTMLElement> = E & { target: Partial<T>; }; | ||||||
|  |  | ||||||
| function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]) { | function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]) { | ||||||
|   if (typeof el === 'string' || el instanceof String) { |   if (typeof el === 'string' || el instanceof String) { | ||||||
| @@ -87,7 +88,7 @@ export function queryElemChildren<T extends Element>(parent: Element | ParentNod | |||||||
|  |  | ||||||
| // it works like parent.querySelectorAll: all descendants are selected | // it works like parent.querySelectorAll: all descendants are selected | ||||||
| // in the future, all "queryElems(document, ...)" should be refactored to use a more specific parent | // in the future, all "queryElems(document, ...)" should be refactored to use a more specific parent | ||||||
| export function queryElems<T extends Element>(parent: Element | ParentNode, selector: string, fn?: ElementsCallback<T>): ArrayLikeIterable<T> { | export function queryElems<T extends HTMLElement>(parent: Element | ParentNode, selector: string, fn?: ElementsCallback<T>): ArrayLikeIterable<T> { | ||||||
|   return applyElemsCallback<T>(parent.querySelectorAll(selector), fn); |   return applyElemsCallback<T>(parent.querySelectorAll(selector), fn); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user