mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 10:56:10 +01:00 
			
		
		
		
	Add types to various low-level functions (#31781)
Adds types to various low-level modules. All changes are type-only, no runtime changes. `tsc` now reports 38 less errors. One problem was that `@types/sortablejs` does not accept promise return in its functions which triggered the linter, so I disabled the rules on those line.
This commit is contained in:
		| @@ -1,14 +1,21 @@ | ||||
| import {debounce} from 'throttle-debounce'; | ||||
| import type {Promisable} from 'type-fest'; | ||||
| import type $ from 'jquery'; | ||||
|  | ||||
| function elementsCall(el, func, ...args) { | ||||
| type ElementArg = Element | string | NodeListOf<Element> | Array<Element> | ReturnType<typeof $>; | ||||
| type ElementsCallback = (el: Element) => Promisable<any>; | ||||
| type ElementsCallbackWithArgs = (el: Element, ...args: any[]) => Promisable<any>; | ||||
| type IterableElements = NodeListOf<Element> | Array<Element>; | ||||
|  | ||||
| function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]) { | ||||
|   if (typeof el === 'string' || el instanceof String) { | ||||
|     el = document.querySelectorAll(el); | ||||
|     el = document.querySelectorAll(el as string); | ||||
|   } | ||||
|   if (el instanceof Node) { | ||||
|     func(el, ...args); | ||||
|   } else if (el.length !== undefined) { | ||||
|     // this works for: NodeList, HTMLCollection, Array, jQuery | ||||
|     for (const e of el) { | ||||
|     for (const e of (el as IterableElements)) { | ||||
|       func(e, ...args); | ||||
|     } | ||||
|   } else { | ||||
| @@ -17,10 +24,10 @@ function elementsCall(el, func, ...args) { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param el string (selector), Node, NodeList, HTMLCollection, Array or jQuery | ||||
|  * @param el Element | ||||
|  * @param force force=true to show or force=false to hide, undefined to toggle | ||||
|  */ | ||||
| function toggleShown(el, force) { | ||||
| function toggleShown(el: Element, force: boolean) { | ||||
|   if (force === true) { | ||||
|     el.classList.remove('tw-hidden'); | ||||
|   } else if (force === false) { | ||||
| @@ -32,26 +39,26 @@ function toggleShown(el, force) { | ||||
|   } | ||||
| } | ||||
|  | ||||
| export function showElem(el) { | ||||
| export function showElem(el: ElementArg) { | ||||
|   elementsCall(el, toggleShown, true); | ||||
| } | ||||
|  | ||||
| export function hideElem(el) { | ||||
| export function hideElem(el: ElementArg) { | ||||
|   elementsCall(el, toggleShown, false); | ||||
| } | ||||
|  | ||||
| export function toggleElem(el, force) { | ||||
| export function toggleElem(el: ElementArg, force?: boolean) { | ||||
|   elementsCall(el, toggleShown, force); | ||||
| } | ||||
|  | ||||
| export function isElemHidden(el) { | ||||
|   const res = []; | ||||
| export function isElemHidden(el: ElementArg) { | ||||
|   const res: boolean[] = []; | ||||
|   elementsCall(el, (e) => res.push(e.classList.contains('tw-hidden'))); | ||||
|   if (res.length > 1) throw new Error(`isElemHidden doesn't work for multiple elements`); | ||||
|   return res[0]; | ||||
| } | ||||
|  | ||||
| function applyElemsCallback(elems, fn) { | ||||
| function applyElemsCallback(elems: IterableElements, fn?: ElementsCallback) { | ||||
|   if (fn) { | ||||
|     for (const el of elems) { | ||||
|       fn(el); | ||||
| @@ -60,20 +67,22 @@ function applyElemsCallback(elems, fn) { | ||||
|   return elems; | ||||
| } | ||||
|  | ||||
| export function queryElemSiblings(el, selector = '*', fn) { | ||||
|   return applyElemsCallback(Array.from(el.parentNode.children).filter((child) => child !== el && child.matches(selector)), fn); | ||||
| export function queryElemSiblings(el: Element, selector = '*', fn?: ElementsCallback) { | ||||
|   return applyElemsCallback(Array.from(el.parentNode.children).filter((child: Element) => { | ||||
|     return child !== el && child.matches(selector); | ||||
|   }), fn); | ||||
| } | ||||
|  | ||||
| // it works like jQuery.children: only the direct children are selected | ||||
| export function queryElemChildren(parent, selector = '*', fn) { | ||||
| export function queryElemChildren(parent: Element | ParentNode, selector = '*', fn?: ElementsCallback) { | ||||
|   return applyElemsCallback(parent.querySelectorAll(`:scope > ${selector}`), fn); | ||||
| } | ||||
|  | ||||
| export function queryElems(selector, fn) { | ||||
| export function queryElems(selector: string, fn?: ElementsCallback) { | ||||
|   return applyElemsCallback(document.querySelectorAll(selector), fn); | ||||
| } | ||||
|  | ||||
| export function onDomReady(cb) { | ||||
| export function onDomReady(cb: () => Promisable<void>) { | ||||
|   if (document.readyState === 'loading') { | ||||
|     document.addEventListener('DOMContentLoaded', cb); | ||||
|   } else { | ||||
| @@ -83,7 +92,7 @@ export function onDomReady(cb) { | ||||
|  | ||||
| // checks whether an element is owned by the current document, and whether it is a document fragment or element node | ||||
| // if it is, it means it is a "normal" element managed by us, which can be modified safely. | ||||
| export function isDocumentFragmentOrElementNode(el) { | ||||
| export function isDocumentFragmentOrElementNode(el: Element) { | ||||
|   try { | ||||
|     return el.ownerDocument === document && el.nodeType === Node.ELEMENT_NODE || el.nodeType === Node.DOCUMENT_FRAGMENT_NODE; | ||||
|   } catch { | ||||
| @@ -108,12 +117,15 @@ export function isDocumentFragmentOrElementNode(el) { | ||||
| // The above copyright notice and this permission notice shall be | ||||
| // included in all copies or substantial portions of the Software. | ||||
| // --------------------------------------------------------------------- | ||||
| export function autosize(textarea, {viewportMarginBottom = 0} = {}) { | ||||
| export function autosize(textarea: HTMLTextAreaElement, {viewportMarginBottom = 0}: {viewportMarginBottom?: number} = {}) { | ||||
|   let isUserResized = false; | ||||
|   // lastStyleHeight and initialStyleHeight are CSS values like '100px' | ||||
|   let lastMouseX, lastMouseY, lastStyleHeight, initialStyleHeight; | ||||
|   let lastMouseX: number; | ||||
|   let lastMouseY: number; | ||||
|   let lastStyleHeight: string; | ||||
|   let initialStyleHeight: string; | ||||
|  | ||||
|   function onUserResize(event) { | ||||
|   function onUserResize(event: MouseEvent) { | ||||
|     if (isUserResized) return; | ||||
|     if (lastMouseX !== event.clientX || lastMouseY !== event.clientY) { | ||||
|       const newStyleHeight = textarea.style.height; | ||||
| @@ -133,7 +145,7 @@ export function autosize(textarea, {viewportMarginBottom = 0} = {}) { | ||||
|  | ||||
|     while (el !== document.body && el !== null) { | ||||
|       offsetTop += el.offsetTop || 0; | ||||
|       el = el.offsetParent; | ||||
|       el = el.offsetParent as HTMLTextAreaElement; | ||||
|     } | ||||
|  | ||||
|     const top = offsetTop - document.defaultView.scrollY; | ||||
| @@ -213,14 +225,15 @@ export function autosize(textarea, {viewportMarginBottom = 0} = {}) { | ||||
|   }; | ||||
| } | ||||
|  | ||||
| export function onInputDebounce(fn) { | ||||
| export function onInputDebounce(fn: () => Promisable<any>) { | ||||
|   return debounce(300, fn); | ||||
| } | ||||
|  | ||||
| type LoadableElement = HTMLEmbedElement | HTMLIFrameElement | HTMLImageElement | HTMLScriptElement | HTMLTrackElement; | ||||
|  | ||||
| // Set the `src` attribute on an element and returns a promise that resolves once the element | ||||
| // has loaded or errored. Suitable for all elements mention in: | ||||
| // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/load_event | ||||
| export function loadElem(el, src) { | ||||
| // has loaded or errored. | ||||
| export function loadElem(el: LoadableElement, src: string) { | ||||
|   return new Promise((resolve) => { | ||||
|     el.addEventListener('load', () => resolve(true), {once: true}); | ||||
|     el.addEventListener('error', () => resolve(false), {once: true}); | ||||
| @@ -256,14 +269,14 @@ export function initSubmitEventPolyfill() { | ||||
|  * @param {HTMLElement} element The element to check. | ||||
|  * @returns {boolean} True if the element is visible. | ||||
|  */ | ||||
| export function isElemVisible(element) { | ||||
| export function isElemVisible(element: HTMLElement) { | ||||
|   if (!element) return false; | ||||
|  | ||||
|   return Boolean(element.offsetWidth || element.offsetHeight || element.getClientRects().length); | ||||
| } | ||||
|  | ||||
| // replace selected text in a textarea while preserving editor history, e.g. CTRL-Z works after this | ||||
| export function replaceTextareaSelection(textarea, text) { | ||||
| export function replaceTextareaSelection(textarea: HTMLTextAreaElement, text: string) { | ||||
|   const before = textarea.value.slice(0, textarea.selectionStart ?? undefined); | ||||
|   const after = textarea.value.slice(textarea.selectionEnd ?? undefined); | ||||
|   let success = true; | ||||
| @@ -287,13 +300,13 @@ export function replaceTextareaSelection(textarea, text) { | ||||
| } | ||||
|  | ||||
| // Warning: Do not enter any unsanitized variables here | ||||
| export function createElementFromHTML(htmlString) { | ||||
| export function createElementFromHTML(htmlString: string) { | ||||
|   const div = document.createElement('div'); | ||||
|   div.innerHTML = htmlString.trim(); | ||||
|   return div.firstChild; | ||||
|   return div.firstChild as Element; | ||||
| } | ||||
|  | ||||
| export function createElementFromAttrs(tagName, attrs) { | ||||
| export function createElementFromAttrs(tagName: string, attrs: Record<string, any>) { | ||||
|   const el = document.createElement(tagName); | ||||
|   for (const [key, value] of Object.entries(attrs)) { | ||||
|     if (value === undefined || value === null) continue; | ||||
| @@ -307,7 +320,7 @@ export function createElementFromAttrs(tagName, attrs) { | ||||
|   return el; | ||||
| } | ||||
|  | ||||
| export function animateOnce(el, animationClassName) { | ||||
| export function animateOnce(el: Element, animationClassName: string): Promise<void> { | ||||
|   return new Promise((resolve) => { | ||||
|     el.addEventListener('animationend', function onAnimationEnd() { | ||||
|       el.classList.remove(animationClassName); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user