diff options
author | silverwind <me@silverwind.io> | 2025-01-15 21:26:17 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-01-16 04:26:17 +0800 |
commit | 4b21a6c792e1bc42ce0f824c7dd7d1426aad2d3b (patch) | |
tree | be908079c27b5188d09b764c65e0b01e76fff7d2 /web_src | |
parent | b15d01b0cec32f34fafc41eaa97887305b1376f7 (diff) | |
download | gitea-4b21a6c792e1bc42ce0f824c7dd7d1426aad2d3b.tar.gz gitea-4b21a6c792e1bc42ce0f824c7dd7d1426aad2d3b.zip |
Enable Typescript `noImplicitThis` (#33250)
- Enable https://www.typescriptlang.org/tsconfig/#noImplicitThis
- Wrap Vue Template-Syntax SFCs in
[`defineComponent`](https://vuejs.org/api/general#definecomponent) which
makes type inference and linter work better
- Move `createApp` calls outside the SFCs into separate files
- Use [`PropType`](https://vuejs.org/api/utility-types#proptype-t) where
appropriate
- Some top-level component properties changed order as dictated by the
linter
- Fix all tsc and lint issues that popped up during these refactors
Diffstat (limited to 'web_src')
28 files changed, 208 insertions, 190 deletions
diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue index 41793d60ed..40ecbba5e3 100644 --- a/web_src/js/components/DashboardRepoList.vue +++ b/web_src/js/components/DashboardRepoList.vue @@ -1,5 +1,5 @@ <script lang="ts"> -import {createApp, nextTick} from 'vue'; +import {nextTick, defineComponent} from 'vue'; import {SvgIcon} from '../svg.ts'; import {GET} from '../modules/fetch.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; @@ -24,7 +24,7 @@ const commitStatus: CommitStatusMap = { warning: {name: 'gitea-exclamation', color: 'yellow'}, }; -const sfc = { +export default defineComponent({ components: {SvgIcon}, data() { const params = new URLSearchParams(window.location.search); @@ -335,16 +335,8 @@ const sfc = { } }, }, -}; - -export function initDashboardRepoList() { - const el = document.querySelector('#dashboard-repo-list'); - if (el) { - createApp(sfc).mount(el); - } -} +}); -export default sfc; // activate the IDE's Vue plugin </script> <template> <div> diff --git a/web_src/js/components/DiffCommitSelector.vue b/web_src/js/components/DiffCommitSelector.vue index 3a394955ca..840acd4b51 100644 --- a/web_src/js/components/DiffCommitSelector.vue +++ b/web_src/js/components/DiffCommitSelector.vue @@ -1,9 +1,10 @@ <script lang="ts"> +import {defineComponent} from 'vue'; import {SvgIcon} from '../svg.ts'; import {GET} from '../modules/fetch.ts'; import {generateAriaId} from '../modules/fomantic/base.ts'; -export default { +export default defineComponent({ components: {SvgIcon}, data: () => { const el = document.querySelector('#diff-commit-select'); @@ -55,11 +56,11 @@ export default { switch (event.key) { case 'ArrowDown': // select next element event.preventDefault(); - this.focusElem(item.nextElementSibling, item); + this.focusElem(item.nextElementSibling as HTMLElement, item); break; case 'ArrowUp': // select previous element event.preventDefault(); - this.focusElem(item.previousElementSibling, item); + this.focusElem(item.previousElementSibling as HTMLElement, item); break; case 'Escape': // close menu event.preventDefault(); @@ -118,9 +119,9 @@ export default { // set correct tabindex to allow easier navigation this.$nextTick(() => { if (this.menuVisible) { - this.focusElem(this.$refs.showAllChanges, this.$refs.expandBtn); + this.focusElem(this.$refs.showAllChanges as HTMLElement, this.$refs.expandBtn as HTMLElement); } else { - this.focusElem(this.$refs.expandBtn, this.$refs.showAllChanges); + this.focusElem(this.$refs.expandBtn as HTMLElement, this.$refs.showAllChanges as HTMLElement); } }); }, @@ -188,7 +189,7 @@ export default { } }, }, -}; +}); </script> <template> <div class="ui scrolling dropdown custom diff-commit-selector"> diff --git a/web_src/js/components/DiffFileList.vue b/web_src/js/components/DiffFileList.vue index 2888c53d2e..792a1aefac 100644 --- a/web_src/js/components/DiffFileList.vue +++ b/web_src/js/components/DiffFileList.vue @@ -17,7 +17,7 @@ function toggleFileList() { store.fileListIsVisible = !store.fileListIsVisible; } -function diffTypeToString(pType) { +function diffTypeToString(pType: number) { const diffTypes = { 1: 'add', 2: 'modify', @@ -28,7 +28,7 @@ function diffTypeToString(pType) { return diffTypes[pType]; } -function diffStatsWidth(adds, dels) { +function diffStatsWidth(adds: number, dels: number) { return `${adds / (adds + dels) * 100}%`; } diff --git a/web_src/js/components/DiffFileTree.vue b/web_src/js/components/DiffFileTree.vue index 9eabc65ae9..8676c4d37f 100644 --- a/web_src/js/components/DiffFileTree.vue +++ b/web_src/js/components/DiffFileTree.vue @@ -60,7 +60,7 @@ const fileTree = computed(() => { parent = newParent; } } - const mergeChildIfOnlyOneDir = (entries) => { + const mergeChildIfOnlyOneDir = (entries: Array<Record<string, any>>) => { for (const entry of entries) { if (entry.children) { mergeChildIfOnlyOneDir(entry.children); @@ -110,13 +110,13 @@ function toggleVisibility() { updateVisibility(!store.fileTreeIsVisible); } -function updateVisibility(visible) { +function updateVisibility(visible: boolean) { store.fileTreeIsVisible = visible; localStorage.setItem(LOCAL_STORAGE_KEY, store.fileTreeIsVisible); updateState(store.fileTreeIsVisible); } -function updateState(visible) { +function updateState(visible: boolean) { const btn = document.querySelector('.diff-toggle-file-tree-button'); const [toShow, toHide] = btn.querySelectorAll('.icon'); const tree = document.querySelector('#diff-file-tree'); diff --git a/web_src/js/components/DiffFileTreeItem.vue b/web_src/js/components/DiffFileTreeItem.vue index 31ce94aacd..9a21a8ac10 100644 --- a/web_src/js/components/DiffFileTreeItem.vue +++ b/web_src/js/components/DiffFileTreeItem.vue @@ -25,7 +25,7 @@ defineProps<{ const store = diffTreeStore(); const collapsed = ref(false); -function getIconForDiffType(pType) { +function getIconForDiffType(pType: number) { const diffTypes = { 1: {name: 'octicon-diff-added', classes: ['text', 'green']}, 2: {name: 'octicon-diff-modified', classes: ['text', 'yellow']}, @@ -36,7 +36,7 @@ function getIconForDiffType(pType) { return diffTypes[pType]; } -function fileIcon(file) { +function fileIcon(file: File) { if (file.IsSubmodule) { return 'octicon-file-submodule'; } diff --git a/web_src/js/components/PullRequestMergeForm.vue b/web_src/js/components/PullRequestMergeForm.vue index 3be7b802a3..1393a7f258 100644 --- a/web_src/js/components/PullRequestMergeForm.vue +++ b/web_src/js/components/PullRequestMergeForm.vue @@ -68,7 +68,7 @@ function toggleActionForm(show: boolean) { mergeMessageFieldValue.value = mergeStyleDetail.value.mergeMessageFieldText; } -function switchMergeStyle(name, autoMerge = false) { +function switchMergeStyle(name: string, autoMerge = false) { mergeStyle.value = name; autoMergeWhenSucceed.value = autoMerge; } diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 914c9e76de..79b43a3746 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -1,7 +1,7 @@ <script lang="ts"> import {SvgIcon} from '../svg.ts'; import ActionRunStatus from './ActionRunStatus.vue'; -import {createApp} from 'vue'; +import {defineComponent, type PropType} from 'vue'; import {createElementFromAttrs, toggleElem} from '../utils/dom.ts'; import {formatDatetime} from '../utils/time.ts'; import {renderAnsi} from '../render/ansi.ts'; @@ -38,7 +38,7 @@ function parseLineCommand(line: LogLine): LogLineCommand | null { return null; } -function isLogElementInViewport(el: HTMLElement): boolean { +function isLogElementInViewport(el: Element): boolean { const rect = el.getBoundingClientRect(); return rect.top >= 0 && rect.bottom <= window.innerHeight; // only check height but not width } @@ -57,25 +57,28 @@ function getLocaleStorageOptions(): LocaleStorageOptions { return {autoScroll: true, expandRunning: false}; } -const sfc = { +export default defineComponent({ name: 'RepoActionView', components: { SvgIcon, ActionRunStatus, }, props: { - runIndex: String, - jobIndex: String, - actionsURL: String, - locale: Object, - }, - - watch: { - optionAlwaysAutoScroll() { - this.saveLocaleStorageOptions(); + runIndex: { + type: String, + default: '', }, - optionAlwaysExpandRunning() { - this.saveLocaleStorageOptions(); + jobIndex: { + type: String, + default: '', + }, + actionsURL: { + type: String, + default: '', + }, + locale: { + type: Object as PropType<Record<string, string>>, + default: null, }, }, @@ -102,10 +105,11 @@ const sfc = { link: '', title: '', titleHTML: '', - status: '', + status: 'unknown' as RunStatus, canCancel: false, canApprove: false, canRerun: false, + canDeleteArtifact: false, done: false, workflowID: '', workflowLink: '', @@ -131,6 +135,7 @@ const sfc = { branch: { name: '', link: '', + isDeleted: false, }, }, }, @@ -148,7 +153,16 @@ const sfc = { }; }, - async mounted() { + watch: { + optionAlwaysAutoScroll() { + this.saveLocaleStorageOptions(); + }, + optionAlwaysExpandRunning() { + this.saveLocaleStorageOptions(); + }, + }, + + async mounted() { // eslint-disable-line @typescript-eslint/no-misused-promises // load job data and then auto-reload periodically // need to await first loadJob so this.currentJobStepsStates is initialized and can be used in hashChangeListener await this.loadJob(); @@ -186,6 +200,7 @@ const sfc = { // get the active logs container element, either the `job-step-logs` or the `job-log-list` in the `job-log-group` getActiveLogsContainer(stepIndex: number): HTMLElement { const el = this.getJobStepLogsContainer(stepIndex); + // @ts-expect-error - _stepLogsActiveContainer is a custom property return el._stepLogsActiveContainer ?? el; }, // begin a log group @@ -263,7 +278,7 @@ const sfc = { const el = this.getJobStepLogsContainer(stepIndex); // if the logs container is empty, then auto-scroll if the step is expanded if (!el.lastChild) return this.currentJobStepsStates[stepIndex].expanded; - return isLogElementInViewport(el.lastChild); + return isLogElementInViewport(el.lastChild as Element); }, appendLogs(stepIndex: number, startTime: number, logLines: LogLine[]) { @@ -380,7 +395,7 @@ const sfc = { toggleTimeDisplay(type: string) { this.timeVisible[`log-time-${type}`] = !this.timeVisible[`log-time-${type}`]; - for (const el of this.$refs.steps.querySelectorAll(`.log-time-${type}`)) { + for (const el of (this.$refs.steps as HTMLElement).querySelectorAll(`.log-time-${type}`)) { toggleElem(el, this.timeVisible[`log-time-${type}`]); } }, @@ -414,59 +429,12 @@ const sfc = { // so logline can be selected by querySelector await this.loadJob(); } - const logLine = this.$refs.steps.querySelector(selectedLogStep); + const logLine = (this.$refs.steps as HTMLElement).querySelector(selectedLogStep); if (!logLine) return; - logLine.querySelector('.line-num').click(); + logLine.querySelector<HTMLAnchorElement>('.line-num').click(); }, }, -}; - -export default sfc; - -export function initRepositoryActionView() { - const el = document.querySelector('#repo-action-view'); - if (!el) return; - - // TODO: the parent element's full height doesn't work well now, - // but we can not pollute the global style at the moment, only fix the height problem for pages with this component - const parentFullHeight = document.querySelector<HTMLElement>('body > div.full.height'); - if (parentFullHeight) parentFullHeight.style.paddingBottom = '0'; - - const view = createApp(sfc, { - runIndex: el.getAttribute('data-run-index'), - jobIndex: el.getAttribute('data-job-index'), - actionsURL: el.getAttribute('data-actions-url'), - locale: { - approve: el.getAttribute('data-locale-approve'), - cancel: el.getAttribute('data-locale-cancel'), - rerun: el.getAttribute('data-locale-rerun'), - rerun_all: el.getAttribute('data-locale-rerun-all'), - scheduled: el.getAttribute('data-locale-runs-scheduled'), - commit: el.getAttribute('data-locale-runs-commit'), - pushedBy: el.getAttribute('data-locale-runs-pushed-by'), - artifactsTitle: el.getAttribute('data-locale-artifacts-title'), - areYouSure: el.getAttribute('data-locale-are-you-sure'), - confirmDeleteArtifact: el.getAttribute('data-locale-confirm-delete-artifact'), - showTimeStamps: el.getAttribute('data-locale-show-timestamps'), - showLogSeconds: el.getAttribute('data-locale-show-log-seconds'), - showFullScreen: el.getAttribute('data-locale-show-full-screen'), - downloadLogs: el.getAttribute('data-locale-download-logs'), - status: { - unknown: el.getAttribute('data-locale-status-unknown'), - waiting: el.getAttribute('data-locale-status-waiting'), - running: el.getAttribute('data-locale-status-running'), - success: el.getAttribute('data-locale-status-success'), - failure: el.getAttribute('data-locale-status-failure'), - cancelled: el.getAttribute('data-locale-status-cancelled'), - skipped: el.getAttribute('data-locale-status-skipped'), - blocked: el.getAttribute('data-locale-status-blocked'), - }, - logsAlwaysAutoScroll: el.getAttribute('data-locale-logs-always-auto-scroll'), - logsAlwaysExpandRunning: el.getAttribute('data-locale-logs-always-expand-running'), - }, - }); - view.mount(el); -} +}); </script> <template> <div class="ui container action-view-container"> diff --git a/web_src/js/components/RepoActivityTopAuthors.vue b/web_src/js/components/RepoActivityTopAuthors.vue index 1f5e9825ba..1295e15582 100644 --- a/web_src/js/components/RepoActivityTopAuthors.vue +++ b/web_src/js/components/RepoActivityTopAuthors.vue @@ -8,13 +8,15 @@ const colors = ref({ textAltColor: 'white', }); -// possible keys: -// * avatar_link: (...) -// * commits: (...) -// * home_link: (...) -// * login: (...) -// * name: (...) -const activityTopAuthors = window.config.pageData.repoActivityTopAuthors || []; +type ActivityAuthorData = { + avatar_link: string; + commits: number; + home_link: string; + login: string; + name: string; +} + +const activityTopAuthors: Array<ActivityAuthorData> = window.config.pageData.repoActivityTopAuthors || []; const graphPoints = computed(() => { return activityTopAuthors.map((item) => { @@ -26,7 +28,7 @@ const graphPoints = computed(() => { }); const graphAuthors = computed(() => { - return activityTopAuthors.map((item, idx) => { + return activityTopAuthors.map((item, idx: number) => { return { position: idx + 1, ...item, diff --git a/web_src/js/components/RepoBranchTagSelector.vue b/web_src/js/components/RepoBranchTagSelector.vue index a5ed8b6dad..7e35d55b2f 100644 --- a/web_src/js/components/RepoBranchTagSelector.vue +++ b/web_src/js/components/RepoBranchTagSelector.vue @@ -1,5 +1,5 @@ <script lang="ts"> -import {nextTick} from 'vue'; +import {defineComponent, nextTick} from 'vue'; import {SvgIcon} from '../svg.ts'; import {showErrorToast} from '../modules/toast.ts'; import {GET} from '../modules/fetch.ts'; @@ -17,51 +17,11 @@ type SelectedTab = 'branches' | 'tags'; type TabLoadingStates = Record<SelectedTab, '' | 'loading' | 'done'> -const sfc = { +export default defineComponent({ components: {SvgIcon}, props: { elRoot: HTMLElement, }, - computed: { - searchFieldPlaceholder() { - return this.selectedTab === 'branches' ? this.textFilterBranch : this.textFilterTag; - }, - filteredItems(): ListItem[] { - const searchTermLower = this.searchTerm.toLowerCase(); - const items = this.allItems.filter((item: ListItem) => { - const typeMatched = (this.selectedTab === 'branches' && item.refType === 'branch') || (this.selectedTab === 'tags' && item.refType === 'tag'); - if (!typeMatched) return false; - if (!this.searchTerm) return true; // match all - return item.refShortName.toLowerCase().includes(searchTermLower); - }); - - // TODO: fix this anti-pattern: side-effects-in-computed-properties - this.activeItemIndex = !items.length && this.showCreateNewRef ? 0 : -1; - return items; - }, - showNoResults() { - if (this.tabLoadingStates[this.selectedTab] !== 'done') return false; - return !this.filteredItems.length && !this.showCreateNewRef; - }, - showCreateNewRef() { - if (!this.allowCreateNewRef || !this.searchTerm) { - return false; - } - return !this.allItems.filter((item: ListItem) => { - return item.refShortName === this.searchTerm; // FIXME: not quite right here, it mixes "branch" and "tag" names - }).length; - }, - createNewRefFormActionUrl() { - return `${this.currentRepoLink}/branches/_new/${this.currentRefType}/${pathEscapeSegments(this.currentRefShortName)}`; - }, - }, - watch: { - menuVisible(visible: boolean) { - if (!visible) return; - this.focusSearchField(); - this.loadTabItems(); - }, - }, data() { const shouldShowTabBranches = this.elRoot.getAttribute('data-show-tab-branches') === 'true'; return { @@ -89,7 +49,7 @@ const sfc = { currentRepoDefaultBranch: this.elRoot.getAttribute('data-current-repo-default-branch'), currentRepoLink: this.elRoot.getAttribute('data-current-repo-link'), currentTreePath: this.elRoot.getAttribute('data-current-tree-path'), - currentRefType: this.elRoot.getAttribute('data-current-ref-type'), + currentRefType: this.elRoot.getAttribute('data-current-ref-type') as GitRefType, currentRefShortName: this.elRoot.getAttribute('data-current-ref-short-name'), refLinkTemplate: this.elRoot.getAttribute('data-ref-link-template'), @@ -102,6 +62,46 @@ const sfc = { enableFeed: this.elRoot.getAttribute('data-enable-feed') === 'true', }; }, + computed: { + searchFieldPlaceholder() { + return this.selectedTab === 'branches' ? this.textFilterBranch : this.textFilterTag; + }, + filteredItems(): ListItem[] { + const searchTermLower = this.searchTerm.toLowerCase(); + const items = this.allItems.filter((item: ListItem) => { + const typeMatched = (this.selectedTab === 'branches' && item.refType === 'branch') || (this.selectedTab === 'tags' && item.refType === 'tag'); + if (!typeMatched) return false; + if (!this.searchTerm) return true; // match all + return item.refShortName.toLowerCase().includes(searchTermLower); + }); + + // TODO: fix this anti-pattern: side-effects-in-computed-properties + this.activeItemIndex = !items.length && this.showCreateNewRef ? 0 : -1; // eslint-disable-line vue/no-side-effects-in-computed-properties + return items; + }, + showNoResults() { + if (this.tabLoadingStates[this.selectedTab] !== 'done') return false; + return !this.filteredItems.length && !this.showCreateNewRef; + }, + showCreateNewRef() { + if (!this.allowCreateNewRef || !this.searchTerm) { + return false; + } + return !this.allItems.filter((item: ListItem) => { + return item.refShortName === this.searchTerm; // FIXME: not quite right here, it mixes "branch" and "tag" names + }).length; + }, + createNewRefFormActionUrl() { + return `${this.currentRepoLink}/branches/_new/${this.currentRefType}/${pathEscapeSegments(this.currentRefShortName)}`; + }, + }, + watch: { + menuVisible(visible: boolean) { + if (!visible) return; + this.focusSearchField(); + this.loadTabItems(); + }, + }, beforeMount() { document.body.addEventListener('click', (e) => { if (this.$el.contains(e.target)) return; @@ -139,11 +139,11 @@ const sfc = { } }, createNewRef() { - this.$refs.createNewRefForm?.submit(); + (this.$refs.createNewRefForm as HTMLFormElement)?.submit(); }, focusSearchField() { nextTick(() => { - this.$refs.searchField.focus(); + (this.$refs.searchField as HTMLInputElement).focus(); }); }, getSelectedIndexInFiltered() { @@ -154,6 +154,7 @@ const sfc = { }, getActiveItem() { const el = this.$refs[`listItem${this.activeItemIndex}`]; // eslint-disable-line no-jquery/variable-pattern + // @ts-expect-error - el is unknown type return (el && el.length) ? el[0] : null; }, keydown(e) { @@ -212,9 +213,7 @@ const sfc = { } }, }, -}; - -export default sfc; // activate IDE's Vue plugin +}); </script> <template> <div class="ui dropdown custom branch-selector-dropdown ellipsis-items-nowrap"> diff --git a/web_src/js/components/RepoContributors.vue b/web_src/js/components/RepoContributors.vue index e79fc80d8e..5d2c74b6a9 100644 --- a/web_src/js/components/RepoContributors.vue +++ b/web_src/js/components/RepoContributors.vue @@ -1,4 +1,5 @@ <script lang="ts"> +import {defineComponent, type PropType} from 'vue'; import {SvgIcon} from '../svg.ts'; import dayjs from 'dayjs'; import { @@ -56,11 +57,11 @@ Chart.register( customEventListener, ); -export default { +export default defineComponent({ components: {ChartLine, SvgIcon}, props: { locale: { - type: Object, + type: Object as PropType<Record<string, any>>, required: true, }, repoLink: { @@ -88,7 +89,7 @@ export default { this.fetchGraphData(); fomanticQuery('#repo-contributors').dropdown({ - onChange: (val) => { + onChange: (val: string) => { this.xAxisMin = this.xAxisStart; this.xAxisMax = this.xAxisEnd; this.type = val; @@ -320,7 +321,7 @@ export default { }; }, }, -}; +}); </script> <template> <div> diff --git a/web_src/js/features/common-organization.ts b/web_src/js/features/common-organization.ts index 47a61ece22..a1f19bedea 100644 --- a/web_src/js/features/common-organization.ts +++ b/web_src/js/features/common-organization.ts @@ -6,7 +6,7 @@ export function initCommonOrganization() { return; } - document.querySelector('.organization.settings.options #org_name')?.addEventListener('input', function () { + document.querySelector<HTMLInputElement>('.organization.settings.options #org_name')?.addEventListener('input', function () { const nameChanged = this.value.toLowerCase() !== this.getAttribute('data-org-name').toLowerCase(); toggleElem('#org-name-change-prompt', nameChanged); }); diff --git a/web_src/js/features/comp/WebHookEditor.ts b/web_src/js/features/comp/WebHookEditor.ts index 203396af80..794b3c99ca 100644 --- a/web_src/js/features/comp/WebHookEditor.ts +++ b/web_src/js/features/comp/WebHookEditor.ts @@ -6,7 +6,7 @@ export function initCompWebHookEditor() { return; } - for (const input of document.querySelectorAll('.events.checkbox input')) { + for (const input of document.querySelectorAll<HTMLInputElement>('.events.checkbox input')) { input.addEventListener('change', function () { if (this.checked) { showElem('.events.fields'); @@ -14,7 +14,7 @@ export function initCompWebHookEditor() { }); } - for (const input of document.querySelectorAll('.non-events.checkbox input')) { + for (const input of document.querySelectorAll<HTMLInputElement>('.non-events.checkbox input')) { input.addEventListener('change', function () { if (this.checked) { hideElem('.events.fields'); @@ -34,7 +34,7 @@ export function initCompWebHookEditor() { } // Test delivery - document.querySelector('#test-delivery')?.addEventListener('click', async function () { + document.querySelector<HTMLButtonElement>('#test-delivery')?.addEventListener('click', async function () { this.classList.add('is-loading', 'disabled'); await POST(this.getAttribute('data-link')); setTimeout(() => { diff --git a/web_src/js/features/dashboard.ts b/web_src/js/features/dashboard.ts new file mode 100644 index 0000000000..71a0df3a64 --- /dev/null +++ b/web_src/js/features/dashboard.ts @@ -0,0 +1,9 @@ +import {createApp} from 'vue'; +import DashboardRepoList from '../components/DashboardRepoList.vue'; + +export function initDashboardRepoList() { + const el = document.querySelector('#dashboard-repo-list'); + if (el) { + createApp(DashboardRepoList).mount(el); + } +} diff --git a/web_src/js/features/install.ts b/web_src/js/features/install.ts index 725dcafab0..dddeb1e954 100644 --- a/web_src/js/features/install.ts +++ b/web_src/js/features/install.ts @@ -27,7 +27,7 @@ function initPreInstall() { const dbName = document.querySelector<HTMLInputElement>('#db_name'); // Database type change detection. - document.querySelector('#db_type').addEventListener('change', function () { + document.querySelector<HTMLInputElement>('#db_type').addEventListener('change', function () { const dbType = this.value; hideElem('div[data-db-setting-for]'); showElem(`div[data-db-setting-for=${dbType}]`); @@ -59,26 +59,26 @@ function initPreInstall() { } // TODO: better handling of exclusive relations. - document.querySelector('#offline-mode input').addEventListener('change', function () { + document.querySelector<HTMLInputElement>('#offline-mode input').addEventListener('change', function () { if (this.checked) { 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 () { + document.querySelector<HTMLInputElement>('#disable-gravatar input').addEventListener('change', function () { if (this.checked) { document.querySelector<HTMLInputElement>('#federated-avatar-lookup input').checked = false; } else { document.querySelector<HTMLInputElement>('#offline-mode input').checked = false; } }); - document.querySelector('#federated-avatar-lookup input').addEventListener('change', function () { + document.querySelector<HTMLInputElement>('#federated-avatar-lookup input').addEventListener('change', function () { if (this.checked) { 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 () { + document.querySelector<HTMLInputElement>('#enable-openid-signin input').addEventListener('change', function () { if (this.checked) { if (!document.querySelector<HTMLInputElement>('#disable-registration input').checked) { document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = true; @@ -87,7 +87,7 @@ function initPreInstall() { document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = false; } }); - document.querySelector('#disable-registration input').addEventListener('change', function () { + document.querySelector<HTMLInputElement>('#disable-registration input').addEventListener('change', function () { if (this.checked) { document.querySelector<HTMLInputElement>('#enable-captcha input').checked = false; document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = false; @@ -95,7 +95,7 @@ function initPreInstall() { document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = true; } }); - document.querySelector('#enable-captcha input').addEventListener('change', function () { + document.querySelector<HTMLInputElement>('#enable-captcha input').addEventListener('change', function () { if (this.checked) { document.querySelector<HTMLInputElement>('#disable-registration input').checked = false; } diff --git a/web_src/js/features/pull-view-file.ts b/web_src/js/features/pull-view-file.ts index 36fe4bc4df..5202d84b28 100644 --- a/web_src/js/features/pull-view-file.ts +++ b/web_src/js/features/pull-view-file.ts @@ -38,7 +38,7 @@ export function initViewedCheckboxListenerFor() { // 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 - const checkbox = form.querySelector('input[type=checkbox]'); + const checkbox = form.querySelector<HTMLInputElement>('input[type=checkbox]'); checkbox.addEventListener('input', function() { // Mark the file as viewed visually - will especially change the background if (this.checked) { diff --git a/web_src/js/features/repo-actions.ts b/web_src/js/features/repo-actions.ts new file mode 100644 index 0000000000..cbd0429c04 --- /dev/null +++ b/web_src/js/features/repo-actions.ts @@ -0,0 +1,47 @@ +import {createApp} from 'vue'; +import RepoActionView from '../components/RepoActionView.vue'; + +export function initRepositoryActionView() { + const el = document.querySelector('#repo-action-view'); + if (!el) return; + + // TODO: the parent element's full height doesn't work well now, + // but we can not pollute the global style at the moment, only fix the height problem for pages with this component + const parentFullHeight = document.querySelector<HTMLElement>('body > div.full.height'); + if (parentFullHeight) parentFullHeight.style.paddingBottom = '0'; + + const view = createApp(RepoActionView, { + runIndex: el.getAttribute('data-run-index'), + jobIndex: el.getAttribute('data-job-index'), + actionsURL: el.getAttribute('data-actions-url'), + locale: { + approve: el.getAttribute('data-locale-approve'), + cancel: el.getAttribute('data-locale-cancel'), + rerun: el.getAttribute('data-locale-rerun'), + rerun_all: el.getAttribute('data-locale-rerun-all'), + scheduled: el.getAttribute('data-locale-runs-scheduled'), + commit: el.getAttribute('data-locale-runs-commit'), + pushedBy: el.getAttribute('data-locale-runs-pushed-by'), + artifactsTitle: el.getAttribute('data-locale-artifacts-title'), + areYouSure: el.getAttribute('data-locale-are-you-sure'), + confirmDeleteArtifact: el.getAttribute('data-locale-confirm-delete-artifact'), + showTimeStamps: el.getAttribute('data-locale-show-timestamps'), + showLogSeconds: el.getAttribute('data-locale-show-log-seconds'), + showFullScreen: el.getAttribute('data-locale-show-full-screen'), + downloadLogs: el.getAttribute('data-locale-download-logs'), + status: { + unknown: el.getAttribute('data-locale-status-unknown'), + waiting: el.getAttribute('data-locale-status-waiting'), + running: el.getAttribute('data-locale-status-running'), + success: el.getAttribute('data-locale-status-success'), + failure: el.getAttribute('data-locale-status-failure'), + cancelled: el.getAttribute('data-locale-status-cancelled'), + skipped: el.getAttribute('data-locale-status-skipped'), + blocked: el.getAttribute('data-locale-status-blocked'), + }, + logsAlwaysAutoScroll: el.getAttribute('data-locale-logs-always-auto-scroll'), + logsAlwaysExpandRunning: el.getAttribute('data-locale-logs-always-expand-running'), + }, + }); + view.mount(el); +} diff --git a/web_src/js/features/repo-commit.ts b/web_src/js/features/repo-commit.ts index 56493443d9..8994a57f4a 100644 --- a/web_src/js/features/repo-commit.ts +++ b/web_src/js/features/repo-commit.ts @@ -2,7 +2,7 @@ import {createTippy} from '../modules/tippy.ts'; import {toggleElem} from '../utils/dom.ts'; export function initRepoEllipsisButton() { - for (const button of document.querySelectorAll('.js-toggle-commit-body')) { + for (const button of document.querySelectorAll<HTMLButtonElement>('.js-toggle-commit-body')) { button.addEventListener('click', function (e) { e.preventDefault(); const expanded = this.getAttribute('aria-expanded') === 'true'; diff --git a/web_src/js/features/repo-home.ts b/web_src/js/features/repo-home.ts index 9c1e317486..763f8e503f 100644 --- a/web_src/js/features/repo-home.ts +++ b/web_src/js/features/repo-home.ts @@ -89,7 +89,7 @@ export function initRepoTopicBar() { url: `${appSubUrl}/explore/topics/search?q={query}`, throttle: 500, cache: false, - onResponse(res) { + onResponse(this: any, res: any) { const formattedResponse = { success: false, results: [], diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index d74d3f7700..d2a89682e8 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -216,7 +216,7 @@ export function initRepoIssueCodeCommentCancel() { export function initRepoPullRequestUpdate() { // Pull Request update button - const pullUpdateButton = document.querySelector('.update-button > button'); + const pullUpdateButton = document.querySelector<HTMLButtonElement>('.update-button > button'); if (!pullUpdateButton) return; pullUpdateButton.addEventListener('click', async function (e) { diff --git a/web_src/js/features/repo-settings.ts b/web_src/js/features/repo-settings.ts index 90b0219f3e..7b3ab504cb 100644 --- a/web_src/js/features/repo-settings.ts +++ b/web_src/js/features/repo-settings.ts @@ -79,21 +79,21 @@ function initRepoSettingsGitHook() { function initRepoSettingsBranches() { if (!document.querySelector('.repository.settings.branches')) return; - for (const el of document.querySelectorAll('.toggle-target-enabled')) { + for (const el of document.querySelectorAll<HTMLInputElement>('.toggle-target-enabled')) { el.addEventListener('change', function () { const target = document.querySelector(this.getAttribute('data-target')); target?.classList.toggle('disabled', !this.checked); }); } - for (const el of document.querySelectorAll('.toggle-target-disabled')) { + for (const el of document.querySelectorAll<HTMLInputElement>('.toggle-target-disabled')) { el.addEventListener('change', function () { const target = document.querySelector(this.getAttribute('data-target')); if (this.checked) target?.classList.add('disabled'); // only disable, do not auto enable }); } - document.querySelector('#dismiss_stale_approvals')?.addEventListener('change', function () { + document.querySelector<HTMLInputElement>('#dismiss_stale_approvals')?.addEventListener('change', function () { document.querySelector('#ignore_stale_approvals_box')?.classList.toggle('disabled', this.checked); }); diff --git a/web_src/js/features/sshkey-helper.ts b/web_src/js/features/sshkey-helper.ts index 9234e3ec44..860bc5b294 100644 --- a/web_src/js/features/sshkey-helper.ts +++ b/web_src/js/features/sshkey-helper.ts @@ -1,6 +1,6 @@ export function initSshKeyFormParser() { // Parse SSH Key - document.querySelector('#ssh-key-content')?.addEventListener('input', function () { + document.querySelector<HTMLTextAreaElement>('#ssh-key-content')?.addEventListener('input', function () { const arrays = this.value.split(' '); const title = document.querySelector<HTMLInputElement>('#ssh-key-title'); if (!title.value && arrays.length === 3 && arrays[2] !== '') { diff --git a/web_src/js/features/user-settings.ts b/web_src/js/features/user-settings.ts index c097df7b6c..6312a8b682 100644 --- a/web_src/js/features/user-settings.ts +++ b/web_src/js/features/user-settings.ts @@ -13,7 +13,7 @@ export function initUserSettings() { initUserSettingsAvatarCropper(); - const usernameInput = document.querySelector('#username'); + const usernameInput = document.querySelector<HTMLInputElement>('#username'); if (!usernameInput) return; usernameInput.addEventListener('input', function () { const prompt = document.querySelector('#name-change-prompt'); diff --git a/web_src/js/index.ts b/web_src/js/index.ts index 022be033da..b89e596047 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -2,8 +2,7 @@ import './bootstrap.ts'; import './htmx.ts'; -import {initDashboardRepoList} from './components/DashboardRepoList.vue'; - +import {initDashboardRepoList} from './features/dashboard.ts'; import {initGlobalCopyToClipboardListener} from './features/clipboard.ts'; import {initContextPopups} from './features/contextpopup.ts'; import {initRepoGraphGit} from './features/repo-graph.ts'; @@ -53,7 +52,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'; -import {initRepositoryActionView} from './components/RepoActionView.vue'; +import {initRepositoryActionView} from './features/repo-actions.ts'; import {initGlobalTooltips} from './modules/tippy.ts'; import {initGiteaFomantic} from './modules/fomantic.ts'; import {initSubmitEventPolyfill, onDomReady} from './utils/dom.ts'; diff --git a/web_src/js/modules/fomantic/dimmer.ts b/web_src/js/modules/fomantic/dimmer.ts index 4e05cac0cd..cbdfac23cb 100644 --- a/web_src/js/modules/fomantic/dimmer.ts +++ b/web_src/js/modules/fomantic/dimmer.ts @@ -3,7 +3,7 @@ import {queryElemChildren} from '../../utils/dom.ts'; export function initFomanticDimmer() { // stand-in for removed dimmer module - $.fn.dimmer = function (arg0: string, arg1: any) { + $.fn.dimmer = function (this: any, arg0: string, arg1: any) { if (arg0 === 'add content') { const $el = arg1; const existingDimmer = document.querySelector('body > .ui.dimmer'); diff --git a/web_src/js/modules/fomantic/dropdown.ts b/web_src/js/modules/fomantic/dropdown.ts index 9bdc9bfc33..e479c79ee6 100644 --- a/web_src/js/modules/fomantic/dropdown.ts +++ b/web_src/js/modules/fomantic/dropdown.ts @@ -17,7 +17,7 @@ export function initAriaDropdownPatch() { // the patched `$.fn.dropdown` function, it passes the arguments to Fomantic's `$.fn.dropdown` function, and: // * it does the one-time attaching on the first call // * it delegates the `onLabelCreate` to the patched `onLabelCreate` to add necessary aria attributes -function ariaDropdownFn(...args: Parameters<FomanticInitFunction>) { +function ariaDropdownFn(this: any, ...args: Parameters<FomanticInitFunction>) { const ret = fomanticDropdownFn.apply(this, args); // if the `$().dropdown()` call is without arguments, or it has non-string (object) argument, @@ -76,18 +76,18 @@ function delegateOne($dropdown: any) { const oldFocusSearch = dropdownCall('internal', 'focusSearch'); const oldBlurSearch = dropdownCall('internal', 'blurSearch'); // * If the "dropdown icon" is clicked, Fomantic calls "focusSearch", so show the menu - dropdownCall('internal', 'focusSearch', function () { dropdownCall('show'); oldFocusSearch.call(this) }); + dropdownCall('internal', 'focusSearch', function (this: any) { dropdownCall('show'); oldFocusSearch.call(this) }); // * If the "dropdown icon" is clicked again when the menu is visible, Fomantic calls "blurSearch", so hide the menu - dropdownCall('internal', 'blurSearch', function () { oldBlurSearch.call(this); dropdownCall('hide') }); + dropdownCall('internal', 'blurSearch', function (this: any) { oldBlurSearch.call(this); dropdownCall('hide') }); const oldFilterItems = dropdownCall('internal', 'filterItems'); - dropdownCall('internal', 'filterItems', function (...args: any[]) { + dropdownCall('internal', 'filterItems', function (this: any, ...args: any[]) { oldFilterItems.call(this, ...args); processMenuItems($dropdown, dropdownCall); }); const oldShow = dropdownCall('internal', 'show'); - dropdownCall('internal', 'show', function (...args: any[]) { + dropdownCall('internal', 'show', function (this: any, ...args: any[]) { oldShow.call(this, ...args); processMenuItems($dropdown, dropdownCall); }); @@ -110,7 +110,7 @@ function delegateOne($dropdown: any) { // the `onLabelCreate` is used to add necessary aria attributes for dynamically created selection labels const dropdownOnLabelCreateOld = dropdownCall('setting', 'onLabelCreate'); - dropdownCall('setting', 'onLabelCreate', function(value: any, text: string) { + dropdownCall('setting', 'onLabelCreate', function(this: any, value: any, text: string) { const $label = dropdownOnLabelCreateOld.call(this, value, text); updateSelectionLabel($label[0]); return $label; diff --git a/web_src/js/modules/fomantic/modal.ts b/web_src/js/modules/fomantic/modal.ts index fb80047d01..6a2c558890 100644 --- a/web_src/js/modules/fomantic/modal.ts +++ b/web_src/js/modules/fomantic/modal.ts @@ -12,7 +12,7 @@ export function initAriaModalPatch() { // the patched `$.fn.modal` modal function // * it does the one-time attaching on the first call -function ariaModalFn(...args: Parameters<FomanticInitFunction>) { +function ariaModalFn(this: any, ...args: Parameters<FomanticInitFunction>) { const ret = fomanticModalFn.apply(this, args); if (args[0] === 'show' || args[0]?.autoShow) { for (const el of this) { diff --git a/web_src/js/modules/tippy.ts b/web_src/js/modules/tippy.ts index aaaf580de1..bc6d5bfdd6 100644 --- a/web_src/js/modules/tippy.ts +++ b/web_src/js/modules/tippy.ts @@ -121,7 +121,7 @@ function switchTitleToTooltip(target: Element): void { * Some browsers like PaleMoon don't support "addEventListener('mouseenter', capture)" * The tippy by default uses "mouseenter" event to show, so we use "mouseover" event to switch to tippy */ -function lazyTooltipOnMouseHover(e: Event): void { +function lazyTooltipOnMouseHover(this: HTMLElement, e: Event): void { e.target.removeEventListener('mouseover', lazyTooltipOnMouseHover, true); attachTooltip(this); } diff --git a/web_src/js/svg.ts b/web_src/js/svg.ts index 6a8246fa1b..b074fecd04 100644 --- a/web_src/js/svg.ts +++ b/web_src/js/svg.ts @@ -1,4 +1,4 @@ -import {h} from 'vue'; +import {defineComponent, h, type PropType} from 'vue'; import {parseDom, serializeXml} from './utils.ts'; import giteaDoubleChevronLeft from '../../public/assets/img/svg/gitea-double-chevron-left.svg'; import giteaDoubleChevronRight from '../../public/assets/img/svg/gitea-double-chevron-right.svg'; @@ -194,10 +194,10 @@ export function svgParseOuterInner(name: SvgName) { return {svgOuter, svgInnerHtml}; } -export const SvgIcon = { +export const SvgIcon = defineComponent({ name: 'SvgIcon', props: { - name: {type: String, required: true}, + name: {type: String as PropType<SvgName>, required: true}, size: {type: Number, default: 16}, className: {type: String, default: ''}, symbolId: {type: String}, @@ -215,7 +215,7 @@ export const SvgIcon = { attrs[`^height`] = this.size; // make the <SvgIcon class="foo" class-name="bar"> classes work together - const classes = []; + const classes: Array<string> = []; for (const cls of svgOuter.classList) { classes.push(cls); } @@ -234,4 +234,4 @@ export const SvgIcon = { innerHTML: svgInnerHtml, }); }, -}; +}); |