From d8590e153b9838448debad1455d49a757eb81e5e Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 14 Dec 2024 08:53:34 +0100 Subject: [PATCH 01/15] Fix a number of typescript issuest --- web_src/js/features/common-issue-list.ts | 11 +++-- .../js/features/comp/ComboMarkdownEditor.ts | 4 +- .../js/features/comp/EasyMDEToolbarActions.ts | 44 ++++++++++--------- web_src/js/features/comp/ReactionSelector.ts | 2 +- web_src/js/features/comp/WebHookEditor.ts | 2 +- web_src/js/features/dropzone.ts | 6 +-- web_src/js/features/emoji.ts | 2 +- .../js/features/eventsource.sharedworker.ts | 7 ++- web_src/js/features/heatmap.ts | 4 +- web_src/js/features/install.ts | 36 +++++++-------- web_src/js/features/notification.ts | 28 ++++++------ web_src/js/features/oauth2-settings.ts | 8 ++-- web_src/js/features/pull-view-file.ts | 4 +- web_src/js/features/repo-editor.ts | 2 +- web_src/js/globals.d.ts | 4 +- web_src/js/utils.ts | 10 ++--- 16 files changed, 91 insertions(+), 83 deletions(-) diff --git a/web_src/js/features/common-issue-list.ts b/web_src/js/features/common-issue-list.ts index e8a47eabad65a..e2073647940ab 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('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 80eabaa37aea7..bba50a1296f7c 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 d91dd23d115e5..ec5c7304befb6 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> { + const actions: Record | 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 1e955c7ab4066..671bade3bec5c 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 b13a2ffca3df5..203396af80a5d 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('#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 c9b0149df581c..f5d4efeb895ba 100644 --- a/web_src/js/features/dropzone.ts +++ b/web_src/js/features/dropzone.ts @@ -22,7 +22,7 @@ async function createDropzone(el, opts) { 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 +60,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 = { url: dropzoneEl.getAttribute('data-upload-url'), headers: {'X-Csrf-Token': csrfToken}, acceptedFiles: ['*/*', ''].includes(dropzoneEl.getAttribute('data-accepts')) ? null : dropzoneEl.getAttribute('data-accepts'), diff --git a/web_src/js/features/emoji.ts b/web_src/js/features/emoji.ts index 032a3efe8aa6a..933aa951c548c 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 62581cf687db3..991c92cc8e8db 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; + clients: Array; + 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}) => { 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 69cd069a94f04..53eebc93e55b6 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 3defb7904aa6f..725dcafab0832 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('#db_host'); + const dbUser = document.querySelector('#db_user'); + const dbName = document.querySelector('#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('#app_url'); if (appUrl.value.includes('://localhost')) { appUrl.value = window.location.href; } - const domain = document.querySelector('#domain'); + const domain = document.querySelector('#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('#disable-gravatar input').checked = true; + document.querySelector('#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('#federated-avatar-lookup input').checked = false; } else { - document.querySelector('#offline-mode input').checked = false; + document.querySelector('#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('#disable-gravatar input').checked = false; + document.querySelector('#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('#disable-registration input').checked) { + document.querySelector('#enable-openid-signup input').checked = true; } } else { - document.querySelector('#enable-openid-signup input').checked = false; + document.querySelector('#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('#enable-captcha input').checked = false; + document.querySelector('#enable-openid-signup input').checked = false; } else { - document.querySelector('#enable-openid-signup input').checked = true; + document.querySelector('#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('#disable-registration input').checked = false; } }); } diff --git a/web_src/js/features/notification.ts b/web_src/js/features/notification.ts index 539f779056118..ecafbd8580737 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('.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,8 +149,8 @@ async function updateNotificationTable() { if (notificationDiv) { try { const params = new URLSearchParams(window.location.search); - params.set('div-only', true); - params.set('sequence-number', ++notificationSequenceNumber); + params.set('div-only', String(true)); + params.set('sequence-number', String(++notificationSequenceNumber)); const url = `${appSubUrl}/notifications?${params.toString()}`; const response = await GET(url); @@ -169,7 +169,7 @@ async function updateNotificationTable() { } } -async function updateNotificationCount() { +async function updateNotificationCount(): Promise { try { const response = await GET(`${appSubUrl}/notifications/new`); @@ -185,9 +185,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 1e62ca0096481..c89b7c492458d 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 e of document.querySelectorAll('.disable-setting')) { + e.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 9a052207d5099..36fe4bc4dfb74 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 = {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 96b08250fb1c7..32d0b84f4c9bb 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('.page-content.repository.editor.upload .dropzone'); if (dropzoneUpload) initDropzone(dropzoneUpload); const editArea = document.querySelector('.page-content.repository.editor textarea#edit_area'); diff --git a/web_src/js/globals.d.ts b/web_src/js/globals.d.ts index 9780a1cf3cf3f..e4ffef5db3717 100644 --- a/web_src/js/globals.d.ts +++ b/web_src/js/globals.d.ts @@ -16,8 +16,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: 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/utils.ts b/web_src/js/utils.ts index bd872f094ca97..997a4d1ff3f90 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, '+')); From c4a0b6543504e2682c1c51b6f5a89d5db9003634 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 14 Dec 2024 08:58:09 +0100 Subject: [PATCH 02/15] fix --- web_src/js/globals.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/globals.d.ts b/web_src/js/globals.d.ts index e4ffef5db3717..65954dde71733 100644 --- a/web_src/js/globals.d.ts +++ b/web_src/js/globals.d.ts @@ -16,7 +16,7 @@ declare module 'htmx.org/dist/htmx.esm.js' { } declare module 'uint8-to-base64' { - export function encode(arrayBuffer: ArrayBuffer | Uint8Array): string; + export function encode(arrayBuffer: Uint8Array): string; export function decode(base64str: string): Uint8Array; } From ed73924e37ea03171e6bdb9251c117a52091929a Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 14 Dec 2024 09:17:28 +0100 Subject: [PATCH 03/15] remove var --- web_src/js/features/notification.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web_src/js/features/notification.ts b/web_src/js/features/notification.ts index ecafbd8580737..5cdcd967f06e4 100644 --- a/web_src/js/features/notification.ts +++ b/web_src/js/features/notification.ts @@ -151,8 +151,7 @@ async function updateNotificationTable() { const params = new URLSearchParams(window.location.search); params.set('div-only', String(true)); params.set('sequence-number', String(++notificationSequenceNumber)); - const url = `${appSubUrl}/notifications?${params.toString()}`; - const response = await GET(url); + const response = await GET(`${appSubUrl}/notifications?${params.toString()}`); if (!response.ok) { throw new Error('Failed to fetch notification table'); From 3b7f4ed8e1302e89dd06fd1e510445dc036720bd Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 14 Dec 2024 09:27:17 +0100 Subject: [PATCH 04/15] rename var --- web_src/js/features/oauth2-settings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_src/js/features/oauth2-settings.ts b/web_src/js/features/oauth2-settings.ts index c89b7c492458d..a206bc8912f5e 100644 --- a/web_src/js/features/oauth2-settings.ts +++ b/web_src/js/features/oauth2-settings.ts @@ -1,6 +1,6 @@ export function initOAuth2SettingsDisableCheckbox() { - for (const e of document.querySelectorAll('.disable-setting')) { - e.addEventListener('change', (e: Event & {target: HTMLInputElement}) => { + 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); }); } From 5c2780c0e521340fe7c7b3120f25abce5e396f6f Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 14 Dec 2024 19:09:45 +0800 Subject: [PATCH 05/15] fix vue import errors --- web_src/js/vue-sfc.d.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 web_src/js/vue-sfc.d.ts diff --git a/web_src/js/vue-sfc.d.ts b/web_src/js/vue-sfc.d.ts new file mode 100644 index 0000000000000..4a820a2b4512d --- /dev/null +++ b/web_src/js/vue-sfc.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import type {DefineComponent} from 'vue'; + const component: DefineComponent; + export default component; +} From 80192fa391616d3ff0194faba41e998e7d732168 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 14 Dec 2024 19:15:47 +0800 Subject: [PATCH 06/15] fix dropzone --- web_src/js/features/dropzone.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web_src/js/features/dropzone.ts b/web_src/js/features/dropzone.ts index f5d4efeb895ba..666c6452304cf 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,7 +16,7 @@ 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'), ]); @@ -88,7 +89,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 '' 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: HTMLElement) { 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}); From ec99bdc8eacedcf917dd67d1f1ea6e44e0dabeb0 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 14 Dec 2024 19:28:50 +0800 Subject: [PATCH 07/15] fix all tsc error --- web_src/js/features/repo-legacy.ts | 1 + web_src/js/features/repo-search.ts | 2 +- web_src/js/features/repo-settings-branches.test.ts | 5 +++-- web_src/js/features/tribute.ts | 1 + web_src/js/index.ts | 2 ++ 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/web_src/js/features/repo-legacy.ts b/web_src/js/features/repo-legacy.ts index eaa9e3742a869..371b070bfb731 100644 --- a/web_src/js/features/repo-legacy.ts +++ b/web_src/js/features/repo-legacy.ts @@ -7,6 +7,7 @@ import { initRepoPullRequestUpdate, } from './repo-issue.ts'; import {initUnicodeEscapeButton} from './repo-unicode-escape.ts'; +// @ts-expect-error import functions from Vue SFC import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue'; import {initRepoCloneButtons} from './repo-common.ts'; import {initCitationFileCopyContent} from './citation.ts'; diff --git a/web_src/js/features/repo-search.ts b/web_src/js/features/repo-search.ts index 9cc2dd42239f4..f56dc39ee0456 100644 --- a/web_src/js/features/repo-search.ts +++ b/web_src/js/features/repo-search.ts @@ -6,7 +6,7 @@ export function initRepositorySearch() { e.preventDefault(); const formData = new FormData(repositorySearchForm); - const params = new URLSearchParams(formData); + const params = new URLSearchParams(formData.toString()); if (e.target.name === 'clear-filter') { params.delete('archived'); diff --git a/web_src/js/features/repo-settings-branches.test.ts b/web_src/js/features/repo-settings-branches.test.ts index c4609999bef8c..32ab54e4c2c02 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 44588c0064698..fa65bcbb280bb 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/index.ts b/web_src/js/index.ts index 51d8c96fbdfbf..9f3621534937f 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -2,6 +2,7 @@ import './bootstrap.ts'; import './htmx.ts'; +// @ts-expect-error import functions from Vue SFC import {initDashboardRepoList} from './components/DashboardRepoList.vue'; import {initGlobalCopyToClipboardListener} from './features/clipboard.ts'; @@ -54,6 +55,7 @@ import {initRepoWikiForm} from './features/repo-wiki.ts'; import {initRepository, initBranchSelectorTabs} from './features/repo-legacy.ts'; import {initCopyContent} from './features/copycontent.ts'; import {initCaptcha} from './features/captcha.ts'; +// @ts-expect-error import functions from Vue SFC import {initRepositoryActionView} from './components/RepoActionView.vue'; import {initGlobalTooltips} from './modules/tippy.ts'; import {initGiteaFomantic} from './modules/fomantic.ts'; From aab1edbbbe17b1c65b40b6980f5bcda7dc841c97 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 14 Dec 2024 19:47:10 +0800 Subject: [PATCH 08/15] fix repo search from --- web_src/js/features/repo-search.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web_src/js/features/repo-search.ts b/web_src/js/features/repo-search.ts index f56dc39ee0456..7f111dce33f8c 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.toString()); - + 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'); From 907d11ded78c5017b3a79a7469c85eaafd6c364e Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 14 Dec 2024 20:31:52 +0100 Subject: [PATCH 09/15] remove vue-tsc, enable tsc in `make lint-js` --- Makefile | 8 +-- package-lock.json | 140 +--------------------------------------------- package.json | 3 +- 3 files changed, 4 insertions(+), 147 deletions(-) diff --git a/Makefile b/Makefile index 0cd6936b3bb27..6859fe4cbd68f 100644 --- a/Makefile +++ b/Makefile @@ -377,12 +377,12 @@ lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig .PHONY: lint-js lint-js: node_modules npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) -# npx vue-tsc + npx tsc .PHONY: lint-js-fix lint-js-fix: node_modules npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix -# npx vue-tsc + npx tsc .PHONY: lint-css lint-css: node_modules @@ -451,10 +451,6 @@ lint-templates: .venv node_modules lint-yaml: .venv @poetry run yamllint . -.PHONY: tsc -tsc: - npx vue-tsc - .PHONY: watch watch: @bash tools/watch.sh diff --git a/package-lock.json b/package-lock.json index 4764282f65e88..cd710fb9570ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,8 +111,7 @@ "type-fest": "4.30.0", "updates": "16.4.0", "vite-string-plugin": "1.3.4", - "vitest": "2.1.8", - "vue-tsc": "2.1.10" + "vitest": "2.1.8" }, "engines": { "node": ">= 18.0.0" @@ -5334,35 +5333,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@volar/language-core": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.10.tgz", - "integrity": "sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/source-map": "2.4.10" - } - }, - "node_modules/@volar/source-map": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.10.tgz", - "integrity": "sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@volar/typescript": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.10.tgz", - "integrity": "sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.10", - "path-browserify": "^1.0.1", - "vscode-uri": "^3.0.8" - } - }, "node_modules/@vue/compiler-core": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", @@ -5422,58 +5392,6 @@ "@vue/shared": "3.5.13" } }, - "node_modules/@vue/compiler-vue2": { - "version": "2.7.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", - "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", - "dev": true, - "license": "MIT", - "dependencies": { - "de-indent": "^1.0.2", - "he": "^1.2.0" - } - }, - "node_modules/@vue/language-core": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.1.10.tgz", - "integrity": "sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "~2.4.8", - "@vue/compiler-dom": "^3.5.0", - "@vue/compiler-vue2": "^2.7.16", - "@vue/shared": "^3.5.0", - "alien-signals": "^0.2.0", - "minimatch": "^9.0.3", - "muggle-string": "^0.4.1", - "path-browserify": "^1.0.1" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@vue/language-core/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@vue/reactivity": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", @@ -5846,13 +5764,6 @@ "ajv": "^8.8.2" } }, - "node_modules/alien-signals": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.2.2.tgz", - "integrity": "sha512-cZIRkbERILsBOXTQmMrxc9hgpxglstn69zm+F1ARf4aPAzdAFYd6sBq87ErO0Fj3DV94tglcyHG5kQz9nDC/8A==", - "dev": true, - "license": "MIT" - }, "node_modules/ansi_up": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/ansi_up/-/ansi_up-6.0.2.tgz", @@ -7523,13 +7434,6 @@ "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", "license": "MIT" }, - "node_modules/de-indent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", - "dev": true, - "license": "MIT" - }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -10496,16 +10400,6 @@ "node": ">=12.4.0" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -11971,13 +11865,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "dev": true, - "license": "MIT" - }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -12353,13 +12240,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true, - "license": "MIT" - }, "node_modules/path-data-parser": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", @@ -15780,24 +15660,6 @@ } } }, - "node_modules/vue-tsc": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.1.10.tgz", - "integrity": "sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/typescript": "~2.4.8", - "@vue/language-core": "2.1.10", - "semver": "^7.5.4" - }, - "bin": { - "vue-tsc": "bin/vue-tsc.js" - }, - "peerDependencies": { - "typescript": ">=5.0.0" - } - }, "node_modules/watchpack": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", diff --git a/package.json b/package.json index 275ca898e29e6..03bcb39815953 100644 --- a/package.json +++ b/package.json @@ -110,8 +110,7 @@ "type-fest": "4.30.0", "updates": "16.4.0", "vite-string-plugin": "1.3.4", - "vitest": "2.1.8", - "vue-tsc": "2.1.10" + "vitest": "2.1.8" }, "browserslist": [ "defaults" From 2b89d11670aff70e8cb37417ed036e67fa6e5316 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sat, 14 Dec 2024 20:33:32 +0100 Subject: [PATCH 10/15] move module declaration to globals.d.ts --- web_src/js/globals.d.ts | 6 ++++++ web_src/js/vue-sfc.d.ts | 5 ----- 2 files changed, 6 insertions(+), 5 deletions(-) delete mode 100644 web_src/js/vue-sfc.d.ts diff --git a/web_src/js/globals.d.ts b/web_src/js/globals.d.ts index 65954dde71733..a5920d20ebe2a 100644 --- a/web_src/js/globals.d.ts +++ b/web_src/js/globals.d.ts @@ -8,6 +8,12 @@ declare module '*.css' { export default value; } +declare module '*.vue' { + import type {DefineComponent} from 'vue'; + const component: DefineComponent; + export default component; +} + declare let __webpack_public_path__: string; declare module 'htmx.org/dist/htmx.esm.js' { diff --git a/web_src/js/vue-sfc.d.ts b/web_src/js/vue-sfc.d.ts deleted file mode 100644 index 4a820a2b4512d..0000000000000 --- a/web_src/js/vue-sfc.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module '*.vue' { - import type {DefineComponent} from 'vue'; - const component: DefineComponent; - export default component; -} From e9439c902c2f74b735b695050869c8b5aae76285 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sun, 15 Dec 2024 02:30:51 +0100 Subject: [PATCH 11/15] use vue-tsc again --- Makefile | 4 +- package-lock.json | 138 +++++++++++++++++++++++++++++ package.json | 1 + vitest.config.ts | 1 + web_src/js/features/repo-legacy.ts | 1 - web_src/js/index.ts | 2 - 6 files changed, 142 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 6859fe4cbd68f..4591825d48ca6 100644 --- a/Makefile +++ b/Makefile @@ -377,12 +377,12 @@ lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig .PHONY: lint-js lint-js: node_modules npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) - npx tsc + npx vue-tsc .PHONY: lint-js-fix lint-js-fix: node_modules npx eslint --color --max-warnings=0 --ext js,ts,vue $(ESLINT_FILES) --fix - npx tsc + npx vue-tsc .PHONY: lint-css lint-css: node_modules diff --git a/package-lock.json b/package-lock.json index cd710fb9570ac..8755cfe06f702 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,6 +67,7 @@ "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "4.4.1", "@playwright/test": "1.49.0", + "@silverwind/vue-tsc": "2.1.13", "@stoplight/spectral-cli": "6.14.2", "@stylistic/eslint-plugin-js": "2.11.0", "@stylistic/stylelint-plugin": "3.1.1", @@ -3832,6 +3833,24 @@ "hasInstallScript": true, "license": "Apache-2.0" }, + "node_modules/@silverwind/vue-tsc": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@silverwind/vue-tsc/-/vue-tsc-2.1.13.tgz", + "integrity": "sha512-ejFxz1KZiUGAESbC+eURnjqt0N95qkU9eZU7W15wgF9zV+v2FEu3ZLduuXTC7D/Sg6lL1R/QjPfUbxbAbBQOsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "~2.4.11", + "@vue/language-core": "2.1.10", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, "node_modules/@silverwind/vue3-calendar-heatmap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@silverwind/vue3-calendar-heatmap/-/vue3-calendar-heatmap-2.0.6.tgz", @@ -5333,6 +5352,35 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@volar/language-core": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.11.tgz", + "integrity": "sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.11" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.11.tgz", + "integrity": "sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.11.tgz", + "integrity": "sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.11", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, "node_modules/@vue/compiler-core": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", @@ -5392,6 +5440,58 @@ "@vue/shared": "3.5.13" } }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/language-core": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.1.10.tgz", + "integrity": "sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~2.4.8", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^0.2.0", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@vue/reactivity": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", @@ -5764,6 +5864,13 @@ "ajv": "^8.8.2" } }, + "node_modules/alien-signals": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.2.2.tgz", + "integrity": "sha512-cZIRkbERILsBOXTQmMrxc9hgpxglstn69zm+F1ARf4aPAzdAFYd6sBq87ErO0Fj3DV94tglcyHG5kQz9nDC/8A==", + "dev": true, + "license": "MIT" + }, "node_modules/ansi_up": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/ansi_up/-/ansi_up-6.0.2.tgz", @@ -7434,6 +7541,13 @@ "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", "license": "MIT" }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -10400,6 +10514,16 @@ "node": ">=12.4.0" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -11865,6 +11989,13 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -12240,6 +12371,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, "node_modules/path-data-parser": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", diff --git a/package.json b/package.json index 03bcb39815953..61e65c1f43eee 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "4.4.1", "@playwright/test": "1.49.0", + "@silverwind/vue-tsc": "2.1.13", "@stoplight/spectral-cli": "6.14.2", "@stylistic/eslint-plugin-js": "2.11.0", "@stylistic/stylelint-plugin": "3.1.1", diff --git a/vitest.config.ts b/vitest.config.ts index da61450764e4f..7dde6c384c16c 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -16,6 +16,7 @@ export default defineConfig({ }, plugins: [ stringPlugin(), + // @ts-expect-error TS2351: This expression is not callable, only happens with vue-tsc vuePlugin(), ], }); diff --git a/web_src/js/features/repo-legacy.ts b/web_src/js/features/repo-legacy.ts index 371b070bfb731..eaa9e3742a869 100644 --- a/web_src/js/features/repo-legacy.ts +++ b/web_src/js/features/repo-legacy.ts @@ -7,7 +7,6 @@ import { initRepoPullRequestUpdate, } from './repo-issue.ts'; import {initUnicodeEscapeButton} from './repo-unicode-escape.ts'; -// @ts-expect-error import functions from Vue SFC import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue'; import {initRepoCloneButtons} from './repo-common.ts'; import {initCitationFileCopyContent} from './citation.ts'; diff --git a/web_src/js/index.ts b/web_src/js/index.ts index 9f3621534937f..51d8c96fbdfbf 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -2,7 +2,6 @@ import './bootstrap.ts'; import './htmx.ts'; -// @ts-expect-error import functions from Vue SFC import {initDashboardRepoList} from './components/DashboardRepoList.vue'; import {initGlobalCopyToClipboardListener} from './features/clipboard.ts'; @@ -55,7 +54,6 @@ import {initRepoWikiForm} from './features/repo-wiki.ts'; import {initRepository, initBranchSelectorTabs} from './features/repo-legacy.ts'; import {initCopyContent} from './features/copycontent.ts'; import {initCaptcha} from './features/captcha.ts'; -// @ts-expect-error import functions from Vue SFC import {initRepositoryActionView} from './components/RepoActionView.vue'; import {initGlobalTooltips} from './modules/tippy.ts'; import {initGiteaFomantic} from './modules/fomantic.ts'; From 2858799e02287c3db2d1e6b6c463c862a091c6e3 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sun, 15 Dec 2024 03:23:00 +0100 Subject: [PATCH 12/15] use esnext/bundler --- tsconfig.json | 3 ++- vitest.config.ts | 3 +-- web_src/js/features/captcha.ts | 2 +- web_src/js/modules/tippy.ts | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index e006535c02ca2..7d0316db29902 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,8 @@ ], "compilerOptions": { "target": "es2020", - "module": "nodenext", + "module": "esnext", + "moduleResolution": "bundler", "lib": ["dom", "dom.iterable", "dom.asynciterable", "esnext"], "allowImportingTsExtensions": true, "allowJs": true, diff --git a/vitest.config.ts b/vitest.config.ts index 7dde6c384c16c..f486443518c72 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,5 +1,5 @@ import {defineConfig} from 'vitest/config'; -import vuePlugin from '@vitejs/plugin-vue'; +import {default as vuePlugin} from '@vitejs/plugin-vue'; import {stringPlugin} from 'vite-string-plugin'; export default defineConfig({ @@ -16,7 +16,6 @@ export default defineConfig({ }, plugins: [ stringPlugin(), - // @ts-expect-error TS2351: This expression is not callable, only happens with vue-tsc vuePlugin(), ], }); diff --git a/web_src/js/features/captcha.ts b/web_src/js/features/captcha.ts index 69b4aa6852fca..026e7f78e5b40 100644 --- a/web_src/js/features/captcha.ts +++ b/web_src/js/features/captcha.ts @@ -1,4 +1,4 @@ -import {isDarkTheme} from '../utils.ts'; +import {isDarkTheme} from '../utils'; export async function initCaptcha() { const captchaEl = document.querySelector('#captcha'); diff --git a/web_src/js/modules/tippy.ts b/web_src/js/modules/tippy.ts index ce0b3cbc39809..4e7f1ac093374 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, From 2ac1c7776103508f99453a7d1d34d3aee3cad90b Mon Sep 17 00:00:00 2001 From: silverwind Date: Sun, 15 Dec 2024 03:23:53 +0100 Subject: [PATCH 13/15] tweak import --- vitest.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vitest.config.ts b/vitest.config.ts index f486443518c72..da61450764e4f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,5 +1,5 @@ import {defineConfig} from 'vitest/config'; -import {default as vuePlugin} from '@vitejs/plugin-vue'; +import vuePlugin from '@vitejs/plugin-vue'; import {stringPlugin} from 'vite-string-plugin'; export default defineConfig({ From 1d9a9c93f82650ffa1192308d0c43ffd77fb89a1 Mon Sep 17 00:00:00 2001 From: silverwind Date: Sun, 15 Dec 2024 03:24:14 +0100 Subject: [PATCH 14/15] revert --- web_src/js/features/captcha.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/features/captcha.ts b/web_src/js/features/captcha.ts index 026e7f78e5b40..69b4aa6852fca 100644 --- a/web_src/js/features/captcha.ts +++ b/web_src/js/features/captcha.ts @@ -1,4 +1,4 @@ -import {isDarkTheme} from '../utils'; +import {isDarkTheme} from '../utils.ts'; export async function initCaptcha() { const captchaEl = document.querySelector('#captcha'); From 62e120edbd83640785da0e813a1826e77130285f Mon Sep 17 00:00:00 2001 From: silverwind Date: Sun, 15 Dec 2024 08:57:44 +0100 Subject: [PATCH 15/15] add declarations for named exports from SFCs --- web_src/js/globals.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web_src/js/globals.d.ts b/web_src/js/globals.d.ts index a5920d20ebe2a..a5ec29a83f666 100644 --- a/web_src/js/globals.d.ts +++ b/web_src/js/globals.d.ts @@ -12,6 +12,11 @@ declare module '*.vue' { import type {DefineComponent} from 'vue'; const component: DefineComponent; 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;