diff options
author | silverwind <me@silverwind.io> | 2024-11-21 14:57:42 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-21 13:57:42 +0000 |
commit | 675c288811364ac08146c7e8038de23d5584c29d (patch) | |
tree | dff7e7a814e022078a3453d7d36b6dda8bec2253 | |
parent | 9bf821ae6c108379d22ae11d8d5784a4ed7ad647 (diff) | |
download | gitea-675c288811364ac08146c7e8038de23d5584c29d.tar.gz gitea-675c288811364ac08146c7e8038de23d5584c29d.zip |
Fix some typescript issues (#32586)
Fixes around 30 or so typescript errors. No runtime changes.
24 files changed, 89 insertions, 73 deletions
diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 081e4a2db4..88ae98db8d 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -642,7 +642,7 @@ rules: no-this-before-super: [2] no-throw-literal: [2] no-undef-init: [2] - no-undef: [2, {typeof: true}] + no-undef: [2, {typeof: true}] # TODO: disable this rule after tsc passes no-undefined: [0] no-underscore-dangle: [0] no-unexpected-multiline: [2] diff --git a/web_src/js/features/autofocus-end.ts b/web_src/js/features/autofocus-end.ts index da71ce9536..53e475b543 100644 --- a/web_src/js/features/autofocus-end.ts +++ b/web_src/js/features/autofocus-end.ts @@ -1,5 +1,5 @@ export function initAutoFocusEnd() { - for (const el of document.querySelectorAll('.js-autofocus-end')) { + for (const el of document.querySelectorAll<HTMLInputElement>('.js-autofocus-end')) { el.focus(); // expects only one such element on one page. If there are many, then the last one gets the focus. el.setSelectionRange(el.value.length, el.value.length); } diff --git a/web_src/js/features/captcha.ts b/web_src/js/features/captcha.ts index 23dbae3740..69b4aa6852 100644 --- a/web_src/js/features/captcha.ts +++ b/web_src/js/features/captcha.ts @@ -35,9 +35,11 @@ export async function initCaptcha() { } case 'm-captcha': { const {default: mCaptcha} = await import(/* webpackChunkName: "mcaptcha-vanilla-glue" */'@mcaptcha/vanilla-glue'); + // @ts-expect-error mCaptcha.INPUT_NAME = 'm-captcha-response'; const instanceURL = captchaEl.getAttribute('data-instance-url'); + // @ts-expect-error mCaptcha.default({ siteKey: { instanceUrl: new URL(instanceURL), diff --git a/web_src/js/features/citation.ts b/web_src/js/features/citation.ts index c9b07efe77..8fc6beabfb 100644 --- a/web_src/js/features/citation.ts +++ b/web_src/js/features/citation.ts @@ -3,7 +3,7 @@ import {fomanticQuery} from '../modules/fomantic/base.ts'; const {pageData} = window.config; -async function initInputCitationValue(citationCopyApa, citationCopyBibtex) { +async function initInputCitationValue(citationCopyApa: HTMLButtonElement, citationCopyBibtex: HTMLButtonElement) { const [{Cite, plugins}] = await Promise.all([ import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'), import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'), @@ -27,9 +27,9 @@ export async function initCitationFileCopyContent() { if (!pageData.citationFileContent) return; - const citationCopyApa = document.querySelector('#citation-copy-apa'); - const citationCopyBibtex = document.querySelector('#citation-copy-bibtex'); - const inputContent = document.querySelector('#citation-copy-content'); + const citationCopyApa = document.querySelector<HTMLButtonElement>('#citation-copy-apa'); + const citationCopyBibtex = document.querySelector<HTMLButtonElement>('#citation-copy-bibtex'); + const inputContent = document.querySelector<HTMLInputElement>('#citation-copy-content'); if ((!citationCopyApa && !citationCopyBibtex) || !inputContent) return; @@ -41,7 +41,7 @@ export async function initCitationFileCopyContent() { citationCopyApa.classList.toggle('primary', !isBibtex); }; - document.querySelector('#cite-repo-button')?.addEventListener('click', async (e) => { + document.querySelector('#cite-repo-button')?.addEventListener('click', async (e: MouseEvent & {target: HTMLAnchorElement}) => { const dropdownBtn = e.target.closest('.ui.dropdown.button'); dropdownBtn.classList.add('is-loading'); diff --git a/web_src/js/features/clipboard.ts b/web_src/js/features/clipboard.ts index 8de150d0f9..8f40f34f74 100644 --- a/web_src/js/features/clipboard.ts +++ b/web_src/js/features/clipboard.ts @@ -9,7 +9,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) => { + document.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => { const target = e.target.closest('[data-clipboard-text], [data-clipboard-target]'); if (!target) return; @@ -17,7 +17,7 @@ export function initGlobalCopyToClipboardListener() { let text = target.getAttribute('data-clipboard-text'); if (!text) { - text = document.querySelector(target.getAttribute('data-clipboard-target'))?.value; + text = document.querySelector<HTMLInputElement>(target.getAttribute('data-clipboard-target'))?.value; } if (text && target.getAttribute('data-clipboard-text-type') === 'url') { diff --git a/web_src/js/features/codeeditor.ts b/web_src/js/features/codeeditor.ts index 9a854b2657..ee515e25e2 100644 --- a/web_src/js/features/codeeditor.ts +++ b/web_src/js/features/codeeditor.ts @@ -21,7 +21,7 @@ const baseOptions = { automaticLayout: true, }; -function getEditorconfig(input) { +function getEditorconfig(input: HTMLInputElement) { try { return JSON.parse(input.getAttribute('data-editorconfig')); } catch { @@ -58,7 +58,7 @@ function exportEditor(editor) { if (!window.codeEditors.includes(editor)) window.codeEditors.push(editor); } -export async function createMonaco(textarea, filename, editorOpts) { +export async function createMonaco(textarea: HTMLTextAreaElement, filename: string, editorOpts: Record<string, any>) { const monaco = await import(/* webpackChunkName: "monaco" */'monaco-editor'); initLanguages(monaco); @@ -72,7 +72,7 @@ export async function createMonaco(textarea, filename, editorOpts) { // https://github.com/microsoft/monaco-editor/issues/2427 // also, monaco can only parse 6-digit hex colors, so we convert the colors to that format const styles = window.getComputedStyle(document.documentElement); - const getColor = (name) => tinycolor(styles.getPropertyValue(name).trim()).toString('hex6'); + const getColor = (name: string) => tinycolor(styles.getPropertyValue(name).trim()).toString('hex6'); monaco.editor.defineTheme('gitea', { base: isDarkTheme() ? 'vs-dark' : 'vs', @@ -127,13 +127,13 @@ export async function createMonaco(textarea, filename, editorOpts) { return {monaco, editor}; } -function getFileBasedOptions(filename, lineWrapExts) { +function getFileBasedOptions(filename: string, lineWrapExts: string[]) { return { wordWrap: (lineWrapExts || []).includes(extname(filename)) ? 'on' : 'off', }; } -function togglePreviewDisplay(previewable) { +function togglePreviewDisplay(previewable: boolean) { const previewTab = document.querySelector('a[data-tab="preview"]'); if (!previewTab) return; @@ -152,7 +152,7 @@ function togglePreviewDisplay(previewable) { } } -export async function createCodeEditor(textarea, filenameInput) { +export async function createCodeEditor(textarea: HTMLTextAreaElement, filenameInput: HTMLInputElement) { const filename = basename(filenameInput.value); const previewableExts = new Set((textarea.getAttribute('data-previewable-extensions') || '').split(',')); const lineWrapExts = (textarea.getAttribute('data-line-wrap-extensions') || '').split(','); @@ -177,10 +177,10 @@ export async function createCodeEditor(textarea, filenameInput) { return editor; } -function getEditorConfigOptions(ec) { +function getEditorConfigOptions(ec: Record<string, any>): Record<string, any> { if (!isObject(ec)) return {}; - const opts = {}; + const opts: Record<string, any> = {}; opts.detectIndentation = !('indent_style' in ec) || !('indent_size' in ec); if ('indent_size' in ec) opts.indentSize = Number(ec.indent_size); if ('tab_width' in ec) opts.tabSize = Number(ec.tab_width) || opts.indentSize; diff --git a/web_src/js/features/colorpicker.ts b/web_src/js/features/colorpicker.ts index 3b5f8c66eb..bf20aed545 100644 --- a/web_src/js/features/colorpicker.ts +++ b/web_src/js/features/colorpicker.ts @@ -1,7 +1,7 @@ import {createTippy} from '../modules/tippy.ts'; export async function initColorPickers() { - const els = document.querySelectorAll('.js-color-picker-input'); + const els = document.querySelectorAll<HTMLElement>('.js-color-picker-input'); if (!els.length) return; await Promise.all([ @@ -14,15 +14,15 @@ export async function initColorPickers() { } } -function updateSquare(el, newValue) { +function updateSquare(el: HTMLElement, newValue: string): void { el.style.color = /#[0-9a-f]{6}/i.test(newValue) ? newValue : 'transparent'; } -function updatePicker(el, newValue) { +function updatePicker(el: HTMLElement, newValue: string): void { el.setAttribute('color', newValue); } -function initPicker(el) { +function initPicker(el: HTMLElement): void { const input = el.querySelector('input'); const square = document.createElement('div'); @@ -37,7 +37,7 @@ function initPicker(el) { updateSquare(square, e.detail.value); }); - input.addEventListener('input', (e) => { + input.addEventListener('input', (e: Event & {target: HTMLInputElement}) => { updateSquare(square, e.target.value); updatePicker(picker, e.target.value); }); @@ -56,7 +56,7 @@ function initPicker(el) { // init precolors for (const colorEl of el.querySelectorAll('.precolors .color')) { - colorEl.addEventListener('click', (e) => { + colorEl.addEventListener('click', (e: MouseEvent & {target: HTMLAnchorElement}) => { const newValue = e.target.getAttribute('data-color-hex'); input.value = newValue; input.dispatchEvent(new Event('input', {bubbles: true})); diff --git a/web_src/js/features/common-button.ts b/web_src/js/features/common-button.ts index 7e335ce7b4..4aca5ef8f5 100644 --- a/web_src/js/features/common-button.ts +++ b/web_src/js/features/common-button.ts @@ -3,7 +3,7 @@ import {POST} from '../modules/fetch.ts'; import {hideElem, showElem, toggleElem} from '../utils/dom.ts'; import {showErrorToast} from '../modules/toast.ts'; -export function initGlobalButtonClickOnEnter() { +export function initGlobalButtonClickOnEnter(): void { $(document).on('keypress', 'div.ui.button,span.ui.button', (e) => { if (e.code === ' ' || e.code === 'Enter') { $(e.target).trigger('click'); @@ -12,13 +12,13 @@ export function initGlobalButtonClickOnEnter() { }); } -export function initGlobalDeleteButton() { +export function initGlobalDeleteButton(): void { // ".delete-button" shows a confirmation modal defined by `data-modal-id` attribute. // Some model/form elements will be filled by `data-id` / `data-name` / `data-data-xxx` attributes. // If there is a form defined by `data-form`, then the form will be submitted as-is (without any modification). // If there is no form, then the data will be posted to `data-url`. // TODO: it's not encouraged to use this method. `show-modal` does far better than this. - for (const btn of document.querySelectorAll('.delete-button')) { + for (const btn of document.querySelectorAll<HTMLElement>('.delete-button')) { btn.addEventListener('click', (e) => { e.preventDefault(); @@ -46,7 +46,7 @@ export function initGlobalDeleteButton() { // if `data-type="form"` exists, then submit the form by the selector provided by `data-form="..."` if (btn.getAttribute('data-type') === 'form') { const formSelector = btn.getAttribute('data-form'); - const form = document.querySelector(formSelector); + const form = document.querySelector<HTMLFormElement>(formSelector); if (!form) throw new Error(`no form named ${formSelector} found`); form.submit(); } @@ -73,7 +73,7 @@ export function initGlobalDeleteButton() { } } -export function initGlobalButtons() { +export function initGlobalButtons(): void { // There are many "cancel button" elements in modal dialogs, Fomantic UI expects they are button-like elements but never submit a form. // However, Gitea misuses the modal dialog and put the cancel buttons inside forms, so we must prevent the form submission. // There are a few cancel buttons in non-modal forms, and there are some dynamically created forms (eg: the "Edit Issue Content") diff --git a/web_src/js/globals.d.ts b/web_src/js/globals.d.ts index 68081af606..9964293727 100644 --- a/web_src/js/globals.d.ts +++ b/web_src/js/globals.d.ts @@ -58,4 +58,8 @@ interface Window { push: (e: ErrorEvent & PromiseRejectionEvent) => void | number, }, __webpack_public_path__: string; + grecaptcha: any, + turnstile: any, + hcaptcha: any, + codeEditors: any[], } diff --git a/web_src/js/markup/math.ts b/web_src/js/markup/math.ts index 34ca79f493..6a1ca2f2e3 100644 --- a/web_src/js/markup/math.ts +++ b/web_src/js/markup/math.ts @@ -6,7 +6,7 @@ function targetElement(el: Element) { return el.classList.contains('is-loading') ? el : el.closest('pre'); } -export async function renderMath(): void { +export async function renderMath(): Promise<void> { const els = document.querySelectorAll('.markup code.language-math'); if (!els.length) return; diff --git a/web_src/js/modules/dirauto.ts b/web_src/js/modules/dirauto.ts index db45a9cd17..7058a59b09 100644 --- a/web_src/js/modules/dirauto.ts +++ b/web_src/js/modules/dirauto.ts @@ -13,7 +13,7 @@ function attachDirAuto(el: DirElement) { } } -export function initDirAuto() { +export function initDirAuto(): void { const observer = new MutationObserver((mutationList) => { const len = mutationList.length; for (let i = 0; i < len; i++) { diff --git a/web_src/js/modules/fetch.ts b/web_src/js/modules/fetch.ts index 90ff718498..de3ef1de7e 100644 --- a/web_src/js/modules/fetch.ts +++ b/web_src/js/modules/fetch.ts @@ -9,7 +9,7 @@ const safeMethods = new Set(['GET', 'HEAD', 'OPTIONS', 'TRACE']); // fetch wrapper, use below method name functions and the `data` option to pass in data // which will automatically set an appropriate headers. For json content, only object // and array types are currently supported. -export function request(url: string, {method = 'GET', data, headers = {}, ...other}: RequestOpts = {}) { +export function request(url: string, {method = 'GET', data, headers = {}, ...other}: RequestOpts = {}): Promise<Response> { let body: RequestData; let contentType: string; if (data instanceof FormData || data instanceof URLSearchParams) { diff --git a/web_src/js/modules/fomantic.ts b/web_src/js/modules/fomantic.ts index d4c71ede57..af47c8fb51 100644 --- a/web_src/js/modules/fomantic.ts +++ b/web_src/js/modules/fomantic.ts @@ -19,7 +19,7 @@ export function initGiteaFomantic() { // Do not use "cursor: pointer" for dropdown labels $.fn.dropdown.settings.className.label += ' tw-cursor-default'; // Always use Gitea's SVG icons - $.fn.dropdown.settings.templates.label = function(_value, text, preserveHTML, className) { + $.fn.dropdown.settings.templates.label = function(_value: any, text: any, preserveHTML: any, className: Record<string, string>) { const escape = $.fn.dropdown.settings.templates.escape; return escape(text, preserveHTML) + svg('octicon-x', 16, `${className.delete} icon`); }; diff --git a/web_src/js/modules/fomantic/api.ts b/web_src/js/modules/fomantic/api.ts index ca212c9fef..97430450e2 100644 --- a/web_src/js/modules/fomantic/api.ts +++ b/web_src/js/modules/fomantic/api.ts @@ -1,4 +1,5 @@ import $ from 'jquery'; +import type {FomanticInitFunction} from '../../types.ts'; export function initFomanticApiPatch() { // @@ -15,7 +16,7 @@ export function initFomanticApiPatch() { // const patchKey = '_giteaFomanticApiPatch'; const oldApi = $.api; - $.api = $.fn.api = function(...args) { + $.api = $.fn.api = function(...args: Parameters<FomanticInitFunction>) { const apiCall = oldApi.bind(this); const ret = oldApi.apply(this, args); @@ -23,7 +24,7 @@ export function initFomanticApiPatch() { const internalGet = apiCall('internal', 'get'); if (!internalGet.urlEncodedValue[patchKey]) { const oldUrlEncodedValue = internalGet.urlEncodedValue; - internalGet.urlEncodedValue = function (value) { + internalGet.urlEncodedValue = function (value: any) { try { return oldUrlEncodedValue(value); } catch { diff --git a/web_src/js/modules/fomantic/base.ts b/web_src/js/modules/fomantic/base.ts index 10b5ed014f..18f91932a9 100644 --- a/web_src/js/modules/fomantic/base.ts +++ b/web_src/js/modules/fomantic/base.ts @@ -5,7 +5,7 @@ export function generateAriaId() { return `_aria_auto_id_${ariaIdCounter++}`; } -export function linkLabelAndInput(label, input) { +export function linkLabelAndInput(label: Element, input: Element) { const labelFor = label.getAttribute('for'); const inputId = input.getAttribute('id'); diff --git a/web_src/js/modules/fomantic/dimmer.ts b/web_src/js/modules/fomantic/dimmer.ts index e59b6e9eea..4e05cac0cd 100644 --- a/web_src/js/modules/fomantic/dimmer.ts +++ b/web_src/js/modules/fomantic/dimmer.ts @@ -3,7 +3,7 @@ import {queryElemChildren} from '../../utils/dom.ts'; export function initFomanticDimmer() { // stand-in for removed dimmer module - $.fn.dimmer = function (arg0, arg1) { + $.fn.dimmer = function (arg0: string, arg1: any) { if (arg0 === 'add content') { const $el = arg1; const existingDimmer = document.querySelector('body > .ui.dimmer'); diff --git a/web_src/js/modules/fomantic/dropdown.ts b/web_src/js/modules/fomantic/dropdown.ts index 7ef6aed62b..d8fb4d6e6e 100644 --- a/web_src/js/modules/fomantic/dropdown.ts +++ b/web_src/js/modules/fomantic/dropdown.ts @@ -1,5 +1,6 @@ import $ from 'jquery'; import {generateAriaId} from './base.ts'; +import type {FomanticInitFunction} from '../../types.ts'; const ariaPatchKey = '_giteaAriaPatchDropdown'; const fomanticDropdownFn = $.fn.dropdown; @@ -8,13 +9,13 @@ const fomanticDropdownFn = $.fn.dropdown; export function initAriaDropdownPatch() { if ($.fn.dropdown === ariaDropdownFn) throw new Error('initAriaDropdownPatch could only be called once'); $.fn.dropdown = ariaDropdownFn; - ariaDropdownFn.settings = fomanticDropdownFn.settings; + (ariaDropdownFn as FomanticInitFunction).settings = fomanticDropdownFn.settings; } // the patched `$.fn.dropdown` function, it passes the arguments to Fomantic's `$.fn.dropdown` function, and: // * it does the one-time attaching on the first call // * it delegates the `onLabelCreate` to the patched `onLabelCreate` to add necessary aria attributes -function ariaDropdownFn(...args) { +function ariaDropdownFn(...args: Parameters<FomanticInitFunction>) { const ret = fomanticDropdownFn.apply(this, args); // if the `$().dropdown()` call is without arguments, or it has non-string (object) argument, @@ -33,7 +34,7 @@ function ariaDropdownFn(...args) { // make the item has role=option/menuitem, add an id if there wasn't one yet, make items as non-focusable // the elements inside the dropdown menu item should not be focusable, the focus should always be on the dropdown primary element. -function updateMenuItem(dropdown, item) { +function updateMenuItem(dropdown: HTMLElement, item: HTMLElement) { if (!item.id) item.id = generateAriaId(); item.setAttribute('role', dropdown[ariaPatchKey].listItemRole); item.setAttribute('tabindex', '-1'); @@ -43,7 +44,7 @@ function updateMenuItem(dropdown, item) { * make the label item and its "delete icon" have correct aria attributes * @param {HTMLElement} label */ -function updateSelectionLabel(label) { +function updateSelectionLabel(label: HTMLElement) { // the "label" is like this: "<a|div class="ui label" data-value="1">the-label-name <i|svg class="delete icon"/></a>" if (!label.id) { label.id = generateAriaId(); @@ -59,7 +60,7 @@ function updateSelectionLabel(label) { } // delegate the dropdown's template functions and callback functions to add aria attributes. -function delegateOne($dropdown) { +function delegateOne($dropdown: any) { const dropdownCall = fomanticDropdownFn.bind($dropdown); // If there is a "search input" in the "menu", Fomantic will only "focus the input" but not "toggle the menu" when the "dropdown icon" is clicked. @@ -74,7 +75,7 @@ function delegateOne($dropdown) { // the "template" functions are used for dynamic creation (eg: AJAX) const dropdownTemplates = {...dropdownCall('setting', 'templates'), t: performance.now()}; const dropdownTemplatesMenuOld = dropdownTemplates.menu; - dropdownTemplates.menu = function(response, fields, preserveHTML, className) { + dropdownTemplates.menu = function(response: any, fields: any, preserveHTML: any, className: Record<string, string>) { // when the dropdown menu items are loaded from AJAX requests, the items are created dynamically const menuItems = dropdownTemplatesMenuOld(response, fields, preserveHTML, className); const div = document.createElement('div'); @@ -89,7 +90,7 @@ function delegateOne($dropdown) { // the `onLabelCreate` is used to add necessary aria attributes for dynamically created selection labels const dropdownOnLabelCreateOld = dropdownCall('setting', 'onLabelCreate'); - dropdownCall('setting', 'onLabelCreate', function(value, text) { + dropdownCall('setting', 'onLabelCreate', function(value: any, text: string) { const $label = dropdownOnLabelCreateOld.call(this, value, text); updateSelectionLabel($label[0]); return $label; @@ -97,7 +98,7 @@ function delegateOne($dropdown) { const oldSet = dropdownCall('internal', 'set'); const oldSetDirection = oldSet.direction; - oldSet.direction = function($menu) { + oldSet.direction = function($menu: any) { oldSetDirection.call(this, $menu); const classNames = dropdownCall('setting', 'className'); $menu = $menu || $dropdown.find('> .menu'); @@ -113,7 +114,7 @@ function delegateOne($dropdown) { } // for static dropdown elements (generated by server-side template), prepare them with necessary aria attributes -function attachStaticElements(dropdown, focusable, menu) { +function attachStaticElements(dropdown: HTMLElement, focusable: HTMLElement, menu: HTMLElement) { // prepare static dropdown menu list popup if (!menu.id) { menu.id = generateAriaId(); @@ -125,7 +126,7 @@ function attachStaticElements(dropdown, focusable, menu) { menu.setAttribute('role', dropdown[ariaPatchKey].listPopupRole); // prepare selection label items - for (const label of dropdown.querySelectorAll('.ui.label')) { + for (const label of dropdown.querySelectorAll<HTMLElement>('.ui.label')) { updateSelectionLabel(label); } @@ -142,7 +143,7 @@ function attachStaticElements(dropdown, focusable, menu) { } } -function attachInit(dropdown) { +function attachInit(dropdown: HTMLElement) { dropdown[ariaPatchKey] = {}; if (dropdown.classList.contains('custom')) return; @@ -161,7 +162,7 @@ function attachInit(dropdown) { // TODO: multiple selection is only partially supported. Check and test them one by one in the future. - const textSearch = dropdown.querySelector('input.search'); + const textSearch = dropdown.querySelector<HTMLElement>('input.search'); const focusable = textSearch || dropdown; // the primary element for focus, see comment above if (!focusable) return; @@ -191,7 +192,7 @@ function attachInit(dropdown) { attachStaticElements(dropdown, focusable, menu); } -function attachDomEvents(dropdown, focusable, menu) { +function attachDomEvents(dropdown: HTMLElement, focusable: HTMLElement, menu: HTMLElement) { // when showing, it has class: ".animating.in" // when hiding, it has class: ".visible.animating.out" const isMenuVisible = () => (menu.classList.contains('visible') && !menu.classList.contains('out')) || menu.classList.contains('in'); @@ -215,7 +216,7 @@ function attachDomEvents(dropdown, focusable, menu) { } }; - dropdown.addEventListener('keydown', (e) => { + dropdown.addEventListener('keydown', (e: KeyboardEvent) => { // here it must use keydown event before dropdown's keyup handler, otherwise there is no Enter event in our keyup handler if (e.key === 'Enter') { const dropdownCall = fomanticDropdownFn.bind($(dropdown)); @@ -260,7 +261,7 @@ function attachDomEvents(dropdown, focusable, menu) { deferredRefreshAriaActiveItem(100); }, 0); }, true); - dropdown.addEventListener('click', (e) => { + dropdown.addEventListener('click', (e: MouseEvent) => { if (isMenuVisible() && ignoreClickPreVisible !== 2 && // dropdown is switch from invisible to visible ignoreClickPreEvents === 2 // the click event is related to mousedown+focus diff --git a/web_src/js/modules/fomantic/modal.ts b/web_src/js/modules/fomantic/modal.ts index 8b455cf4de..fb80047d01 100644 --- a/web_src/js/modules/fomantic/modal.ts +++ b/web_src/js/modules/fomantic/modal.ts @@ -1,4 +1,5 @@ import $ from 'jquery'; +import type {FomanticInitFunction} from '../../types.ts'; const fomanticModalFn = $.fn.modal; @@ -6,12 +7,12 @@ const fomanticModalFn = $.fn.modal; export function initAriaModalPatch() { if ($.fn.modal === ariaModalFn) throw new Error('initAriaModalPatch could only be called once'); $.fn.modal = ariaModalFn; - ariaModalFn.settings = fomanticModalFn.settings; + (ariaModalFn as FomanticInitFunction).settings = fomanticModalFn.settings; } // the patched `$.fn.modal` modal function // * it does the one-time attaching on the first call -function ariaModalFn(...args) { +function ariaModalFn(...args: Parameters<FomanticInitFunction>) { const ret = fomanticModalFn.apply(this, args); if (args[0] === 'show' || args[0]?.autoShow) { for (const el of this) { diff --git a/web_src/js/modules/fomantic/transition.ts b/web_src/js/modules/fomantic/transition.ts index 78aa0538b0..52c407c9c0 100644 --- a/web_src/js/modules/fomantic/transition.ts +++ b/web_src/js/modules/fomantic/transition.ts @@ -8,13 +8,13 @@ export function initFomanticTransition() { 'set duration', 'save conditions', 'restore conditions', ]); // stand-in for removed transition module - $.fn.transition = function (arg0, arg1, arg2) { + $.fn.transition = function (arg0: any, arg1: any, arg2: any) { if (arg0 === 'is supported') return true; if (arg0 === 'is animating') return false; if (arg0 === 'is inward') return false; if (arg0 === 'is outward') return false; - let argObj; + let argObj: Record<string, any>; if (typeof arg0 === 'string') { // many behaviors are no-op now. https://fomantic-ui.com/modules/transition.html#/usage if (transitionNopBehaviors.has(arg0)) return this; diff --git a/web_src/js/modules/sortable.ts b/web_src/js/modules/sortable.ts index 460f4c6d91..c31135357c 100644 --- a/web_src/js/modules/sortable.ts +++ b/web_src/js/modules/sortable.ts @@ -1,17 +1,18 @@ -import type {SortableOptions} from 'sortablejs'; +import type {SortableOptions, SortableEvent} from 'sortablejs'; -export async function createSortable(el, opts: {handle?: string} & SortableOptions = {}) { +export async function createSortable(el: HTMLElement, opts: {handle?: string} & SortableOptions = {}) { + // @ts-expect-error: wrong type derived by typescript const {Sortable} = await import(/* webpackChunkName: "sortablejs" */'sortablejs'); return new Sortable(el, { animation: 150, ghostClass: 'card-ghost', - onChoose: (e) => { + onChoose: (e: SortableEvent) => { const handle = opts.handle ? e.item.querySelector(opts.handle) : e.item; handle.classList.add('tw-cursor-grabbing'); opts.onChoose?.(e); }, - onUnchoose: (e) => { + onUnchoose: (e: SortableEvent) => { const handle = opts.handle ? e.item.querySelector(opts.handle) : e.item; handle.classList.remove('tw-cursor-grabbing'); opts.onUnchoose?.(e); diff --git a/web_src/js/modules/tippy.ts b/web_src/js/modules/tippy.ts index 7948e3ecbc..ce0b3cbc39 100644 --- a/web_src/js/modules/tippy.ts +++ b/web_src/js/modules/tippy.ts @@ -1,7 +1,7 @@ import tippy, {followCursor} from 'tippy.js'; import {isDocumentFragmentOrElementNode} from '../utils/dom.ts'; import {formatDatetime} from '../utils/time.ts'; -import type {Content, Instance, Props} from 'tippy.js'; +import type {Content, Instance, Placement, Props} from 'tippy.js'; type TippyOpts = { role?: string, @@ -16,6 +16,7 @@ export function createTippy(target: Element, opts: TippyOpts = {}): Instance { // because we should use our own wrapper functions to handle them, do not let the user override them const {onHide, onShow, onDestroy, role, theme, arrow, ...other} = opts; + // @ts-expect-error: wrong type derived by typescript const instance: Instance = tippy(target, { appendTo: document.body, animation: false, @@ -65,7 +66,7 @@ export function createTippy(target: Element, opts: TippyOpts = {}): Instance { * * Note: "tooltip" doesn't equal to "tippy". "tooltip" means a auto-popup content, it just uses tippy as the implementation. */ -function attachTooltip(target: Element, content: Content = null) { +function attachTooltip(target: Element, content: Content = null): Instance { switchTitleToTooltip(target); content = content ?? target.getAttribute('data-tooltip-content'); @@ -77,16 +78,16 @@ function attachTooltip(target: Element, content: Content = null) { const hasClipboardTarget = target.hasAttribute('data-clipboard-target'); const hideOnClick = !hasClipboardTarget; - const props = { + const props: TippyOpts = { content, delay: 100, role: 'tooltip', theme: 'tooltip', hideOnClick, - placement: target.getAttribute('data-tooltip-placement') || 'top-start', - followCursor: target.getAttribute('data-tooltip-follow-cursor') || false, + placement: target.getAttribute('data-tooltip-placement') as Placement || 'top-start', + followCursor: target.getAttribute('data-tooltip-follow-cursor') as Props['followCursor'] || false, ...(target.getAttribute('data-tooltip-interactive') === 'true' ? {interactive: true, aria: {content: 'describedby', expanded: false}} : {}), - } as TippyOpts; + }; if (!target._tippy) { createTippy(target, props); @@ -96,7 +97,7 @@ function attachTooltip(target: Element, content: Content = null) { return target._tippy; } -function switchTitleToTooltip(target: Element) { +function switchTitleToTooltip(target: Element): void { let title = target.getAttribute('title'); if (title) { // apply custom formatting to relative-time's tooltips @@ -121,14 +122,14 @@ function switchTitleToTooltip(target: Element) { * 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) { +function lazyTooltipOnMouseHover(e: MouseEvent): 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) { +function attachLazyTooltip(el: Element): void { el.addEventListener('mouseover', lazyTooltipOnMouseHover, {capture: true}); // meanwhile, if the element has no aria-label, use the tooltip content as aria-label @@ -141,13 +142,13 @@ function attachLazyTooltip(el: Element) { } // Activate the tooltip for all children elements. -function attachChildrenLazyTooltip(target: Element) { +function attachChildrenLazyTooltip(target: Element): void { for (const el of target.querySelectorAll<Element>('[data-tooltip-content]')) { attachLazyTooltip(el); } } -export function initGlobalTooltips() { +export function initGlobalTooltips(): void { // use MutationObserver to detect new "data-tooltip-content" elements added to the DOM, or attributes changed const observerConnect = (observer: MutationObserver) => observer.observe(document, { subtree: true, @@ -178,7 +179,7 @@ export function initGlobalTooltips() { attachChildrenLazyTooltip(document.documentElement); } -export function showTemporaryTooltip(target: Element, content: Content) { +export function showTemporaryTooltip(target: Element, content: Content): void { // if the target is inside a dropdown, the menu will be hidden soon // so display the tooltip on the dropdown instead target = target.closest('.ui.dropdown') || target; diff --git a/web_src/js/modules/worker.ts b/web_src/js/modules/worker.ts index ddc02d7a7d..af2e52f411 100644 --- a/web_src/js/modules/worker.ts +++ b/web_src/js/modules/worker.ts @@ -2,7 +2,7 @@ import {sleep} from '../utils.ts'; const {appSubUrl} = window.config; -export async function logoutFromWorker() { +export async function logoutFromWorker(): Promise<void> { // wait for a while because other requests (eg: logout) may be in the flight await sleep(5000); window.location.href = `${appSubUrl}/`; diff --git a/web_src/js/types.ts b/web_src/js/types.ts index f5c4a40bca..317719fad5 100644 --- a/web_src/js/types.ts +++ b/web_src/js/types.ts @@ -54,3 +54,8 @@ export type Issue = { merged: boolean; }; }; + +export type FomanticInitFunction = { + settings?: Record<string, any>, + (...args: any[]): any, +} diff --git a/web_src/js/utils/match.ts b/web_src/js/utils/match.ts index 1161ea6250..274c9322ff 100644 --- a/web_src/js/utils/match.ts +++ b/web_src/js/utils/match.ts @@ -1,6 +1,6 @@ import emojis from '../../../assets/emoji.json'; import {GET} from '../modules/fetch.ts'; -import type {Issue} from '../features/issue.ts'; +import type {Issue} from '../types.ts'; const maxMatches = 6; |