mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-27 00:23:41 +09: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, | ||||
|     "stripInternal": true, | ||||
|     "strict": false, | ||||
|     "strictFunctionTypes": true, | ||||
|     "noUnusedLocals": true, | ||||
|     "noUnusedParameters": true, | ||||
|     "noPropertyAccessFromIndexSignature": false, | ||||
|   | ||||
| @@ -25,10 +25,9 @@ const body = computed(() => { | ||||
| const root = ref<HTMLElement | null>(null); | ||||
|  | ||||
| onMounted(() => { | ||||
|   root.value.addEventListener('ce-load-context-popup', (e: CustomEvent) => { | ||||
|     const data: IssuePathInfo = e.detail; | ||||
|   root.value.addEventListener('ce-load-context-popup', (e: CustomEventInit<IssuePathInfo>) => { | ||||
|     if (!loading.value && issue.value === null) { | ||||
|       load(data); | ||||
|       load(e.detail); | ||||
|     } | ||||
|   }); | ||||
| }); | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import {showTemporaryTooltip} from '../modules/tippy.ts'; | ||||
| import {toAbsoluteUrl} from '../utils.ts'; | ||||
| import {clippie} from 'clippie'; | ||||
| import type {DOMEvent} from '../utils/dom.ts'; | ||||
|  | ||||
| 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-text-type: When set to 'url' will convert relative to absolute urls | ||||
| 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]'); | ||||
|     if (!target) return; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import {createTippy} from '../modules/tippy.ts'; | ||||
| import type {DOMEvent} from '../utils/dom.ts'; | ||||
|  | ||||
| export async function initColorPickers() { | ||||
|   const els = document.querySelectorAll<HTMLElement>('.js-color-picker-input'); | ||||
| @@ -37,7 +38,7 @@ function initPicker(el: HTMLElement): void { | ||||
|     updateSquare(square, e.detail.value); | ||||
|   }); | ||||
|  | ||||
|   input.addEventListener('input', (e: Event & {target: HTMLInputElement}) => { | ||||
|   input.addEventListener('input', (e: DOMEvent<Event, HTMLInputElement>) => { | ||||
|     updateSquare(square, e.target.value); | ||||
|     updatePicker(picker, e.target.value); | ||||
|   }); | ||||
| @@ -55,8 +56,8 @@ function initPicker(el: HTMLElement): void { | ||||
|   }); | ||||
|  | ||||
|   // init precolors | ||||
|   for (const colorEl of el.querySelectorAll('.precolors .color')) { | ||||
|     colorEl.addEventListener('click', (e: MouseEvent & {target: HTMLAnchorElement}) => { | ||||
|   for (const colorEl of el.querySelectorAll<HTMLElement>('.precolors .color')) { | ||||
|     colorEl.addEventListener('click', (e: DOMEvent<MouseEvent, HTMLAnchorElement>) => { | ||||
|       const newValue = e.target.getAttribute('data-color-hex'); | ||||
|       input.value = newValue; | ||||
|       input.dispatchEvent(new Event('input', {bubbles: true})); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import {applyAreYouSure, initAreYouSure} from '../vendor/jquery.are-you-sure.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'; | ||||
|  | ||||
| export function initGlobalFormDirtyLeaveConfirm() { | ||||
| @@ -13,7 +13,7 @@ export function initGlobalFormDirtyLeaveConfirm() { | ||||
| } | ||||
|  | ||||
| export function initGlobalEnterQuickSubmit() { | ||||
|   document.addEventListener('keydown', (e: KeyboardEvent & {target: HTMLElement}) => { | ||||
|   document.addEventListener('keydown', (e: DOMEvent<KeyboardEvent>) => { | ||||
|     if (e.key !== 'Enter') return; | ||||
|     const hasCtrlOrMeta = ((e.ctrlKey || e.metaKey) && !e.altKey); | ||||
|     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 = { | ||||
|   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; | ||||
|     if (files?.length > 0) { | ||||
|       currentFileName = files[0].name; | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| import {POST} from '../../modules/fetch.ts'; | ||||
| import {fomanticQuery} from '../../modules/fomantic/base.ts'; | ||||
| import type {DOMEvent} from '../../utils/dom.ts'; | ||||
|  | ||||
| export function initCompReactionSelector(parent: ParentNode = document) { | ||||
|   for (const container of parent.querySelectorAll('.issue-content, .diff-file-body')) { | ||||
|     container.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => { | ||||
|   for (const container of parent.querySelectorAll<HTMLElement>('.issue-content, .diff-file-body')) { | ||||
|     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 | ||||
|       const target = e.target.closest('.comment-reaction-button'); | ||||
|       if (!target) return; | ||||
|   | ||||
| @@ -1,13 +1,10 @@ | ||||
| const sourcesByUrl = {}; | ||||
| const sourcesByPort = {}; | ||||
|  | ||||
| class Source { | ||||
|   url: string; | ||||
|   eventSource: EventSource; | ||||
|   listening: Record<string, any>; | ||||
|   clients: Array<any>; | ||||
|   listening: Record<string, boolean>; | ||||
|   clients: Array<MessagePort>; | ||||
|  | ||||
|   constructor(url) { | ||||
|   constructor(url: string) { | ||||
|     this.url = url; | ||||
|     this.eventSource = new EventSource(url); | ||||
|     this.listening = {}; | ||||
| @@ -20,7 +17,7 @@ class Source { | ||||
|     this.listen('error'); | ||||
|   } | ||||
|  | ||||
|   register(port) { | ||||
|   register(port: MessagePort) { | ||||
|     if (this.clients.includes(port)) return; | ||||
|  | ||||
|     this.clients.push(port); | ||||
| @@ -31,7 +28,7 @@ class Source { | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   deregister(port) { | ||||
|   deregister(port: MessagePort) { | ||||
|     const portIdx = this.clients.indexOf(port); | ||||
|     if (portIdx < 0) { | ||||
|       return this.clients.length; | ||||
| @@ -47,7 +44,7 @@ class Source { | ||||
|     this.eventSource = null; | ||||
|   } | ||||
|  | ||||
|   listen(eventType) { | ||||
|   listen(eventType: string) { | ||||
|     if (this.listening[eventType]) return; | ||||
|     this.listening[eventType] = true; | ||||
|     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) { | ||||
|       client.postMessage(event); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   status(port) { | ||||
|   status(port: MessagePort) { | ||||
|     port.postMessage({ | ||||
|       type: 'status', | ||||
|       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) { | ||||
|     port.addEventListener('message', (event) => { | ||||
|       if (!self.EventSource) { | ||||
| @@ -84,14 +85,14 @@ self.addEventListener('connect', (e: Event & {ports: Array<any>}) => { | ||||
|       } | ||||
|       if (event.data.type === 'start') { | ||||
|         const url = event.data.url; | ||||
|         if (sourcesByUrl[url]) { | ||||
|         if (sourcesByUrl.get(url)) { | ||||
|           // we have a Source registered to this url | ||||
|           const source = sourcesByUrl[url]; | ||||
|           const source = sourcesByUrl.get(url); | ||||
|           source.register(port); | ||||
|           sourcesByPort[port] = source; | ||||
|           sourcesByPort.set(port, source); | ||||
|           return; | ||||
|         } | ||||
|         let source = sourcesByPort[port]; | ||||
|         let source = sourcesByPort.get(port); | ||||
|         if (source) { | ||||
|           if (source.eventSource && source.url === url) return; | ||||
|  | ||||
| @@ -101,30 +102,30 @@ self.addEventListener('connect', (e: Event & {ports: Array<any>}) => { | ||||
|           // Clean-up | ||||
|           if (count === 0) { | ||||
|             source.close(); | ||||
|             sourcesByUrl[source.url] = null; | ||||
|             sourcesByUrl.set(source.url, null); | ||||
|           } | ||||
|         } | ||||
|         // Create a new Source | ||||
|         source = new Source(url); | ||||
|         source.register(port); | ||||
|         sourcesByUrl[url] = source; | ||||
|         sourcesByPort[port] = source; | ||||
|         sourcesByUrl.set(url, source); | ||||
|         sourcesByPort.set(port, source); | ||||
|       } else if (event.data.type === 'listen') { | ||||
|         const source = sourcesByPort[port]; | ||||
|         const source = sourcesByPort.get(port); | ||||
|         source.listen(event.data.eventType); | ||||
|       } else if (event.data.type === 'close') { | ||||
|         const source = sourcesByPort[port]; | ||||
|         const source = sourcesByPort.get(port); | ||||
|  | ||||
|         if (!source) return; | ||||
|  | ||||
|         const count = source.deregister(port); | ||||
|         if (count === 0) { | ||||
|           source.close(); | ||||
|           sourcesByUrl[source.url] = null; | ||||
|           sourcesByPort[port] = null; | ||||
|           sourcesByUrl.set(source.url, null); | ||||
|           sourcesByPort.set(port, null); | ||||
|         } | ||||
|       } else if (event.data.type === 'status') { | ||||
|         const source = sourcesByPort[port]; | ||||
|         const source = sourcesByPort.get(port); | ||||
|         if (!source) { | ||||
|           port.postMessage({ | ||||
|             type: 'status', | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import $ from 'jquery'; | ||||
| 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'; | ||||
|  | ||||
| const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config; | ||||
| @@ -25,8 +25,8 @@ export function initNotificationsTable() { | ||||
|   }); | ||||
|  | ||||
|   // mark clicked unread links for deletion on bfcache restore | ||||
|   for (const link of table.querySelectorAll('.notifications-item[data-status="1"] .notifications-link')) { | ||||
|     link.addEventListener('click', (e : MouseEvent & {target: HTMLElement}) => { | ||||
|   for (const link of table.querySelectorAll<HTMLAnchorElement>('.notifications-item[data-status="1"] .notifications-link')) { | ||||
|     link.addEventListener('click', (e: DOMEvent<MouseEvent>) => { | ||||
|       e.target.closest('.notifications-item').setAttribute('data-remove', 'true'); | ||||
|     }); | ||||
|   } | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| import type {DOMEvent} from '../utils/dom.ts'; | ||||
|  | ||||
| export function initOAuth2SettingsDisableCheckbox() { | ||||
|   for (const el of document.querySelectorAll('.disable-setting')) { | ||||
|     el.addEventListener('change', (e: Event & {target: HTMLInputElement}) => { | ||||
|   for (const el of document.querySelectorAll<HTMLInputElement>('.disable-setting')) { | ||||
|     el.addEventListener('change', (e: DOMEvent<Event, HTMLInputElement>) => { | ||||
|       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 {fomanticQuery} from '../modules/fomantic/base.ts'; | ||||
|  | ||||
| export function initRepoGraphGit() { | ||||
|   const graphContainer = document.querySelector('#git-graph-container'); | ||||
|   const graphContainer = document.querySelector<HTMLElement>('#git-graph-container'); | ||||
|   if (!graphContainer) return; | ||||
|  | ||||
|   document.querySelector('#flow-color-monochrome')?.addEventListener('click', () => { | ||||
| @@ -87,7 +87,7 @@ export function initRepoGraphGit() { | ||||
|   fomanticQuery(flowSelectRefsDropdown).dropdown({ | ||||
|     clearable: true, | ||||
|     fullTextSeach: 'exact', | ||||
|     onRemove(toRemove) { | ||||
|     onRemove(toRemove: string) { | ||||
|       if (toRemove === '...flow-hide-pr-refs') { | ||||
|         params.delete('hide-pr-refs'); | ||||
|       } else { | ||||
| @@ -101,7 +101,7 @@ export function initRepoGraphGit() { | ||||
|       } | ||||
|       updateGraph(); | ||||
|     }, | ||||
|     onAdd(toAdd) { | ||||
|     onAdd(toAdd: string) { | ||||
|       if (toAdd === '...flow-hide-pr-refs') { | ||||
|         params.set('hide-pr-refs', 'true'); | ||||
|       } 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')) { | ||||
|       const flow = e.target.getAttribute('data-flow'); | ||||
|       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')) { | ||||
|       const flow = e.target.getAttribute('data-flow'); | ||||
|       if (flow === '0') return; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| 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 {showErrorToast, type Toast} from '../modules/toast.ts'; | ||||
| import {fomanticQuery} from '../modules/fomantic/base.ts'; | ||||
| @@ -28,7 +28,7 @@ export function initRepoTopicBar() { | ||||
|     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(); | ||||
|     const topics = editDiv.querySelector<HTMLInputElement>('input[name=topics]').value; | ||||
|  | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import { | ||||
|   queryElems, | ||||
|   showElem, | ||||
|   toggleElem, | ||||
|   type DOMEvent, | ||||
| } from '../utils/dom.ts'; | ||||
| import {setFileFolding} from './file-fold.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 showArchivedLabels = url.searchParams.get('archived_labels') === 'true'; | ||||
|   const queryLabels = url.searchParams.get('labels') || ''; | ||||
| @@ -125,7 +126,7 @@ export function initRepoIssueFilterItemLabel() { | ||||
|  | ||||
| export function initRepoIssueCommentDelete() { | ||||
|   // Delete comment | ||||
|   document.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => { | ||||
|   document.addEventListener('click', async (e: DOMEvent<MouseEvent>) => { | ||||
|     if (!e.target.matches('.delete-comment')) return; | ||||
|     e.preventDefault(); | ||||
|  | ||||
| @@ -200,7 +201,7 @@ export function initRepoIssueDependencyDelete() { | ||||
|  | ||||
| export function initRepoIssueCodeCommentCancel() { | ||||
|   // 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; | ||||
|  | ||||
|     const form = e.target.closest('form'); | ||||
| @@ -222,7 +223,7 @@ export function initRepoPullRequestUpdate() { | ||||
|     e.preventDefault(); | ||||
|     const redirect = this.getAttribute('data-redirect'); | ||||
|     this.classList.add('is-loading'); | ||||
|     let response; | ||||
|     let response: Response; | ||||
|     try { | ||||
|       response = await POST(this.getAttribute('data-do')); | ||||
|     } catch (error) { | ||||
| @@ -230,7 +231,7 @@ export function initRepoPullRequestUpdate() { | ||||
|     } finally { | ||||
|       this.classList.remove('is-loading'); | ||||
|     } | ||||
|     let data; | ||||
|     let data: Record<string, any>; | ||||
|     try { | ||||
|       data = await response?.json(); // the response is probably not a JSON | ||||
|     } catch (error) { | ||||
| @@ -341,7 +342,7 @@ export function initRepoIssueWipTitle() { | ||||
| export function initRepoIssueComments() { | ||||
|   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'); | ||||
|     if (!urlTarget) return; | ||||
|  | ||||
| @@ -589,7 +590,7 @@ export function initRepoIssueTitleEdit() { | ||||
| } | ||||
|  | ||||
| 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]'); | ||||
|     if (!el) return; | ||||
|     const pullTargetBranch = document.querySelector('#pull-target-branch'); | ||||
| @@ -628,10 +629,10 @@ function initIssueTemplateCommentEditors($commentForm) { | ||||
|   // * new issue with issue template | ||||
|   const $comboFields = $commentForm.find('.combo-editor-dropzone'); | ||||
|  | ||||
|   const initCombo = async (elCombo) => { | ||||
|   const initCombo = async (elCombo: HTMLElement) => { | ||||
|     const $formField = $(elCombo.querySelector('.form-field-real')); | ||||
|     const dropzoneContainer = elCombo.querySelector('.form-field-dropzone'); | ||||
|     const markdownEditor = elCombo.querySelector('.combo-markdown-editor'); | ||||
|     const dropzoneContainer = elCombo.querySelector<HTMLElement>('.form-field-dropzone'); | ||||
|     const markdownEditor = elCombo.querySelector<HTMLElement>('.combo-markdown-editor'); | ||||
|  | ||||
|     const editor = await initComboMarkdownEditor(markdownEditor); | ||||
|     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() { | ||||
|   document.addEventListener('click', (e: MouseEvent & {target: HTMLElement}) => { | ||||
|   document.addEventListener('click', (e: DOMEvent<MouseEvent>) => { | ||||
|     if (e.target.matches('.remove-rel-attach')) { | ||||
|       const uuid = e.target.getAttribute('data-uuid'); | ||||
|       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 | ||||
|   tagNameInput.addEventListener('input', (e: InputEvent & {target: HTMLInputElement}) => { | ||||
|     hideTargetInput(e.target); | ||||
|   tagNameInput.addEventListener('input', (e) => { | ||||
|     hideTargetInput(e.target as HTMLInputElement); | ||||
|   }); | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,10 @@ | ||||
| import type {DOMEvent} from '../utils/dom.ts'; | ||||
|  | ||||
| export function initRepositorySearch() { | ||||
|   const repositorySearchForm = document.querySelector<HTMLFormElement>('#repo-search-form'); | ||||
|   if (!repositorySearchForm) return; | ||||
|  | ||||
|   repositorySearchForm.addEventListener('change', (e: Event & {target: HTMLFormElement}) => { | ||||
|   repositorySearchForm.addEventListener('change', (e: DOMEvent<Event, HTMLInputElement>) => { | ||||
|     e.preventDefault(); | ||||
|  | ||||
|     const params = new URLSearchParams(); | ||||
|   | ||||
| @@ -122,7 +122,7 @@ function initRepoSettingsBranches() { | ||||
| function initRepoSettingsOptions() { | ||||
|   if ($('.repository.settings.options').length > 0) { | ||||
|     // 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) { | ||||
|         $($(this).data('target')).removeClass('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'); | ||||
|       } | ||||
|     }); | ||||
|     $('.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') { | ||||
|         $($(this).data('target')).addClass('disabled'); | ||||
|         if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled'); | ||||
|   | ||||
| @@ -13,7 +13,7 @@ async function initRepoWikiFormEditor() { | ||||
|   let editor: ComboMarkdownEditor; | ||||
|  | ||||
|   let renderRequesting = false; | ||||
|   let lastContent; | ||||
|   let lastContent: string = ''; | ||||
|   const renderEasyMDEPreview = async function () { | ||||
|     if (renderRequesting) return; | ||||
|  | ||||
|   | ||||
| @@ -9,13 +9,13 @@ window.htmx.config.requestClass = 'is-loading'; | ||||
| window.htmx.config.scrollIntoViewOnBoost = false; | ||||
|  | ||||
| // https://htmx.org/events/#htmx:sendError | ||||
| document.body.addEventListener('htmx:sendError', (event: HtmxEvent) => { | ||||
| document.body.addEventListener('htmx:sendError', (event: Partial<HtmxEvent>) => { | ||||
|   // TODO: add translations | ||||
|   showErrorToast(`Network error when calling ${event.detail.requestConfig.path}`); | ||||
| }); | ||||
|  | ||||
| // https://htmx.org/events/#htmx:responseError | ||||
| document.body.addEventListener('htmx:responseError', (event: HtmxEvent) => { | ||||
| document.body.addEventListener('htmx:responseError', (event: Partial<HtmxEvent>) => { | ||||
|   // TODO: add translations | ||||
|   showErrorToast(`Error ${event.detail.xhr.status} when calling ${event.detail.requestConfig.path}`); | ||||
| }); | ||||
|   | ||||
| @@ -12,7 +12,7 @@ type ProcessorContext = { | ||||
|  | ||||
| function prepareProcessors(ctx:ProcessorContext): Processors { | ||||
|   const processors = { | ||||
|     H1(el: HTMLHeadingElement) { | ||||
|     H1(el: HTMLElement) { | ||||
|       const level = parseInt(el.tagName.slice(1)); | ||||
|       el.textContent = `${'#'.repeat(level)} ${el.textContent.trim()}`; | ||||
|     }, | ||||
| @@ -25,7 +25,7 @@ function prepareProcessors(ctx:ProcessorContext): Processors { | ||||
|     DEL(el: HTMLElement) { | ||||
|       return `~~${el.textContent}~~`; | ||||
|     }, | ||||
|     A(el: HTMLAnchorElement) { | ||||
|     A(el: HTMLElement) { | ||||
|       const text = el.textContent || 'link'; | ||||
|       const href = el.getAttribute('href'); | ||||
|       if (/^https?:/.test(text) && text === href) { | ||||
| @@ -33,7 +33,7 @@ function prepareProcessors(ctx:ProcessorContext): Processors { | ||||
|       } | ||||
|       return href ? `[${text}](${href})` : text; | ||||
|     }, | ||||
|     IMG(el: HTMLImageElement) { | ||||
|     IMG(el: HTMLElement) { | ||||
|       const alt = el.getAttribute('alt') || 'image'; | ||||
|       const src = el.getAttribute('src'); | ||||
|       const widthAttr = el.hasAttribute('width') ? ` width="${htmlEscape(el.getAttribute('width') || '')}"` : ''; | ||||
| @@ -43,7 +43,7 @@ function prepareProcessors(ctx:ProcessorContext): Processors { | ||||
|       } | ||||
|       return ``; | ||||
|     }, | ||||
|     P(el: HTMLParagraphElement) { | ||||
|     P(el: HTMLElement) { | ||||
|       el.textContent = `${el.textContent}\n`; | ||||
|     }, | ||||
|     BLOCKQUOTE(el: HTMLElement) { | ||||
| @@ -54,14 +54,14 @@ function prepareProcessors(ctx:ProcessorContext): Processors { | ||||
|       el.textContent = `${preNewLine}${el.textContent}\n`; | ||||
|     }, | ||||
|     LI(el: HTMLElement) { | ||||
|       const parent = el.parentNode; | ||||
|       const bullet = (parent as HTMLElement).tagName === 'OL' ? `1. ` : '* '; | ||||
|       const parent = el.parentNode as HTMLElement; | ||||
|       const bullet = parent.tagName === 'OL' ? `1. ` : '* '; | ||||
|       const nestingIdentLevel = Math.max(0, ctx.listNestingLevel - 1); | ||||
|       el.textContent = `${' '.repeat(nestingIdentLevel * 4)}${bullet}${el.textContent}${ctx.elementIsLast ? '' : '\n'}`; | ||||
|       return el; | ||||
|     }, | ||||
|     INPUT(el: HTMLInputElement) { | ||||
|       return el.checked ? '[x] ' : '[ ] '; | ||||
|     INPUT(el: HTMLElement) { | ||||
|       return (el as HTMLInputElement).checked ? '[x] ' : '[ ] '; | ||||
|     }, | ||||
|     CODE(el: HTMLElement) { | ||||
|       const text = el.textContent; | ||||
|   | ||||
| @@ -121,14 +121,14 @@ function switchTitleToTooltip(target: Element): void { | ||||
|  * 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 | ||||
|  */ | ||||
| function lazyTooltipOnMouseHover(e: MouseEvent): void { | ||||
| function lazyTooltipOnMouseHover(e: Event): void { | ||||
|   e.target.removeEventListener('mouseover', lazyTooltipOnMouseHover, true); | ||||
|   attachTooltip(this); | ||||
| } | ||||
|  | ||||
| // Activate the tooltip for current element. | ||||
| // 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}); | ||||
|  | ||||
|   // 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. | ||||
| function attachChildrenLazyTooltip(target: Element): void { | ||||
|   for (const el of target.querySelectorAll<Element>('[data-tooltip-content]')) { | ||||
| function attachChildrenLazyTooltip(target: HTMLElement): void { | ||||
|   for (const el of target.querySelectorAll<HTMLElement>('[data-tooltip-content]')) { | ||||
|     attachLazyTooltip(el); | ||||
|   } | ||||
| } | ||||
| @@ -160,7 +160,7 @@ export function initGlobalTooltips(): void { | ||||
|     for (const mutation of [...mutationList, ...pending]) { | ||||
|       if (mutation.type === 'childList') { | ||||
|         // 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; | ||||
|           attachChildrenLazyTooltip(el); | ||||
|           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 ElementsCallback<T extends Element> = (el: T) => 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[]) { | ||||
|   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 | ||||
| // 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); | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user