Enable Typescript noImplicitAny (#33322)

Enable `noImplicitAny` and fix all issues.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
silverwind 2025-01-22 08:11:51 +01:00 committed by GitHub
parent 6fe4d1c038
commit c7f4ca2653
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 326 additions and 270 deletions

View file

@ -90,7 +90,7 @@ export function initAdminCommon(): void {
onOAuth2UseCustomURLChange(applyDefaultValues);
}
function onOAuth2UseCustomURLChange(applyDefaultValues) {
function onOAuth2UseCustomURLChange(applyDefaultValues: boolean) {
const provider = document.querySelector<HTMLInputElement>('#oauth2_provider').value;
hideElem('.oauth2_use_custom_url_field');
for (const input of document.querySelectorAll<HTMLInputElement>('.oauth2_use_custom_url_field input[required]')) {

View file

@ -5,9 +5,13 @@ const {pageData} = window.config;
async function initInputCitationValue(citationCopyApa: HTMLButtonElement, citationCopyBibtex: HTMLButtonElement) {
const [{Cite, plugins}] = await Promise.all([
// @ts-expect-error: module exports no types
import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'),
// @ts-expect-error: module exports no types
import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'),
// @ts-expect-error: module exports no types
import(/* webpackChunkName: "citation-js-bibtex" */'@citation-js/plugin-bibtex'),
// @ts-expect-error: module exports no types
import(/* webpackChunkName: "citation-js-csl" */'@citation-js/plugin-csl'),
]);
const {citationFileContent} = pageData;

View file

@ -74,10 +74,10 @@ export function initGlobalDeleteButton(): void {
}
}
function onShowPanelClick(e) {
function onShowPanelClick(e: MouseEvent) {
// a '.show-panel' element can show a panel, by `data-panel="selector"`
// if it has "toggle" class, it toggles the panel
const el = e.currentTarget;
const el = e.currentTarget as HTMLElement;
e.preventDefault();
const sel = el.getAttribute('data-panel');
if (el.classList.contains('toggle')) {
@ -87,9 +87,9 @@ function onShowPanelClick(e) {
}
}
function onHidePanelClick(e) {
function onHidePanelClick(e: MouseEvent) {
// a `.hide-panel` element can hide a panel, by `data-panel="selector"` or `data-panel-closest="selector"`
const el = e.currentTarget;
const el = e.currentTarget as HTMLElement;
e.preventDefault();
let sel = el.getAttribute('data-panel');
if (sel) {
@ -98,13 +98,13 @@ function onHidePanelClick(e) {
}
sel = el.getAttribute('data-panel-closest');
if (sel) {
hideElem(el.parentNode.closest(sel));
hideElem((el.parentNode as HTMLElement).closest(sel));
return;
}
throw new Error('no panel to hide'); // should never happen, otherwise there is a bug in code
}
function onShowModalClick(e) {
function onShowModalClick(e: MouseEvent) {
// A ".show-modal" button will show a modal dialog defined by its "data-modal" attribute.
// Each "data-modal-{target}" attribute will be filled to target element's value or text-content.
// * First, try to query '#target'
@ -112,7 +112,7 @@ function onShowModalClick(e) {
// * Then, try to query '.target'
// * Then, try to query 'target' as HTML tag
// If there is a ".{attr}" part like "data-modal-form.action", then the form's "action" attribute will be set.
const el = e.currentTarget;
const el = e.currentTarget as HTMLElement;
e.preventDefault();
const modalSelector = el.getAttribute('data-modal');
const elModal = document.querySelector(modalSelector);
@ -137,9 +137,9 @@ function onShowModalClick(e) {
}
if (attrTargetAttr) {
attrTarget[camelize(attrTargetAttr)] = attrib.value;
(attrTarget as any)[camelize(attrTargetAttr)] = attrib.value;
} else if (attrTarget.matches('input, textarea')) {
attrTarget.value = attrib.value; // FIXME: add more supports like checkbox
(attrTarget as HTMLInputElement | HTMLTextAreaElement).value = attrib.value; // FIXME: add more supports like checkbox
} else {
attrTarget.textContent = attrib.value; // FIXME: it should be more strict here, only handle div/span/p
}

View file

@ -75,7 +75,10 @@ async function formFetchAction(formEl: HTMLFormElement, e: SubmitEvent) {
}
let reqUrl = formActionUrl;
const reqOpt = {method: formMethod.toUpperCase(), body: null};
const reqOpt = {
method: formMethod.toUpperCase(),
body: null as FormData | null,
};
if (formMethod.toLowerCase() === 'get') {
const params = new URLSearchParams();
for (const [key, value] of formData) {

View file

@ -17,13 +17,13 @@ export function initGlobalEnterQuickSubmit() {
if (e.key !== 'Enter') return;
const hasCtrlOrMeta = ((e.ctrlKey || e.metaKey) && !e.altKey);
if (hasCtrlOrMeta && e.target.matches('textarea')) {
if (handleGlobalEnterQuickSubmit(e.target)) {
if (handleGlobalEnterQuickSubmit(e.target as HTMLElement)) {
e.preventDefault();
}
} else if (e.target.matches('input') && !e.target.closest('form')) {
// input in a normal form could handle Enter key by default, so we only handle the input outside a form
// eslint-disable-next-line unicorn/no-lonely-if
if (handleGlobalEnterQuickSubmit(e.target)) {
if (handleGlobalEnterQuickSubmit(e.target as HTMLElement)) {
e.preventDefault();
}
}

View file

@ -29,10 +29,10 @@ let elementIdCounter = 0;
/**
* validate if the given textarea is non-empty.
* @param {HTMLElement} textarea - The textarea element to be validated.
* @param {HTMLTextAreaElement} textarea - The textarea element to be validated.
* @returns {boolean} returns true if validation succeeded.
*/
export function validateTextareaNonEmpty(textarea) {
export function validateTextareaNonEmpty(textarea: HTMLTextAreaElement) {
// When using EasyMDE, the original edit area HTML element is hidden, breaking HTML5 input validation.
// The workaround (https://github.com/sparksuite/simplemde-markdown-editor/issues/324) doesn't work with contenteditable, so we just show an alert.
if (!textarea.value) {
@ -49,16 +49,25 @@ export function validateTextareaNonEmpty(textarea) {
return true;
}
type Heights = {
minHeight?: string,
height?: string,
maxHeight?: string,
};
type ComboMarkdownEditorOptions = {
editorHeights?: {minHeight?: string, height?: string, maxHeight?: string},
editorHeights?: Heights,
easyMDEOptions?: EasyMDE.Options,
};
type ComboMarkdownEditorTextarea = HTMLTextAreaElement & {_giteaComboMarkdownEditor: any};
type ComboMarkdownEditorContainer = HTMLElement & {_giteaComboMarkdownEditor?: any};
export class ComboMarkdownEditor {
static EventEditorContentChanged = EventEditorContentChanged;
static EventUploadStateChanged = EventUploadStateChanged;
public container : HTMLElement;
public container: HTMLElement;
options: ComboMarkdownEditorOptions;
@ -70,7 +79,7 @@ export class ComboMarkdownEditor {
easyMDEToolbarActions: any;
easyMDEToolbarDefault: any;
textarea: HTMLTextAreaElement & {_giteaComboMarkdownEditor: any};
textarea: ComboMarkdownEditorTextarea;
textareaMarkdownToolbar: HTMLElement;
textareaAutosize: any;
@ -81,7 +90,7 @@ export class ComboMarkdownEditor {
previewUrl: string;
previewContext: string;
constructor(container, options:ComboMarkdownEditorOptions = {}) {
constructor(container: ComboMarkdownEditorContainer, options:ComboMarkdownEditorOptions = {}) {
if (container._giteaComboMarkdownEditor) throw new Error('ComboMarkdownEditor already initialized');
container._giteaComboMarkdownEditor = this;
this.options = options;
@ -98,7 +107,7 @@ export class ComboMarkdownEditor {
await this.switchToUserPreference();
}
applyEditorHeights(el, heights) {
applyEditorHeights(el: HTMLElement, heights: Heights) {
if (!heights) return;
if (heights.minHeight) el.style.minHeight = heights.minHeight;
if (heights.height) el.style.height = heights.height;
@ -283,7 +292,7 @@ export class ComboMarkdownEditor {
];
}
parseEasyMDEToolbar(easyMde: typeof EasyMDE, actions) {
parseEasyMDEToolbar(easyMde: typeof EasyMDE, actions: any) {
this.easyMDEToolbarActions = this.easyMDEToolbarActions || easyMDEToolbarActions(easyMde, this);
const processed = [];
for (const action of actions) {
@ -332,21 +341,21 @@ export class ComboMarkdownEditor {
this.easyMDE = new EasyMDE(easyMDEOpt);
this.easyMDE.codemirror.on('change', () => triggerEditorContentChanged(this.container));
this.easyMDE.codemirror.setOption('extraKeys', {
'Cmd-Enter': (cm) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
'Ctrl-Enter': (cm) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
Enter: (cm) => {
'Cmd-Enter': (cm: any) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
'Ctrl-Enter': (cm: any) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
Enter: (cm: any) => {
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
if (!tributeContainer || tributeContainer.style.display === 'none') {
cm.execCommand('newlineAndIndent');
}
},
Up: (cm) => {
Up: (cm: any) => {
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
if (!tributeContainer || tributeContainer.style.display === 'none') {
return cm.execCommand('goLineUp');
}
},
Down: (cm) => {
Down: (cm: any) => {
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
if (!tributeContainer || tributeContainer.style.display === 'none') {
return cm.execCommand('goLineDown');
@ -354,14 +363,14 @@ export class ComboMarkdownEditor {
},
});
this.applyEditorHeights(this.container.querySelector('.CodeMirror-scroll'), this.options.editorHeights);
await attachTribute(this.easyMDE.codemirror.getInputField(), {mentions: true, emoji: true});
await attachTribute(this.easyMDE.codemirror.getInputField());
if (this.dropzone) {
initEasyMDEPaste(this.easyMDE, this.dropzone);
}
hideElem(this.textareaMarkdownToolbar);
}
value(v = undefined) {
value(v: any = undefined) {
if (v === undefined) {
if (this.easyMDE) {
return this.easyMDE.value();
@ -402,7 +411,7 @@ export class ComboMarkdownEditor {
}
}
export function getComboMarkdownEditor(el) {
export function getComboMarkdownEditor(el: any) {
if (!el) return null;
if (el.length) el = el[0];
return el._giteaComboMarkdownEditor;

View file

@ -1,10 +1,10 @@
export const EventEditorContentChanged = 'ce-editor-content-changed';
export function triggerEditorContentChanged(target) {
export function triggerEditorContentChanged(target: HTMLElement) {
target.dispatchEvent(new CustomEvent(EventEditorContentChanged, {bubbles: true}));
}
export function textareaInsertText(textarea, value) {
export function textareaInsertText(textarea: HTMLTextAreaElement, value: string) {
const startPos = textarea.selectionStart;
const endPos = textarea.selectionEnd;
textarea.value = textarea.value.substring(0, startPos) + value + textarea.value.substring(endPos);
@ -20,7 +20,7 @@ type TextareaValueSelection = {
selEnd: number;
}
function handleIndentSelection(textarea: HTMLTextAreaElement, e) {
function handleIndentSelection(textarea: HTMLTextAreaElement, e: KeyboardEvent) {
const selStart = textarea.selectionStart;
const selEnd = textarea.selectionEnd;
if (selEnd === selStart) return; // do not process when no selection
@ -188,7 +188,7 @@ function isTextExpanderShown(textarea: HTMLElement): boolean {
return Boolean(textarea.closest('text-expander')?.querySelector('.suggestions'));
}
export function initTextareaMarkdown(textarea) {
export function initTextareaMarkdown(textarea: HTMLTextAreaElement) {
textarea.addEventListener('keydown', (e) => {
if (isTextExpanderShown(textarea)) return;
if (e.key === 'Tab' && !e.ctrlKey && !e.metaKey && !e.altKey) {

View file

@ -8,43 +8,46 @@ import {
generateMarkdownLinkForAttachment,
} from '../dropzone.ts';
import type CodeMirror from 'codemirror';
import type EasyMDE from 'easymde';
import type {DropzoneFile} from 'dropzone';
let uploadIdCounter = 0;
export const EventUploadStateChanged = 'ce-upload-state-changed';
export function triggerUploadStateChanged(target) {
export function triggerUploadStateChanged(target: HTMLElement) {
target.dispatchEvent(new CustomEvent(EventUploadStateChanged, {bubbles: true}));
}
function uploadFile(dropzoneEl, file) {
function uploadFile(dropzoneEl: HTMLElement, file: File) {
return new Promise((resolve) => {
const curUploadId = uploadIdCounter++;
file._giteaUploadId = curUploadId;
(file as any)._giteaUploadId = curUploadId;
const dropzoneInst = dropzoneEl.dropzone;
const onUploadDone = ({file}) => {
const onUploadDone = ({file}: {file: any}) => {
if (file._giteaUploadId === curUploadId) {
dropzoneInst.off(DropzoneCustomEventUploadDone, onUploadDone);
resolve(file);
}
};
dropzoneInst.on(DropzoneCustomEventUploadDone, onUploadDone);
dropzoneInst.handleFiles([file]);
// FIXME: this is not entirely correct because `file` does not satisfy DropzoneFile (we have abused the Dropzone for long time)
dropzoneInst.addFile(file as DropzoneFile);
});
}
class TextareaEditor {
editor : HTMLTextAreaElement;
editor: HTMLTextAreaElement;
constructor(editor) {
constructor(editor: HTMLTextAreaElement) {
this.editor = editor;
}
insertPlaceholder(value) {
insertPlaceholder(value: string) {
textareaInsertText(this.editor, value);
}
replacePlaceholder(oldVal, newVal) {
replacePlaceholder(oldVal: string, newVal: string) {
const editor = this.editor;
const startPos = editor.selectionStart;
const endPos = editor.selectionEnd;
@ -65,11 +68,11 @@ class TextareaEditor {
class CodeMirrorEditor {
editor: CodeMirror.EditorFromTextArea;
constructor(editor) {
constructor(editor: CodeMirror.EditorFromTextArea) {
this.editor = editor;
}
insertPlaceholder(value) {
insertPlaceholder(value: string) {
const editor = this.editor;
const startPoint = editor.getCursor('start');
const endPoint = editor.getCursor('end');
@ -80,7 +83,7 @@ class CodeMirrorEditor {
triggerEditorContentChanged(editor.getTextArea());
}
replacePlaceholder(oldVal, newVal) {
replacePlaceholder(oldVal: string, newVal: string) {
const editor = this.editor;
const endPoint = editor.getCursor('end');
if (editor.getSelection() === oldVal) {
@ -96,7 +99,7 @@ class CodeMirrorEditor {
}
}
async function handleUploadFiles(editor, dropzoneEl, files, e) {
async function handleUploadFiles(editor: CodeMirrorEditor | TextareaEditor, dropzoneEl: HTMLElement, files: Array<File> | FileList, e: Event) {
e.preventDefault();
for (const file of files) {
const name = file.name.slice(0, file.name.lastIndexOf('.'));
@ -109,13 +112,13 @@ async function handleUploadFiles(editor, dropzoneEl, files, e) {
}
}
export function removeAttachmentLinksFromMarkdown(text, fileUuid) {
export function removeAttachmentLinksFromMarkdown(text: string, fileUuid: string) {
text = text.replace(new RegExp(`!?\\[([^\\]]+)\\]\\(/?attachments/${fileUuid}\\)`, 'g'), '');
text = text.replace(new RegExp(`<img[^>]+src="/?attachments/${fileUuid}"[^>]*>`, 'g'), '');
return text;
}
function handleClipboardText(textarea, e, {text, isShiftDown}) {
function handleClipboardText(textarea: HTMLTextAreaElement, e: ClipboardEvent, text: string, isShiftDown: boolean) {
// pasting with "shift" means "paste as original content" in most applications
if (isShiftDown) return; // let the browser handle it
@ -131,7 +134,7 @@ function handleClipboardText(textarea, e, {text, isShiftDown}) {
}
// extract text and images from "paste" event
function getPastedContent(e) {
function getPastedContent(e: ClipboardEvent) {
const images = [];
for (const item of e.clipboardData?.items ?? []) {
if (item.type?.startsWith('image/')) {
@ -142,8 +145,8 @@ function getPastedContent(e) {
return {text, images};
}
export function initEasyMDEPaste(easyMDE, dropzoneEl) {
const editor = new CodeMirrorEditor(easyMDE.codemirror);
export function initEasyMDEPaste(easyMDE: EasyMDE, dropzoneEl: HTMLElement) {
const editor = new CodeMirrorEditor(easyMDE.codemirror as any);
easyMDE.codemirror.on('paste', (_, e) => {
const {images} = getPastedContent(e);
if (!images.length) return;
@ -160,28 +163,28 @@ export function initEasyMDEPaste(easyMDE, dropzoneEl) {
});
}
export function initTextareaEvents(textarea, dropzoneEl) {
export function initTextareaEvents(textarea: HTMLTextAreaElement, dropzoneEl: HTMLElement) {
let isShiftDown = false;
textarea.addEventListener('keydown', (e) => {
textarea.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.shiftKey) isShiftDown = true;
});
textarea.addEventListener('keyup', (e) => {
textarea.addEventListener('keyup', (e: KeyboardEvent) => {
if (!e.shiftKey) isShiftDown = false;
});
textarea.addEventListener('paste', (e) => {
textarea.addEventListener('paste', (e: ClipboardEvent) => {
const {images, text} = getPastedContent(e);
if (images.length && dropzoneEl) {
handleUploadFiles(new TextareaEditor(textarea), dropzoneEl, images, e);
} else if (text) {
handleClipboardText(textarea, e, {text, isShiftDown});
handleClipboardText(textarea, e, text, isShiftDown);
}
});
textarea.addEventListener('drop', (e) => {
textarea.addEventListener('drop', (e: DragEvent) => {
if (!e.dataTransfer.files.length) return;
if (!dropzoneEl) return;
handleUploadFiles(new TextareaEditor(textarea), dropzoneEl, e.dataTransfer.files, e);
});
dropzoneEl?.dropzone.on(DropzoneCustomEventRemovedFile, ({fileUuid}) => {
dropzoneEl?.dropzone.on(DropzoneCustomEventRemovedFile, ({fileUuid}: {fileUuid: string}) => {
const newText = removeAttachmentLinksFromMarkdown(textarea.value, fileUuid);
if (textarea.value !== newText) textarea.value = newText;
});

View file

@ -1,6 +1,6 @@
import {querySingleVisibleElem} from '../../utils/dom.ts';
export function handleGlobalEnterQuickSubmit(target) {
export function handleGlobalEnterQuickSubmit(target: HTMLElement) {
let form = target.closest('form');
if (form) {
if (!form.checkValidity()) {

View file

@ -14,7 +14,7 @@ export function initCompSearchUserBox() {
minCharacters: 2,
apiSettings: {
url: `${appSubUrl}/user/search_candidates?q={query}`,
onResponse(response) {
onResponse(response: any) {
const resultItems = [];
const searchQuery = searchUserBox.querySelector('input').value;
const searchQueryUppercase = searchQuery.toUpperCase();

View file

@ -5,6 +5,7 @@ import {parseIssueHref, parseRepoOwnerPathInfo} from '../../utils.ts';
import {createElementFromAttrs, createElementFromHTML} from '../../utils/dom.ts';
import {getIssueColor, getIssueIcon} from '../issue.ts';
import {debounce} from 'perfect-debounce';
import type TextExpanderElement from '@github/text-expander-element';
const debouncedSuggestIssues = debounce((key: string, text: string) => new Promise<{matched:boolean; fragment?: HTMLElement}>(async (resolve) => {
const issuePathInfo = parseIssueHref(window.location.href);
@ -32,8 +33,8 @@ const debouncedSuggestIssues = debounce((key: string, text: string) => new Promi
resolve({matched: true, fragment: ul});
}), 100);
export function initTextExpander(expander) {
expander?.addEventListener('text-expander-change', ({detail: {key, provide, text}}) => {
export function initTextExpander(expander: TextExpanderElement) {
expander?.addEventListener('text-expander-change', ({detail: {key, provide, text}}: Record<string, any>) => {
if (key === ':') {
const matches = matchEmoji(text);
if (!matches.length) return provide({matched: false});
@ -84,7 +85,7 @@ export function initTextExpander(expander) {
provide(debouncedSuggestIssues(key, text));
}
});
expander?.addEventListener('text-expander-value', ({detail}) => {
expander?.addEventListener('text-expander-value', ({detail}: Record<string, any>) => {
if (detail?.item) {
// add a space after @mentions and #issue as it's likely the user wants one
const suffix = ['@', '#'].includes(detail.key) ? ' ' : '';

View file

@ -4,11 +4,11 @@ import {parseIssueHref} from '../utils.ts';
import {createTippy} from '../modules/tippy.ts';
export function initContextPopups() {
const refIssues = document.querySelectorAll('.ref-issue');
const refIssues = document.querySelectorAll<HTMLElement>('.ref-issue');
attachRefIssueContextPopup(refIssues);
}
export function attachRefIssueContextPopup(refIssues) {
export function attachRefIssueContextPopup(refIssues: NodeListOf<HTMLElement>) {
for (const refIssue of refIssues) {
if (refIssue.classList.contains('ref-external-issue')) continue;

View file

@ -46,7 +46,7 @@ export function initCopyContent() {
showTemporaryTooltip(btn, i18n.copy_success);
} else {
if (isRasterImage) {
const success = await clippie(await convertImage(content, 'image/png'));
const success = await clippie(await convertImage(content as Blob, 'image/png'));
showTemporaryTooltip(btn, success ? i18n.copy_success : i18n.copy_error);
} else {
showTemporaryTooltip(btn, i18n.copy_error);

View file

@ -6,16 +6,18 @@ import {GET, POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {createElementFromHTML, createElementFromAttrs} from '../utils/dom.ts';
import {isImageFile, isVideoFile} from '../utils.ts';
import type {DropzoneFile} from 'dropzone/index.js';
import type {DropzoneFile, DropzoneOptions} from 'dropzone/index.js';
const {csrfToken, i18n} = window.config;
type CustomDropzoneFile = DropzoneFile & {uuid: string};
// dropzone has its owner event dispatcher (emitter)
export const DropzoneCustomEventReloadFiles = 'dropzone-custom-reload-files';
export const DropzoneCustomEventRemovedFile = 'dropzone-custom-removed-file';
export const DropzoneCustomEventUploadDone = 'dropzone-custom-upload-done';
async function createDropzone(el, opts) {
async function createDropzone(el: HTMLElement, opts: DropzoneOptions) {
const [{default: Dropzone}] = await Promise.all([
import(/* webpackChunkName: "dropzone" */'dropzone'),
import(/* webpackChunkName: "dropzone" */'dropzone/dist/dropzone.css'),
@ -23,7 +25,7 @@ async function createDropzone(el, opts) {
return new Dropzone(el, opts);
}
export function generateMarkdownLinkForAttachment(file, {width, dppx}: {width?: number, dppx?: number} = {}) {
export function generateMarkdownLinkForAttachment(file: Partial<CustomDropzoneFile>, {width, dppx}: {width?: number, dppx?: number} = {}) {
let fileMarkdown = `[${file.name}](/attachments/${file.uuid})`;
if (isImageFile(file)) {
fileMarkdown = `!${fileMarkdown}`;
@ -43,7 +45,7 @@ export function generateMarkdownLinkForAttachment(file, {width, dppx}: {width?:
return fileMarkdown;
}
function addCopyLink(file) {
function addCopyLink(file: Partial<CustomDropzoneFile>) {
// Create a "Copy Link" element, to conveniently copy the image or file link as Markdown to the clipboard
// The "<a>" element has a hardcoded cursor: pointer because the default is overridden by .dropzone
const copyLinkEl = createElementFromHTML(`
@ -58,6 +60,8 @@ function addCopyLink(file) {
file.previewTemplate.append(copyLinkEl);
}
type FileUuidDict = Record<string, {submitted: boolean}>;
/**
* @param {HTMLElement} dropzoneEl
*/
@ -67,7 +71,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) {
const attachmentBaseLinkUrl = dropzoneEl.getAttribute('data-link-url');
let disableRemovedfileEvent = false; // when resetting the dropzone (removeAllFiles), disable the "removedfile" event
let fileUuidDict = {}; // to record: if a comment has been saved, then the uploaded files won't be deleted from server when clicking the Remove in the dropzone
let fileUuidDict: FileUuidDict = {}; // to record: if a comment has been saved, then the uploaded files won't be deleted from server when clicking the Remove in the dropzone
const opts: Record<string, any> = {
url: dropzoneEl.getAttribute('data-upload-url'),
headers: {'X-Csrf-Token': csrfToken},
@ -89,7 +93,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) {
// "http://localhost:3000/owner/repo/issues/[object%20Event]"
// the reason is that the preview "callback(dataURL)" is assign to "img.onerror" then "thumbnail" uses the error object as the dataURL and generates '<img src="[object Event]">'
const dzInst = await createDropzone(dropzoneEl, opts);
dzInst.on('success', (file: DropzoneFile & {uuid: string}, resp: any) => {
dzInst.on('success', (file: CustomDropzoneFile, resp: any) => {
file.uuid = resp.uuid;
fileUuidDict[file.uuid] = {submitted: false};
const input = createElementFromAttrs('input', {name: 'files', type: 'hidden', id: `dropzone-file-${resp.uuid}`, value: resp.uuid});
@ -98,7 +102,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) {
dzInst.emit(DropzoneCustomEventUploadDone, {file});
});
dzInst.on('removedfile', async (file: DropzoneFile & {uuid: string}) => {
dzInst.on('removedfile', async (file: CustomDropzoneFile) => {
if (disableRemovedfileEvent) return;
dzInst.emit(DropzoneCustomEventRemovedFile, {fileUuid: file.uuid});

View file

@ -15,13 +15,13 @@ export const emojiKeys = Object.keys(tempMap).sort((a, b) => {
return a.localeCompare(b);
});
const emojiMap = {};
const emojiMap: Record<string, string> = {};
for (const key of emojiKeys) {
emojiMap[key] = tempMap[key];
}
// retrieve HTML for given emoji name
export function emojiHTML(name) {
export function emojiHTML(name: string) {
let inner;
if (Object.hasOwn(customEmojis, name)) {
inner = `<img alt=":${name}:" src="${assetUrlPrefix}/img/emoji/${name}.png">`;
@ -33,6 +33,6 @@ export function emojiHTML(name) {
}
// retrieve string for given emoji name
export function emojiString(name) {
export function emojiString(name: string) {
return emojiMap[name] || `:${name}:`;
}

View file

@ -5,15 +5,15 @@ import {svg} from '../svg.ts';
// The fold arrow is the icon displayed on the upper left of the file box, especially intended for components having the 'fold-file' class.
// The file content box is the box that should be hidden or shown, especially intended for components having the 'file-content' class.
//
export function setFileFolding(fileContentBox, foldArrow, newFold) {
export function setFileFolding(fileContentBox: HTMLElement, foldArrow: HTMLElement, newFold: boolean) {
foldArrow.innerHTML = svg(`octicon-chevron-${newFold ? 'right' : 'down'}`, 18);
fileContentBox.setAttribute('data-folded', newFold);
fileContentBox.setAttribute('data-folded', String(newFold));
if (newFold && fileContentBox.getBoundingClientRect().top < 0) {
fileContentBox.scrollIntoView();
}
}
// Like `setFileFolding`, except that it automatically inverts the current file folding state.
export function invertFileFolding(fileContentBox, foldArrow) {
export function invertFileFolding(fileContentBox:HTMLElement, foldArrow: HTMLElement) {
setFileFolding(fileContentBox, foldArrow, fileContentBox.getAttribute('data-folded') !== 'true');
}

View file

@ -7,7 +7,7 @@ export function initHeatmap() {
if (!el) return;
try {
const heatmap = {};
const heatmap: Record<string, number> = {};
for (const {contributions, timestamp} of JSON.parse(el.getAttribute('data-heatmap-data'))) {
// Convert to user timezone and sum contributions by date
const dateStr = new Date(timestamp * 1000).toDateString();

View file

@ -3,7 +3,7 @@ import {hideElem, loadElem, queryElemChildren, queryElems} from '../utils/dom.ts
import {parseDom} from '../utils.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
function getDefaultSvgBoundsIfUndefined(text, src) {
function getDefaultSvgBoundsIfUndefined(text: string, src: string) {
const defaultSize = 300;
const maxSize = 99999;
@ -38,7 +38,7 @@ function getDefaultSvgBoundsIfUndefined(text, src) {
return null;
}
function createContext(imageAfter, imageBefore) {
function createContext(imageAfter: HTMLImageElement, imageBefore: HTMLImageElement) {
const sizeAfter = {
width: imageAfter?.width || 0,
height: imageAfter?.height || 0,
@ -123,7 +123,7 @@ class ImageDiff {
queryElemChildren(containerEl, '.image-diff-tabs', (el) => el.classList.remove('is-loading'));
}
initSideBySide(sizes) {
initSideBySide(sizes: Record<string, any>) {
let factor = 1;
if (sizes.maxSize.width > (this.diffContainerWidth - 24) / 2) {
factor = (this.diffContainerWidth - 24) / 2 / sizes.maxSize.width;
@ -176,7 +176,7 @@ class ImageDiff {
}
}
initSwipe(sizes) {
initSwipe(sizes: Record<string, any>) {
let factor = 1;
if (sizes.maxSize.width > this.diffContainerWidth - 12) {
factor = (this.diffContainerWidth - 12) / sizes.maxSize.width;
@ -215,14 +215,14 @@ class ImageDiff {
this.containerEl.querySelector('.swipe-bar').addEventListener('mousedown', (e) => {
e.preventDefault();
this.initSwipeEventListeners(e.currentTarget);
this.initSwipeEventListeners(e.currentTarget as HTMLElement);
});
}
initSwipeEventListeners(swipeBar) {
const swipeFrame = swipeBar.parentNode;
initSwipeEventListeners(swipeBar: HTMLElement) {
const swipeFrame = swipeBar.parentNode as HTMLElement;
const width = swipeFrame.clientWidth;
const onSwipeMouseMove = (e) => {
const onSwipeMouseMove = (e: MouseEvent) => {
e.preventDefault();
const rect = swipeFrame.getBoundingClientRect();
const value = Math.max(0, Math.min(e.clientX - rect.left, width));
@ -237,7 +237,7 @@ class ImageDiff {
document.addEventListener('mouseup', removeEventListeners);
}
initOverlay(sizes) {
initOverlay(sizes: Record<string, any>) {
let factor = 1;
if (sizes.maxSize.width > this.diffContainerWidth - 12) {
factor = (this.diffContainerWidth - 12) / sizes.maxSize.width;

View file

@ -12,11 +12,12 @@ export function initInstall() {
initPreInstall();
}
}
function initPreInstall() {
const defaultDbUser = 'gitea';
const defaultDbName = 'gitea';
const defaultDbHosts = {
const defaultDbHosts: Record<string, string> = {
mysql: '127.0.0.1:3306',
postgres: '127.0.0.1:5432',
mssql: '127.0.0.1:1433',

View file

@ -21,7 +21,7 @@ function initOrgTeamSearchRepoBox() {
minCharacters: 2,
apiSettings: {
url: `${appSubUrl}/repo/search?q={query}&uid=${$searchRepoBox.data('uid')}`,
onResponse(response) {
onResponse(response: any) {
const items = [];
for (const item of response.data) {
items.push({

View file

@ -59,13 +59,13 @@ export function initViewedCheckboxListenerFor() {
const fileName = checkbox.getAttribute('name');
// check if the file is in our difftreestore and if we find it -> change the IsViewed status
const fileInPageData = diffTreeStore().files.find((x) => x.Name === fileName);
const fileInPageData = diffTreeStore().files.find((x: Record<string, any>) => x.Name === fileName);
if (fileInPageData) {
fileInPageData.IsViewed = this.checked;
}
// Unfortunately, actual forms cause too many problems, hence another approach is needed
const files = {};
const files: Record<string, boolean> = {};
files[fileName] = this.checked;
const data: Record<string, any> = {files};
const headCommitSHA = form.getAttribute('data-headcommit');
@ -82,13 +82,13 @@ export function initViewedCheckboxListenerFor() {
export function initExpandAndCollapseFilesButton() {
// expand btn
document.querySelector(expandFilesBtnSelector)?.addEventListener('click', () => {
for (const box of document.querySelectorAll('.file-content[data-folded="true"]')) {
for (const box of document.querySelectorAll<HTMLElement>('.file-content[data-folded="true"]')) {
setFileFolding(box, box.querySelector('.fold-file'), false);
}
});
// collapse btn, need to exclude the div of “show more”
document.querySelector(collapseFilesBtnSelector)?.addEventListener('click', () => {
for (const box of document.querySelectorAll('.file-content:not([data-folded="true"])')) {
for (const box of document.querySelectorAll<HTMLElement>('.file-content:not([data-folded="true"])')) {
if (box.getAttribute('id') === 'diff-incomplete') continue;
setFileFolding(box, box.querySelector('.fold-file'), true);
}

View file

@ -1,4 +1,4 @@
import {queryElems} from '../utils/dom.ts';
import {queryElems, type DOMEvent} from '../utils/dom.ts';
import {POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {sleep} from '../utils.ts';
@ -7,10 +7,10 @@ import {createApp} from 'vue';
import {toOriginUrl} from '../utils/url.ts';
import {createTippy} from '../modules/tippy.ts';
async function onDownloadArchive(e) {
async function onDownloadArchive(e: DOMEvent<MouseEvent>) {
e.preventDefault();
// there are many places using the "archive-link", eg: the dropdown on the repo code page, the release list
const el = e.target.closest('a.archive-link[href]');
const el = e.target.closest<HTMLAnchorElement>('a.archive-link[href]');
const targetLoading = el.closest('.ui.dropdown') ?? el;
targetLoading.classList.add('is-loading', 'loading-icon-2px');
try {
@ -107,7 +107,7 @@ export function initRepoCloneButtons() {
queryElems(document, '.clone-buttons-combo', initCloneSchemeUrlSelection);
}
export async function updateIssuesMeta(url, action, issue_ids, id) {
export async function updateIssuesMeta(url: string, action: string, issue_ids: string, id: string) {
try {
const response = await POST(url, {data: new URLSearchParams({action, issue_ids, id})});
if (!response.ok) {

View file

@ -168,7 +168,7 @@ function onShowMoreFiles() {
initDiffHeaderPopup();
}
export async function loadMoreFiles(url) {
export async function loadMoreFiles(url: string) {
const target = document.querySelector('a#diff-show-more-files');
if (target?.classList.contains('disabled') || pageData.diffFileInfo.isLoadingNewData) {
return;

View file

@ -168,7 +168,7 @@ export function initRepoEditor() {
silent: true,
dirtyClass: dirtyFileClass,
fieldSelector: ':input:not(.commit-form-wrapper :input)',
change($form) {
change($form: any) {
const dirty = $form[0]?.classList.contains(dirtyFileClass);
commitButton.disabled = !dirty;
},

View file

@ -4,13 +4,15 @@ import {pathEscapeSegments} from '../utils/url.ts';
import {GET} from '../modules/fetch.ts';
const threshold = 50;
let files = [];
let repoFindFileInput, repoFindFileTableBody, repoFindFileNoResult;
let files: Array<string> = [];
let repoFindFileInput: HTMLInputElement;
let repoFindFileTableBody: HTMLElement;
let repoFindFileNoResult: HTMLElement;
// return the case-insensitive sub-match result as an array: [unmatched, matched, unmatched, matched, ...]
// res[even] is unmatched, res[odd] is matched, see unit tests for examples
// argument subLower must be a lower-cased string.
export function strSubMatch(full, subLower) {
export function strSubMatch(full: string, subLower: string) {
const res = [''];
let i = 0, j = 0;
const fullLower = full.toLowerCase();
@ -38,7 +40,7 @@ export function strSubMatch(full, subLower) {
return res;
}
export function calcMatchedWeight(matchResult) {
export function calcMatchedWeight(matchResult: Array<any>) {
let weight = 0;
for (let i = 0; i < matchResult.length; i++) {
if (i % 2 === 1) { // matches are on odd indices, see strSubMatch
@ -49,7 +51,7 @@ export function calcMatchedWeight(matchResult) {
return weight;
}
export function filterRepoFilesWeighted(files, filter) {
export function filterRepoFilesWeighted(files: Array<string>, filter: string) {
let filterResult = [];
if (filter) {
const filterLower = filter.toLowerCase();
@ -71,7 +73,7 @@ export function filterRepoFilesWeighted(files, filter) {
return filterResult;
}
function filterRepoFiles(filter) {
function filterRepoFiles(filter: string) {
const treeLink = repoFindFileInput.getAttribute('data-url-tree-link');
repoFindFileTableBody.innerHTML = '';

View file

@ -92,7 +92,7 @@ export function initRepoTopicBar() {
onResponse(this: any, res: any) {
const formattedResponse = {
success: false,
results: [],
results: [] as Array<Record<string, any>>,
};
const query = stripTags(this.urlData.query.trim());
let found_query = false;
@ -134,12 +134,12 @@ export function initRepoTopicBar() {
return formattedResponse;
},
},
onLabelCreate(value) {
onLabelCreate(value: string) {
value = value.toLowerCase().trim();
this.attr('data-value', value).contents().first().replaceWith(value);
return fomanticQuery(this);
},
onAdd(addedValue, _addedText, $addedChoice) {
onAdd(addedValue: string, _addedText: any, $addedChoice: any) {
addedValue = addedValue.toLowerCase().trim();
$addedChoice[0].setAttribute('data-value', addedValue);
$addedChoice[0].setAttribute('data-text', addedValue);

View file

@ -33,7 +33,7 @@ function showContentHistoryDetail(issueBaseUrl: string, commentId: string, histo
$fomanticDropdownOptions.dropdown({
showOnFocus: false,
allowReselection: true,
async onChange(_value, _text, $item) {
async onChange(_value: string, _text: string, $item: any) {
const optionItem = $item.data('option-item');
if (optionItem === 'delete') {
if (window.confirm(i18nTextDeleteFromHistoryConfirm)) {
@ -115,7 +115,7 @@ function showContentHistoryMenu(issueBaseUrl: string, elCommentItem: Element, co
onHide() {
$fomanticDropdown.dropdown('change values', null);
},
onChange(value, itemHtml, $item) {
onChange(value: string, itemHtml: string, $item: any) {
if (value && !$item.find('[data-history-is-deleted=1]').length) {
showContentHistoryDetail(issueBaseUrl, commentId, value, itemHtml);
}

View file

@ -2,14 +2,14 @@ import {handleReply} from './repo-issue.ts';
import {getComboMarkdownEditor, initComboMarkdownEditor, ComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
import {POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {hideElem, querySingleVisibleElem, showElem} from '../utils/dom.ts';
import {hideElem, querySingleVisibleElem, showElem, type DOMEvent} from '../utils/dom.ts';
import {attachRefIssueContextPopup} from './contextpopup.ts';
import {initCommentContent, initMarkupContent} from '../markup/content.ts';
import {triggerUploadStateChanged} from './comp/EditorUpload.ts';
import {convertHtmlToMarkdown} from '../markup/html2markdown.ts';
import {applyAreYouSure, reinitializeAreYouSure} from '../vendor/jquery.are-you-sure.ts';
async function tryOnEditContent(e) {
async function tryOnEditContent(e: DOMEvent<MouseEvent>) {
const clickTarget = e.target.closest('.edit-content');
if (!clickTarget) return;
@ -21,14 +21,14 @@ async function tryOnEditContent(e) {
let comboMarkdownEditor : ComboMarkdownEditor;
const cancelAndReset = (e) => {
const cancelAndReset = (e: Event) => {
e.preventDefault();
showElem(renderContent);
hideElem(editContentZone);
comboMarkdownEditor.dropzoneReloadFiles();
};
const saveAndRefresh = async (e) => {
const saveAndRefresh = async (e: Event) => {
e.preventDefault();
// we are already in a form, do not bubble up to the document otherwise there will be other "form submit handlers"
// at the moment, the form submit event conflicts with initRepoDiffConversationForm (global '.conversation-holder form' event handler)
@ -60,7 +60,7 @@ async function tryOnEditContent(e) {
} else {
renderContent.innerHTML = data.content;
rawContent.textContent = comboMarkdownEditor.value();
const refIssues = renderContent.querySelectorAll('p .ref-issue');
const refIssues = renderContent.querySelectorAll<HTMLElement>('p .ref-issue');
attachRefIssueContextPopup(refIssues);
}
const content = segment;
@ -125,7 +125,7 @@ function extractSelectedMarkdown(container: HTMLElement) {
return convertHtmlToMarkdown(el);
}
async function tryOnQuoteReply(e) {
async function tryOnQuoteReply(e: Event) {
const clickTarget = (e.target as HTMLElement).closest('.quote-reply');
if (!clickTarget) return;
@ -139,7 +139,7 @@ async function tryOnQuoteReply(e) {
let editor;
if (clickTarget.classList.contains('quote-reply-diff')) {
const replyBtn = clickTarget.closest('.comment-code-cloud').querySelector('button.comment-form-reply');
const replyBtn = clickTarget.closest('.comment-code-cloud').querySelector<HTMLElement>('button.comment-form-reply');
editor = await handleReply(replyBtn);
} else {
// for normal issue/comment page

View file

@ -7,6 +7,7 @@ import {createSortable} from '../modules/sortable.ts';
import {DELETE, POST} from '../modules/fetch.ts';
import {parseDom} from '../utils.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
import type {SortableEvent} from 'sortablejs';
function initRepoIssueListCheckboxes() {
const issueSelectAll = document.querySelector<HTMLInputElement>('.issue-checkbox-all');
@ -104,7 +105,7 @@ function initDropdownUserRemoteSearch(el: Element) {
$searchDropdown.dropdown('setting', {
fullTextSearch: true,
selectOnKeydown: false,
action: (_text, value) => {
action: (_text: string, value: string) => {
window.location.href = actionJumpUrl.replace('{username}', encodeURIComponent(value));
},
});
@ -133,7 +134,7 @@ function initDropdownUserRemoteSearch(el: Element) {
$searchDropdown.dropdown('setting', 'apiSettings', {
cache: false,
url: `${searchUrl}&q={query}`,
onResponse(resp) {
onResponse(resp: any) {
// the content is provided by backend IssuePosters handler
processedResults.length = 0;
for (const item of resp.results) {
@ -153,7 +154,7 @@ function initDropdownUserRemoteSearch(el: Element) {
const dropdownSetup = {...$searchDropdown.dropdown('internal', 'setup')};
const dropdownTemplates = $searchDropdown.dropdown('setting', 'templates');
$searchDropdown.dropdown('internal', 'setup', dropdownSetup);
dropdownSetup.menu = function (values) {
dropdownSetup.menu = function (values: any) {
// remove old dynamic items
for (const el of elMenu.querySelectorAll(':scope > .dynamic-item')) {
el.remove();
@ -193,7 +194,7 @@ function initPinRemoveButton() {
}
}
async function pinMoveEnd(e) {
async function pinMoveEnd(e: SortableEvent) {
const url = e.item.getAttribute('data-move-url');
const id = Number(e.item.getAttribute('data-issue-id'));
await POST(url, {data: {id, position: e.newIndex + 1}});

View file

@ -46,7 +46,7 @@ class IssueSidebarComboList {
return Array.from(this.elDropdown.querySelectorAll('.menu > .item.checked'), (el) => el.getAttribute('data-value'));
}
updateUiList(changedValues) {
updateUiList(changedValues: Array<string>) {
const elEmptyTip = this.elList.querySelector('.item.empty-list');
queryElemChildren(this.elList, '.item:not(.empty-list)', (el) => el.remove());
for (const value of changedValues) {
@ -60,7 +60,7 @@ class IssueSidebarComboList {
toggleElem(elEmptyTip, !hasItems);
}
async updateToBackend(changedValues) {
async updateToBackend(changedValues: Array<string>) {
if (this.updateAlgo === 'diff') {
for (const value of this.initialValues) {
if (!changedValues.includes(value)) {
@ -93,7 +93,7 @@ class IssueSidebarComboList {
}
}
async onItemClick(e) {
async onItemClick(e: Event) {
const elItem = (e.target as HTMLElement).closest('.item');
if (!elItem) return;
e.preventDefault();

View file

@ -32,8 +32,8 @@ export function initRepoIssueSidebarList() {
fullTextSearch: true,
apiSettings: {
url: issueSearchUrl,
onResponse(response) {
const filteredResponse = {success: true, results: []};
onResponse(response: any) {
const filteredResponse = {success: true, results: [] as Array<Record<string, any>>};
const currIssueId = $('#new-dependency-drop-list').data('issue-id');
// Parse the response from the api to work with our dropdown
$.each(response, (_i, issue) => {
@ -247,7 +247,7 @@ export function initRepoPullRequestUpdate() {
});
$('.update-button > .dropdown').dropdown({
onChange(_text, _value, $choice) {
onChange(_text: string, _value: string, $choice: any) {
const choiceEl = $choice[0];
const url = choiceEl.getAttribute('data-do');
if (url) {
@ -298,8 +298,8 @@ export function initRepoIssueReferenceRepositorySearch() {
.dropdown({
apiSettings: {
url: `${appSubUrl}/repo/search?q={query}&limit=20`,
onResponse(response) {
const filteredResponse = {success: true, results: []};
onResponse(response: any) {
const filteredResponse = {success: true, results: [] as Array<Record<string, any>>};
$.each(response.data, (_r, repo) => {
filteredResponse.results.push({
name: htmlEscape(repo.repository.full_name),
@ -310,7 +310,7 @@ export function initRepoIssueReferenceRepositorySearch() {
},
cache: false,
},
onChange(_value, _text, $choice) {
onChange(_value: string, _text: string, $choice: any) {
const $form = $choice.closest('form');
if (!$form.length) return;
@ -360,7 +360,7 @@ export function initRepoIssueComments() {
});
}
export async function handleReply(el) {
export async function handleReply(el: HTMLElement) {
const form = el.closest('.comment-code-cloud').querySelector('.comment-form');
const textarea = form.querySelector('textarea');
@ -379,7 +379,7 @@ export function initRepoPullRequestReview() {
const groupID = commentDiv.closest('div[id^="code-comments-"]')?.getAttribute('id');
if (groupID && groupID.startsWith('code-comments-')) {
const id = groupID.slice(14);
const ancestorDiffBox = commentDiv.closest('.diff-file-box');
const ancestorDiffBox = commentDiv.closest<HTMLElement>('.diff-file-box');
hideElem(`#show-outdated-${id}`);
showElem(`#code-comments-${id}, #code-preview-${id}, #hide-outdated-${id}`);
@ -589,7 +589,7 @@ export function initRepoIssueBranchSelect() {
});
}
async function initSingleCommentEditor($commentForm) {
async function initSingleCommentEditor($commentForm: any) {
// pages:
// * normal new issue/pr page: no status-button, no comment-button (there is only a normal submit button which can submit empty content)
// * issue/pr view page: with comment form, has status-button and comment-button
@ -611,7 +611,7 @@ async function initSingleCommentEditor($commentForm) {
syncUiState();
}
function initIssueTemplateCommentEditors($commentForm) {
function initIssueTemplateCommentEditors($commentForm: any) {
// pages:
// * new issue with issue template
const $comboFields = $commentForm.find('.combo-editor-dropzone');

View file

@ -1,11 +1,11 @@
import {hideElem, showElem} from '../utils/dom.ts';
import {hideElem, showElem, type DOMEvent} from '../utils/dom.ts';
import {GET, POST} from '../modules/fetch.ts';
export function initRepoMigrationStatusChecker() {
const repoMigrating = document.querySelector('#repo_migrating');
if (!repoMigrating) return;
document.querySelector('#repo_migrating_retry')?.addEventListener('click', doMigrationRetry);
document.querySelector<HTMLButtonElement>('#repo_migrating_retry')?.addEventListener('click', doMigrationRetry);
const repoLink = repoMigrating.getAttribute('data-migrating-repo-link');
@ -55,7 +55,7 @@ export function initRepoMigrationStatusChecker() {
syncTaskStatus(); // no await
}
async function doMigrationRetry(e) {
async function doMigrationRetry(e: DOMEvent<MouseEvent>) {
await POST(e.target.getAttribute('data-migrating-task-retry-url'));
window.location.reload();
}

View file

@ -23,7 +23,7 @@ function initRepoNewTemplateSearch(form: HTMLFormElement) {
$dropdown.dropdown('setting', {
apiSettings: {
url: `${appSubUrl}/repo/search?q={query}&template=true&priority_owner_id=${inputRepoOwnerUid.value}`,
onResponse(response) {
onResponse(response: any) {
const results = [];
results.push({name: '', value: ''}); // empty item means not using template
for (const tmplRepo of response.data) {
@ -66,7 +66,7 @@ export function initRepoNew() {
let help = form.querySelector(`.help[data-help-for-repo-name="${CSS.escape(inputRepoName.value)}"]`);
if (!help) help = form.querySelector(`.help[data-help-for-repo-name=""]`);
showElem(help);
const repoNamePreferPrivate = {'.profile': false, '.profile-private': true};
const repoNamePreferPrivate: Record<string, boolean> = {'.profile': false, '.profile-private': true};
const preferPrivate = repoNamePreferPrivate[inputRepoName.value];
// inputPrivate might be disabled because site admin "force private"
if (preferPrivate !== undefined && !inputPrivate.closest('.disabled, [disabled]')) {

View file

@ -12,7 +12,7 @@ function initRepoSettingsCollaboration() {
for (const dropdownEl of queryElems(document, '.page-content.repository .ui.dropdown.access-mode')) {
const textEl = dropdownEl.querySelector(':scope > .text');
$(dropdownEl).dropdown({
async action(text, value) {
async action(text: string, value: string) {
dropdownEl.classList.add('is-loading', 'loading-icon-2px');
const lastValue = dropdownEl.getAttribute('data-last-value');
$(dropdownEl).dropdown('hide');
@ -53,8 +53,8 @@ function initRepoSettingsSearchTeamBox() {
apiSettings: {
url: `${appSubUrl}/org/${searchTeamBox.getAttribute('data-org-name')}/teams/-/search?q={query}`,
headers: {'X-Csrf-Token': csrfToken},
onResponse(response) {
const items = [];
onResponse(response: any) {
const items: Array<Record<string, any>> = [];
$.each(response.data, (_i, item) => {
items.push({
title: item.name,

View file

@ -70,7 +70,7 @@ async function initRepoWikiFormEditor() {
});
}
function collapseWikiTocForMobile(collapse) {
function collapseWikiTocForMobile(collapse: boolean) {
if (collapse) {
document.querySelector('.wiki-content-toc details')?.removeAttribute('open');
}

View file

@ -38,7 +38,7 @@ export function initStopwatch() {
}
let usingPeriodicPoller = false;
const startPeriodicPoller = (timeout) => {
const startPeriodicPoller = (timeout: number) => {
if (timeout <= 0 || !Number.isFinite(timeout)) return;
usingPeriodicPoller = true;
setTimeout(() => updateStopwatchWithCallback(startPeriodicPoller, timeout), timeout);
@ -103,7 +103,7 @@ export function initStopwatch() {
startPeriodicPoller(notificationSettings.MinTimeout);
}
async function updateStopwatchWithCallback(callback, timeout) {
async function updateStopwatchWithCallback(callback: (timeout: number) => void, timeout: number) {
const isSet = await updateStopwatch();
if (!isSet) {
@ -125,7 +125,7 @@ async function updateStopwatch() {
return updateStopwatchData(data);
}
function updateStopwatchData(data) {
function updateStopwatchData(data: any) {
const watch = data[0];
const btnEls = document.querySelectorAll('.active-stopwatch');
if (!watch) {

View file

@ -9,7 +9,7 @@ export function initTableSort() {
}
}
function tableSort(normSort, revSort, isDefault) {
function tableSort(normSort: string, revSort: string, isDefault: string) {
if (!normSort) return false;
if (!revSort) revSort = '';

View file

@ -1,14 +1,16 @@
import {emojiKeys, emojiHTML, emojiString} from './emoji.ts';
import {htmlEscape} from 'escape-goat';
function makeCollections({mentions, emoji}) {
const collections = [];
type TributeItem = Record<string, any>;
if (emoji) {
collections.push({
export async function attachTribute(element: HTMLElement) {
const {default: Tribute} = await import(/* webpackChunkName: "tribute" */'tributejs');
const collections = [
{ // emojis
trigger: ':',
requireLeadingSpace: true,
values: (query, cb) => {
values: (query: string, cb: (matches: Array<string>) => void) => {
const matches = [];
for (const name of emojiKeys) {
if (name.includes(query)) {
@ -18,22 +20,18 @@ function makeCollections({mentions, emoji}) {
}
cb(matches);
},
lookup: (item) => item,
selectTemplate: (item) => {
lookup: (item: TributeItem) => item,
selectTemplate: (item: TributeItem) => {
if (item === undefined) return null;
return emojiString(item.original);
},
menuItemTemplate: (item) => {
menuItemTemplate: (item: TributeItem) => {
return `<div class="tribute-item">${emojiHTML(item.original)}<span>${htmlEscape(item.original)}</span></div>`;
},
});
}
if (mentions) {
collections.push({
}, { // mentions
values: window.config.mentionValues ?? [],
requireLeadingSpace: true,
menuItemTemplate: (item) => {
menuItemTemplate: (item: TributeItem) => {
return `
<div class="tribute-item">
<img src="${htmlEscape(item.original.avatar)}" width="21" height="21"/>
@ -42,15 +40,9 @@ function makeCollections({mentions, emoji}) {
</div>
`;
},
});
}
},
];
return collections;
}
export async function attachTribute(element, {mentions, emoji}) {
const {default: Tribute} = await import(/* webpackChunkName: "tribute" */'tributejs');
const collections = makeCollections({mentions, emoji});
// @ts-expect-error TS2351: This expression is not constructable (strange, why)
const tribute = new Tribute({collection: collections, noMatchTemplate: ''});
tribute.attach(element);

View file

@ -114,7 +114,7 @@ async function login2FA() {
}
}
async function verifyAssertion(assertedCredential) {
async function verifyAssertion(assertedCredential: any) { // TODO: Credential type does not work
// Move data into Arrays in case it is super long
const authData = new Uint8Array(assertedCredential.response.authenticatorData);
const clientDataJSON = new Uint8Array(assertedCredential.response.clientDataJSON);
@ -148,7 +148,7 @@ async function verifyAssertion(assertedCredential) {
window.location.href = reply?.redirect ?? `${appSubUrl}/`;
}
async function webauthnRegistered(newCredential) {
async function webauthnRegistered(newCredential: any) { // TODO: Credential type does not work
const attestationObject = new Uint8Array(newCredential.response.attestationObject);
const clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
const rawId = new Uint8Array(newCredential.rawId);