aboutsummaryrefslogtreecommitdiffstats
path: root/web_src
diff options
context:
space:
mode:
authorsilverwind <me@silverwind.io>2024-12-15 22:02:32 +0100
committerGitHub <noreply@github.com>2024-12-16 05:02:32 +0800
commitc8ea41b049c887794e4dd87b690b3031b98458b9 (patch)
tree2e3fbfdeced634ff8079b19b9f9ecf769a714c76 /web_src
parent74b06d4f5cc8dd11140a778768d384c4240ecd66 (diff)
downloadgitea-c8ea41b049c887794e4dd87b690b3031b98458b9.tar.gz
gitea-c8ea41b049c887794e4dd87b690b3031b98458b9.zip
Fix remaining typescript issues, enable `tsc` (#32840)
Fixes 79 typescript errors. Discovered at least two bugs in `notifications.ts`, and I'm pretty sure this feature was at least partially broken and may still be, I don't really know how to test it. After this, only like ~10 typescript errors remain in the codebase but those are harder to solve. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Diffstat (limited to 'web_src')
-rw-r--r--web_src/js/features/common-issue-list.ts11
-rw-r--r--web_src/js/features/comp/ComboMarkdownEditor.ts4
-rw-r--r--web_src/js/features/comp/EasyMDEToolbarActions.ts44
-rw-r--r--web_src/js/features/comp/ReactionSelector.ts2
-rw-r--r--web_src/js/features/comp/WebHookEditor.ts2
-rw-r--r--web_src/js/features/dropzone.ts13
-rw-r--r--web_src/js/features/emoji.ts2
-rw-r--r--web_src/js/features/eventsource.sharedworker.ts7
-rw-r--r--web_src/js/features/heatmap.ts4
-rw-r--r--web_src/js/features/install.ts36
-rw-r--r--web_src/js/features/notification.ts31
-rw-r--r--web_src/js/features/oauth2-settings.ts8
-rw-r--r--web_src/js/features/pull-view-file.ts4
-rw-r--r--web_src/js/features/repo-editor.ts2
-rw-r--r--web_src/js/features/repo-search.ts7
-rw-r--r--web_src/js/features/repo-settings-branches.test.ts5
-rw-r--r--web_src/js/features/tribute.ts1
-rw-r--r--web_src/js/globals.d.ts15
-rw-r--r--web_src/js/modules/tippy.ts1
-rw-r--r--web_src/js/utils.ts10
20 files changed, 115 insertions, 94 deletions
diff --git a/web_src/js/features/common-issue-list.ts b/web_src/js/features/common-issue-list.ts
index e8a47eabad..e207364794 100644
--- a/web_src/js/features/common-issue-list.ts
+++ b/web_src/js/features/common-issue-list.ts
@@ -7,7 +7,7 @@ const reIssueSharpIndex = /^#(\d+)$/; // eg: "#123"
const reIssueOwnerRepoIndex = /^([-.\w]+)\/([-.\w]+)#(\d+)$/; // eg: "{owner}/{repo}#{index}"
// if the searchText can be parsed to an "issue goto link", return the link, otherwise return empty string
-export function parseIssueListQuickGotoLink(repoLink, searchText) {
+export function parseIssueListQuickGotoLink(repoLink: string, searchText: string) {
searchText = searchText.trim();
let targetUrl = '';
if (repoLink) {
@@ -15,13 +15,12 @@ export function parseIssueListQuickGotoLink(repoLink, searchText) {
if (reIssueIndex.test(searchText)) {
targetUrl = `${repoLink}/issues/${searchText}`;
} else if (reIssueSharpIndex.test(searchText)) {
- targetUrl = `${repoLink}/issues/${searchText.substr(1)}`;
+ targetUrl = `${repoLink}/issues/${searchText.substring(1)}`;
}
} else {
// try to parse it for a global search (eg: "owner/repo#123")
- const matchIssueOwnerRepoIndex = searchText.match(reIssueOwnerRepoIndex);
- if (matchIssueOwnerRepoIndex) {
- const [_, owner, repo, index] = matchIssueOwnerRepoIndex;
+ const [_, owner, repo, index] = reIssueOwnerRepoIndex.exec(searchText) || [];
+ if (owner) {
targetUrl = `${appSubUrl}/${owner}/${repo}/issues/${index}`;
}
}
@@ -33,7 +32,7 @@ export function initCommonIssueListQuickGoto() {
if (!goto) return;
const form = goto.closest('form');
- const input = form.querySelector('input[name=q]');
+ const input = form.querySelector<HTMLInputElement>('input[name=q]');
const repoLink = goto.getAttribute('data-repo-link');
form.addEventListener('submit', (e) => {
diff --git a/web_src/js/features/comp/ComboMarkdownEditor.ts b/web_src/js/features/comp/ComboMarkdownEditor.ts
index 80eabaa37a..bba50a1296 100644
--- a/web_src/js/features/comp/ComboMarkdownEditor.ts
+++ b/web_src/js/features/comp/ComboMarkdownEditor.ts
@@ -283,8 +283,8 @@ export class ComboMarkdownEditor {
];
}
- parseEasyMDEToolbar(EasyMDE, actions) {
- this.easyMDEToolbarActions = this.easyMDEToolbarActions || easyMDEToolbarActions(EasyMDE, this);
+ parseEasyMDEToolbar(easyMde: typeof EasyMDE, actions) {
+ this.easyMDEToolbarActions = this.easyMDEToolbarActions || easyMDEToolbarActions(easyMde, this);
const processed = [];
for (const action of actions) {
const actionButton = this.easyMDEToolbarActions[action];
diff --git a/web_src/js/features/comp/EasyMDEToolbarActions.ts b/web_src/js/features/comp/EasyMDEToolbarActions.ts
index d91dd23d11..ec5c7304be 100644
--- a/web_src/js/features/comp/EasyMDEToolbarActions.ts
+++ b/web_src/js/features/comp/EasyMDEToolbarActions.ts
@@ -1,100 +1,102 @@
import {svg} from '../../svg.ts';
+import type EasyMDE from 'easymde';
+import type {ComboMarkdownEditor} from './ComboMarkdownEditor.ts';
-export function easyMDEToolbarActions(EasyMDE, editor) {
- const actions = {
+export function easyMDEToolbarActions(easyMde: typeof EasyMDE, editor: ComboMarkdownEditor): Record<string, Partial<EasyMDE.ToolbarIcon | string>> {
+ const actions: Record<string, Partial<EasyMDE.ToolbarIcon> | string> = {
'|': '|',
'heading-1': {
- action: EasyMDE.toggleHeading1,
+ action: easyMde.toggleHeading1,
icon: svg('octicon-heading'),
title: 'Heading 1',
},
'heading-2': {
- action: EasyMDE.toggleHeading2,
+ action: easyMde.toggleHeading2,
icon: svg('octicon-heading'),
title: 'Heading 2',
},
'heading-3': {
- action: EasyMDE.toggleHeading3,
+ action: easyMde.toggleHeading3,
icon: svg('octicon-heading'),
title: 'Heading 3',
},
'heading-smaller': {
- action: EasyMDE.toggleHeadingSmaller,
+ action: easyMde.toggleHeadingSmaller,
icon: svg('octicon-heading'),
title: 'Decrease Heading',
},
'heading-bigger': {
- action: EasyMDE.toggleHeadingBigger,
+ action: easyMde.toggleHeadingBigger,
icon: svg('octicon-heading'),
title: 'Increase Heading',
},
'bold': {
- action: EasyMDE.toggleBold,
+ action: easyMde.toggleBold,
icon: svg('octicon-bold'),
title: 'Bold',
},
'italic': {
- action: EasyMDE.toggleItalic,
+ action: easyMde.toggleItalic,
icon: svg('octicon-italic'),
title: 'Italic',
},
'strikethrough': {
- action: EasyMDE.toggleStrikethrough,
+ action: easyMde.toggleStrikethrough,
icon: svg('octicon-strikethrough'),
title: 'Strikethrough',
},
'quote': {
- action: EasyMDE.toggleBlockquote,
+ action: easyMde.toggleBlockquote,
icon: svg('octicon-quote'),
title: 'Quote',
},
'code': {
- action: EasyMDE.toggleCodeBlock,
+ action: easyMde.toggleCodeBlock,
icon: svg('octicon-code'),
title: 'Code',
},
'link': {
- action: EasyMDE.drawLink,
+ action: easyMde.drawLink,
icon: svg('octicon-link'),
title: 'Link',
},
'unordered-list': {
- action: EasyMDE.toggleUnorderedList,
+ action: easyMde.toggleUnorderedList,
icon: svg('octicon-list-unordered'),
title: 'Unordered List',
},
'ordered-list': {
- action: EasyMDE.toggleOrderedList,
+ action: easyMde.toggleOrderedList,
icon: svg('octicon-list-ordered'),
title: 'Ordered List',
},
'image': {
- action: EasyMDE.drawImage,
+ action: easyMde.drawImage,
icon: svg('octicon-image'),
title: 'Image',
},
'table': {
- action: EasyMDE.drawTable,
+ action: easyMde.drawTable,
icon: svg('octicon-table'),
title: 'Table',
},
'horizontal-rule': {
- action: EasyMDE.drawHorizontalRule,
+ action: easyMde.drawHorizontalRule,
icon: svg('octicon-horizontal-rule'),
title: 'Horizontal Rule',
},
'preview': {
- action: EasyMDE.togglePreview,
+ action: easyMde.togglePreview,
icon: svg('octicon-eye'),
title: 'Preview',
},
'fullscreen': {
- action: EasyMDE.toggleFullScreen,
+ action: easyMde.toggleFullScreen,
icon: svg('octicon-screen-full'),
title: 'Fullscreen',
},
'side-by-side': {
- action: EasyMDE.toggleSideBySide,
+ action: easyMde.toggleSideBySide,
icon: svg('octicon-columns'),
title: 'Side by Side',
},
diff --git a/web_src/js/features/comp/ReactionSelector.ts b/web_src/js/features/comp/ReactionSelector.ts
index 1e955c7ab4..671bade3be 100644
--- a/web_src/js/features/comp/ReactionSelector.ts
+++ b/web_src/js/features/comp/ReactionSelector.ts
@@ -3,7 +3,7 @@ import {fomanticQuery} from '../../modules/fomantic/base.ts';
export function initCompReactionSelector(parent: ParentNode = document) {
for (const container of parent.querySelectorAll('.issue-content, .diff-file-body')) {
- container.addEventListener('click', async (e) => {
+ container.addEventListener('click', async (e: MouseEvent & {target: HTMLElement}) => {
// there are 2 places for the "reaction" buttons, one is the top-right reaction menu, one is the bottom of the comment
const target = e.target.closest('.comment-reaction-button');
if (!target) return;
diff --git a/web_src/js/features/comp/WebHookEditor.ts b/web_src/js/features/comp/WebHookEditor.ts
index b13a2ffca3..203396af80 100644
--- a/web_src/js/features/comp/WebHookEditor.ts
+++ b/web_src/js/features/comp/WebHookEditor.ts
@@ -23,7 +23,7 @@ export function initCompWebHookEditor() {
}
// some webhooks (like Gitea) allow to set the request method (GET/POST), and it would toggle the "Content Type" field
- const httpMethodInput = document.querySelector('#http_method');
+ const httpMethodInput = document.querySelector<HTMLInputElement>('#http_method');
if (httpMethodInput) {
const updateContentType = function () {
const visible = httpMethodInput.value === 'POST';
diff --git a/web_src/js/features/dropzone.ts b/web_src/js/features/dropzone.ts
index c9b0149df5..666c645230 100644
--- a/web_src/js/features/dropzone.ts
+++ b/web_src/js/features/dropzone.ts
@@ -6,6 +6,7 @@ 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';
const {csrfToken, i18n} = window.config;
@@ -15,14 +16,14 @@ export const DropzoneCustomEventRemovedFile = 'dropzone-custom-removed-file';
export const DropzoneCustomEventUploadDone = 'dropzone-custom-upload-done';
async function createDropzone(el, opts) {
- const [{Dropzone}] = await Promise.all([
+ const [{default: Dropzone}] = await Promise.all([
import(/* webpackChunkName: "dropzone" */'dropzone'),
import(/* webpackChunkName: "dropzone" */'dropzone/dist/dropzone.css'),
]);
return new Dropzone(el, opts);
}
-export function generateMarkdownLinkForAttachment(file, {width, dppx} = {}) {
+export function generateMarkdownLinkForAttachment(file, {width, dppx}: {width?: number, dppx?: number} = {}) {
let fileMarkdown = `[${file.name}](/attachments/${file.uuid})`;
if (isImageFile(file)) {
fileMarkdown = `!${fileMarkdown}`;
@@ -60,14 +61,14 @@ function addCopyLink(file) {
/**
* @param {HTMLElement} dropzoneEl
*/
-export async function initDropzone(dropzoneEl) {
+export async function initDropzone(dropzoneEl: HTMLElement) {
const listAttachmentsUrl = dropzoneEl.closest('[data-attachment-url]')?.getAttribute('data-attachment-url');
const removeAttachmentUrl = dropzoneEl.getAttribute('data-remove-url');
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
- const opts = {
+ const opts: Record<string, any> = {
url: dropzoneEl.getAttribute('data-upload-url'),
headers: {'X-Csrf-Token': csrfToken},
acceptedFiles: ['*/*', ''].includes(dropzoneEl.getAttribute('data-accepts')) ? null : dropzoneEl.getAttribute('data-accepts'),
@@ -88,7 +89,7 @@ export async function initDropzone(dropzoneEl) {
// "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, resp) => {
+ dzInst.on('success', (file: DropzoneFile & {uuid: string}, 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});
@@ -97,7 +98,7 @@ export async function initDropzone(dropzoneEl) {
dzInst.emit(DropzoneCustomEventUploadDone, {file});
});
- dzInst.on('removedfile', async (file) => {
+ dzInst.on('removedfile', async (file: DropzoneFile & {uuid: string}) => {
if (disableRemovedfileEvent) return;
dzInst.emit(DropzoneCustomEventRemovedFile, {fileUuid: file.uuid});
diff --git a/web_src/js/features/emoji.ts b/web_src/js/features/emoji.ts
index 032a3efe8a..933aa951c5 100644
--- a/web_src/js/features/emoji.ts
+++ b/web_src/js/features/emoji.ts
@@ -1,4 +1,4 @@
-import emojis from '../../../assets/emoji.json';
+import emojis from '../../../assets/emoji.json' with {type: 'json'};
const {assetUrlPrefix, customEmojis} = window.config;
diff --git a/web_src/js/features/eventsource.sharedworker.ts b/web_src/js/features/eventsource.sharedworker.ts
index 62581cf687..991c92cc8e 100644
--- a/web_src/js/features/eventsource.sharedworker.ts
+++ b/web_src/js/features/eventsource.sharedworker.ts
@@ -2,6 +2,11 @@ const sourcesByUrl = {};
const sourcesByPort = {};
class Source {
+ url: string;
+ eventSource: EventSource;
+ listening: Record<string, any>;
+ clients: Array<any>;
+
constructor(url) {
this.url = url;
this.eventSource = new EventSource(url);
@@ -67,7 +72,7 @@ class Source {
}
}
-self.addEventListener('connect', (e) => {
+self.addEventListener('connect', (e: Event & {ports: Array<any>}) => {
for (const port of e.ports) {
port.addEventListener('message', (event) => {
if (!self.EventSource) {
diff --git a/web_src/js/features/heatmap.ts b/web_src/js/features/heatmap.ts
index 69cd069a94..53eebc93e5 100644
--- a/web_src/js/features/heatmap.ts
+++ b/web_src/js/features/heatmap.ts
@@ -21,8 +21,8 @@ export function initHeatmap() {
// last heatmap tooltip localization attempt https://github.com/go-gitea/gitea/pull/24131/commits/a83761cbbae3c2e3b4bced71e680f44432073ac8
const locale = {
heatMapLocale: {
- months: new Array(12).fill().map((_, idx) => translateMonth(idx)),
- days: new Array(7).fill().map((_, idx) => translateDay(idx)),
+ months: new Array(12).fill(undefined).map((_, idx) => translateMonth(idx)),
+ days: new Array(7).fill(undefined).map((_, idx) => translateDay(idx)),
on: ' - ', // no correct locale support for it, because in many languages the sentence is not "something on someday"
more: el.getAttribute('data-locale-more'),
less: el.getAttribute('data-locale-less'),
diff --git a/web_src/js/features/install.ts b/web_src/js/features/install.ts
index 3defb7904a..725dcafab0 100644
--- a/web_src/js/features/install.ts
+++ b/web_src/js/features/install.ts
@@ -22,9 +22,9 @@ function initPreInstall() {
mssql: '127.0.0.1:1433',
};
- const dbHost = document.querySelector('#db_host');
- const dbUser = document.querySelector('#db_user');
- const dbName = document.querySelector('#db_name');
+ const dbHost = document.querySelector<HTMLInputElement>('#db_host');
+ const dbUser = document.querySelector<HTMLInputElement>('#db_user');
+ const dbName = document.querySelector<HTMLInputElement>('#db_name');
// Database type change detection.
document.querySelector('#db_type').addEventListener('change', function () {
@@ -48,12 +48,12 @@ function initPreInstall() {
});
document.querySelector('#db_type').dispatchEvent(new Event('change'));
- const appUrl = document.querySelector('#app_url');
+ const appUrl = document.querySelector<HTMLInputElement>('#app_url');
if (appUrl.value.includes('://localhost')) {
appUrl.value = window.location.href;
}
- const domain = document.querySelector('#domain');
+ const domain = document.querySelector<HTMLInputElement>('#domain');
if (domain.value.trim() === 'localhost') {
domain.value = window.location.hostname;
}
@@ -61,43 +61,43 @@ function initPreInstall() {
// TODO: better handling of exclusive relations.
document.querySelector('#offline-mode input').addEventListener('change', function () {
if (this.checked) {
- document.querySelector('#disable-gravatar input').checked = true;
- document.querySelector('#federated-avatar-lookup input').checked = false;
+ document.querySelector<HTMLInputElement>('#disable-gravatar input').checked = true;
+ document.querySelector<HTMLInputElement>('#federated-avatar-lookup input').checked = false;
}
});
document.querySelector('#disable-gravatar input').addEventListener('change', function () {
if (this.checked) {
- document.querySelector('#federated-avatar-lookup input').checked = false;
+ document.querySelector<HTMLInputElement>('#federated-avatar-lookup input').checked = false;
} else {
- document.querySelector('#offline-mode input').checked = false;
+ document.querySelector<HTMLInputElement>('#offline-mode input').checked = false;
}
});
document.querySelector('#federated-avatar-lookup input').addEventListener('change', function () {
if (this.checked) {
- document.querySelector('#disable-gravatar input').checked = false;
- document.querySelector('#offline-mode input').checked = false;
+ document.querySelector<HTMLInputElement>('#disable-gravatar input').checked = false;
+ document.querySelector<HTMLInputElement>('#offline-mode input').checked = false;
}
});
document.querySelector('#enable-openid-signin input').addEventListener('change', function () {
if (this.checked) {
- if (!document.querySelector('#disable-registration input').checked) {
- document.querySelector('#enable-openid-signup input').checked = true;
+ if (!document.querySelector<HTMLInputElement>('#disable-registration input').checked) {
+ document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = true;
}
} else {
- document.querySelector('#enable-openid-signup input').checked = false;
+ document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = false;
}
});
document.querySelector('#disable-registration input').addEventListener('change', function () {
if (this.checked) {
- document.querySelector('#enable-captcha input').checked = false;
- document.querySelector('#enable-openid-signup input').checked = false;
+ document.querySelector<HTMLInputElement>('#enable-captcha input').checked = false;
+ document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = false;
} else {
- document.querySelector('#enable-openid-signup input').checked = true;
+ document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = true;
}
});
document.querySelector('#enable-captcha input').addEventListener('change', function () {
if (this.checked) {
- document.querySelector('#disable-registration input').checked = false;
+ document.querySelector<HTMLInputElement>('#disable-registration input').checked = false;
}
});
}
diff --git a/web_src/js/features/notification.ts b/web_src/js/features/notification.ts
index 539f779056..5cdcd967f0 100644
--- a/web_src/js/features/notification.ts
+++ b/web_src/js/features/notification.ts
@@ -14,25 +14,25 @@ export function initNotificationsTable() {
window.addEventListener('pageshow', (e) => {
if (e.persisted) { // page was restored from bfcache
const table = document.querySelector('#notification_table');
- const unreadCountEl = document.querySelector('.notifications-unread-count');
+ const unreadCountEl = document.querySelector<HTMLElement>('.notifications-unread-count');
let unreadCount = parseInt(unreadCountEl.textContent);
for (const item of table.querySelectorAll('.notifications-item[data-remove="true"]')) {
item.remove();
unreadCount -= 1;
}
- unreadCountEl.textContent = unreadCount;
+ unreadCountEl.textContent = String(unreadCount);
}
});
// mark clicked unread links for deletion on bfcache restore
for (const link of table.querySelectorAll('.notifications-item[data-status="1"] .notifications-link')) {
- link.addEventListener('click', (e) => {
+ link.addEventListener('click', (e : MouseEvent & {target: HTMLElement}) => {
e.target.closest('.notifications-item').setAttribute('data-remove', 'true');
});
}
}
-async function receiveUpdateCount(event) {
+async function receiveUpdateCount(event: MessageEvent) {
try {
const data = JSON.parse(event.data);
@@ -50,7 +50,7 @@ export function initNotificationCount() {
if (!document.querySelector('.notification_count')) return;
let usingPeriodicPoller = false;
- const startPeriodicPoller = (timeout, lastCount) => {
+ const startPeriodicPoller = (timeout: number, lastCount?: number) => {
if (timeout <= 0 || !Number.isFinite(timeout)) return;
usingPeriodicPoller = true;
lastCount = lastCount ?? getCurrentCount();
@@ -72,13 +72,13 @@ export function initNotificationCount() {
type: 'start',
url: `${window.location.origin}${appSubUrl}/user/events`,
});
- worker.port.addEventListener('message', (event) => {
+ worker.port.addEventListener('message', (event: MessageEvent) => {
if (!event.data || !event.data.type) {
console.error('unknown worker message event', event);
return;
}
if (event.data.type === 'notification-count') {
- const _promise = receiveUpdateCount(event.data);
+ receiveUpdateCount(event); // no await
} else if (event.data.type === 'no-event-source') {
// browser doesn't support EventSource, falling back to periodic poller
if (!usingPeriodicPoller) startPeriodicPoller(notificationSettings.MinTimeout);
@@ -118,10 +118,10 @@ export function initNotificationCount() {
}
function getCurrentCount() {
- return document.querySelector('.notification_count').textContent;
+ return Number(document.querySelector('.notification_count').textContent ?? '0');
}
-async function updateNotificationCountWithCallback(callback, timeout, lastCount) {
+async function updateNotificationCountWithCallback(callback: (timeout: number, newCount: number) => void, timeout: number, lastCount: number) {
const currentCount = getCurrentCount();
if (lastCount !== currentCount) {
callback(notificationSettings.MinTimeout, currentCount);
@@ -149,10 +149,9 @@ async function updateNotificationTable() {
if (notificationDiv) {
try {
const params = new URLSearchParams(window.location.search);
- params.set('div-only', true);
- params.set('sequence-number', ++notificationSequenceNumber);
- const url = `${appSubUrl}/notifications?${params.toString()}`;
- const response = await GET(url);
+ params.set('div-only', String(true));
+ params.set('sequence-number', String(++notificationSequenceNumber));
+ const response = await GET(`${appSubUrl}/notifications?${params.toString()}`);
if (!response.ok) {
throw new Error('Failed to fetch notification table');
@@ -169,7 +168,7 @@ async function updateNotificationTable() {
}
}
-async function updateNotificationCount() {
+async function updateNotificationCount(): Promise<number> {
try {
const response = await GET(`${appSubUrl}/notifications/new`);
@@ -185,9 +184,9 @@ async function updateNotificationCount() {
el.textContent = `${data.new}`;
}
- return `${data.new}`;
+ return data.new as number;
} catch (error) {
console.error(error);
- return '0';
+ return 0;
}
}
diff --git a/web_src/js/features/oauth2-settings.ts b/web_src/js/features/oauth2-settings.ts
index 1e62ca0096..a206bc8912 100644
--- a/web_src/js/features/oauth2-settings.ts
+++ b/web_src/js/features/oauth2-settings.ts
@@ -1,5 +1,7 @@
export function initOAuth2SettingsDisableCheckbox() {
- for (const e of document.querySelectorAll('.disable-setting')) e.addEventListener('change', ({target}) => {
- document.querySelector(e.getAttribute('data-target')).classList.toggle('disabled', target.checked);
- });
+ for (const el of document.querySelectorAll('.disable-setting')) {
+ el.addEventListener('change', (e: Event & {target: HTMLInputElement}) => {
+ document.querySelector(e.target.getAttribute('data-target')).classList.toggle('disabled', e.target.checked);
+ });
+ }
}
diff --git a/web_src/js/features/pull-view-file.ts b/web_src/js/features/pull-view-file.ts
index 9a052207d5..36fe4bc4df 100644
--- a/web_src/js/features/pull-view-file.ts
+++ b/web_src/js/features/pull-view-file.ts
@@ -34,7 +34,7 @@ export function countAndUpdateViewedFiles() {
export function initViewedCheckboxListenerFor() {
for (const form of document.querySelectorAll(`${viewedCheckboxSelector}:not([data-has-viewed-checkbox-listener="true"])`)) {
// To prevent double addition of listeners
- form.setAttribute('data-has-viewed-checkbox-listener', true);
+ form.setAttribute('data-has-viewed-checkbox-listener', String(true));
// The checkbox consists of a div containing the real checkbox with its label and the CSRF token,
// hence the actual checkbox first has to be found
@@ -67,7 +67,7 @@ export function initViewedCheckboxListenerFor() {
// Unfortunately, actual forms cause too many problems, hence another approach is needed
const files = {};
files[fileName] = this.checked;
- const data = {files};
+ const data: Record<string, any> = {files};
const headCommitSHA = form.getAttribute('data-headcommit');
if (headCommitSHA) data.headCommitSHA = headCommitSHA;
POST(form.getAttribute('data-link'), {data});
diff --git a/web_src/js/features/repo-editor.ts b/web_src/js/features/repo-editor.ts
index 96b08250fb..32d0b84f4c 100644
--- a/web_src/js/features/repo-editor.ts
+++ b/web_src/js/features/repo-editor.ts
@@ -35,7 +35,7 @@ function initEditPreviewTab(elForm: HTMLFormElement) {
}
export function initRepoEditor() {
- const dropzoneUpload = document.querySelector('.page-content.repository.editor.upload .dropzone');
+ const dropzoneUpload = document.querySelector<HTMLElement>('.page-content.repository.editor.upload .dropzone');
if (dropzoneUpload) initDropzone(dropzoneUpload);
const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area');
diff --git a/web_src/js/features/repo-search.ts b/web_src/js/features/repo-search.ts
index 9cc2dd4223..7f111dce33 100644
--- a/web_src/js/features/repo-search.ts
+++ b/web_src/js/features/repo-search.ts
@@ -5,9 +5,10 @@ export function initRepositorySearch() {
repositorySearchForm.addEventListener('change', (e: Event & {target: HTMLFormElement}) => {
e.preventDefault();
- const formData = new FormData(repositorySearchForm);
- const params = new URLSearchParams(formData);
-
+ const params = new URLSearchParams();
+ for (const [key, value] of new FormData(repositorySearchForm).entries()) {
+ params.set(key, value.toString());
+ }
if (e.target.name === 'clear-filter') {
params.delete('archived');
params.delete('fork');
diff --git a/web_src/js/features/repo-settings-branches.test.ts b/web_src/js/features/repo-settings-branches.test.ts
index c4609999be..32ab54e4c2 100644
--- a/web_src/js/features/repo-settings-branches.test.ts
+++ b/web_src/js/features/repo-settings-branches.test.ts
@@ -2,6 +2,7 @@ import {beforeEach, describe, expect, test, vi} from 'vitest';
import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts';
import {POST} from '../modules/fetch.ts';
import {createSortable} from '../modules/sortable.ts';
+import type {SortableEvent} from 'sortablejs';
vi.mock('../modules/fetch.ts', () => ({
POST: vi.fn(),
@@ -54,8 +55,8 @@ describe('Repository Branch Settings', () => {
vi.mocked(POST).mockResolvedValue({ok: true} as Response);
// Mock createSortable to capture and execute the onEnd callback
- vi.mocked(createSortable).mockImplementation((_el, options) => {
- options.onEnd();
+ vi.mocked(createSortable).mockImplementation(async (_el: Element, options) => {
+ options.onEnd(new Event('SortableEvent') as SortableEvent);
return {destroy: vi.fn()};
});
diff --git a/web_src/js/features/tribute.ts b/web_src/js/features/tribute.ts
index 44588c0064..fa65bcbb28 100644
--- a/web_src/js/features/tribute.ts
+++ b/web_src/js/features/tribute.ts
@@ -51,6 +51,7 @@ function makeCollections({mentions, emoji}) {
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);
return tribute;
diff --git a/web_src/js/globals.d.ts b/web_src/js/globals.d.ts
index 9780a1cf3c..a5ec29a83f 100644
--- a/web_src/js/globals.d.ts
+++ b/web_src/js/globals.d.ts
@@ -8,6 +8,17 @@ declare module '*.css' {
export default value;
}
+declare module '*.vue' {
+ import type {DefineComponent} from 'vue';
+ const component: DefineComponent<unknown, unknown, any>;
+ export default component;
+ // List of named exports from vue components, used to make `tsc` output clean.
+ // To actually lint .vue files, `vue-tsc` is used because `tsc` can not parse them.
+ export function initRepoBranchTagSelector(selector: string): void;
+ export function initDashboardRepoList(): void;
+ export function initRepositoryActionView(): void;
+}
+
declare let __webpack_public_path__: string;
declare module 'htmx.org/dist/htmx.esm.js' {
@@ -16,8 +27,8 @@ declare module 'htmx.org/dist/htmx.esm.js' {
}
declare module 'uint8-to-base64' {
- export function encode(arrayBuffer: ArrayBuffer): string;
- export function decode(base64str: string): ArrayBuffer;
+ export function encode(arrayBuffer: Uint8Array): string;
+ export function decode(base64str: string): Uint8Array;
}
declare module 'swagger-ui-dist/swagger-ui-es-bundle.js' {
diff --git a/web_src/js/modules/tippy.ts b/web_src/js/modules/tippy.ts
index ce0b3cbc39..4e7f1ac093 100644
--- a/web_src/js/modules/tippy.ts
+++ b/web_src/js/modules/tippy.ts
@@ -16,7 +16,6 @@ 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,
diff --git a/web_src/js/utils.ts b/web_src/js/utils.ts
index bd872f094c..997a4d1ff3 100644
--- a/web_src/js/utils.ts
+++ b/web_src/js/utils.ts
@@ -134,16 +134,16 @@ export function toAbsoluteUrl(url: string): string {
return `${window.location.origin}${url}`;
}
-// Encode an ArrayBuffer into a URLEncoded base64 string.
-export function encodeURLEncodedBase64(arrayBuffer: ArrayBuffer): string {
- return encode(arrayBuffer)
+// Encode an Uint8Array into a URLEncoded base64 string.
+export function encodeURLEncodedBase64(uint8Array: Uint8Array): string {
+ return encode(uint8Array)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
-// Decode a URLEncoded base64 to an ArrayBuffer.
-export function decodeURLEncodedBase64(base64url: string): ArrayBuffer {
+// Decode a URLEncoded base64 to an Uint8Array.
+export function decodeURLEncodedBase64(base64url: string): Uint8Array {
return decode(base64url
.replace(/_/g, '/')
.replace(/-/g, '+'));