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
pull/33276/head^2
silverwind 2025-01-15 21:26:17 +01:00 committed by GitHub
parent b15d01b0ce
commit 4b21a6c792
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 209 additions and 190 deletions

View File

@ -23,6 +23,7 @@
"stripInternal": true, "stripInternal": true,
"strict": false, "strict": false,
"strictFunctionTypes": true, "strictFunctionTypes": true,
"noImplicitThis": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"noPropertyAccessFromIndexSignature": false, "noPropertyAccessFromIndexSignature": false,

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {createApp, nextTick} from 'vue'; import {nextTick, defineComponent} from 'vue';
import {SvgIcon} from '../svg.ts'; import {SvgIcon} from '../svg.ts';
import {GET} from '../modules/fetch.ts'; import {GET} from '../modules/fetch.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts';
@ -24,7 +24,7 @@ const commitStatus: CommitStatusMap = {
warning: {name: 'gitea-exclamation', color: 'yellow'}, warning: {name: 'gitea-exclamation', color: 'yellow'},
}; };
const sfc = { export default defineComponent({
components: {SvgIcon}, components: {SvgIcon},
data() { data() {
const params = new URLSearchParams(window.location.search); 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> </script>
<template> <template>
<div> <div>

View File

@ -1,9 +1,10 @@
<script lang="ts"> <script lang="ts">
import {defineComponent} from 'vue';
import {SvgIcon} from '../svg.ts'; import {SvgIcon} from '../svg.ts';
import {GET} from '../modules/fetch.ts'; import {GET} from '../modules/fetch.ts';
import {generateAriaId} from '../modules/fomantic/base.ts'; import {generateAriaId} from '../modules/fomantic/base.ts';
export default { export default defineComponent({
components: {SvgIcon}, components: {SvgIcon},
data: () => { data: () => {
const el = document.querySelector('#diff-commit-select'); const el = document.querySelector('#diff-commit-select');
@ -55,11 +56,11 @@ export default {
switch (event.key) { switch (event.key) {
case 'ArrowDown': // select next element case 'ArrowDown': // select next element
event.preventDefault(); event.preventDefault();
this.focusElem(item.nextElementSibling, item); this.focusElem(item.nextElementSibling as HTMLElement, item);
break; break;
case 'ArrowUp': // select previous element case 'ArrowUp': // select previous element
event.preventDefault(); event.preventDefault();
this.focusElem(item.previousElementSibling, item); this.focusElem(item.previousElementSibling as HTMLElement, item);
break; break;
case 'Escape': // close menu case 'Escape': // close menu
event.preventDefault(); event.preventDefault();
@ -118,9 +119,9 @@ export default {
// set correct tabindex to allow easier navigation // set correct tabindex to allow easier navigation
this.$nextTick(() => { this.$nextTick(() => {
if (this.menuVisible) { if (this.menuVisible) {
this.focusElem(this.$refs.showAllChanges, this.$refs.expandBtn); this.focusElem(this.$refs.showAllChanges as HTMLElement, this.$refs.expandBtn as HTMLElement);
} else { } 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> </script>
<template> <template>
<div class="ui scrolling dropdown custom diff-commit-selector"> <div class="ui scrolling dropdown custom diff-commit-selector">

View File

@ -17,7 +17,7 @@ function toggleFileList() {
store.fileListIsVisible = !store.fileListIsVisible; store.fileListIsVisible = !store.fileListIsVisible;
} }
function diffTypeToString(pType) { function diffTypeToString(pType: number) {
const diffTypes = { const diffTypes = {
1: 'add', 1: 'add',
2: 'modify', 2: 'modify',
@ -28,7 +28,7 @@ function diffTypeToString(pType) {
return diffTypes[pType]; return diffTypes[pType];
} }
function diffStatsWidth(adds, dels) { function diffStatsWidth(adds: number, dels: number) {
return `${adds / (adds + dels) * 100}%`; return `${adds / (adds + dels) * 100}%`;
} }

View File

@ -60,7 +60,7 @@ const fileTree = computed(() => {
parent = newParent; parent = newParent;
} }
} }
const mergeChildIfOnlyOneDir = (entries) => { const mergeChildIfOnlyOneDir = (entries: Array<Record<string, any>>) => {
for (const entry of entries) { for (const entry of entries) {
if (entry.children) { if (entry.children) {
mergeChildIfOnlyOneDir(entry.children); mergeChildIfOnlyOneDir(entry.children);
@ -110,13 +110,13 @@ function toggleVisibility() {
updateVisibility(!store.fileTreeIsVisible); updateVisibility(!store.fileTreeIsVisible);
} }
function updateVisibility(visible) { function updateVisibility(visible: boolean) {
store.fileTreeIsVisible = visible; store.fileTreeIsVisible = visible;
localStorage.setItem(LOCAL_STORAGE_KEY, store.fileTreeIsVisible); localStorage.setItem(LOCAL_STORAGE_KEY, store.fileTreeIsVisible);
updateState(store.fileTreeIsVisible); updateState(store.fileTreeIsVisible);
} }
function updateState(visible) { function updateState(visible: boolean) {
const btn = document.querySelector('.diff-toggle-file-tree-button'); const btn = document.querySelector('.diff-toggle-file-tree-button');
const [toShow, toHide] = btn.querySelectorAll('.icon'); const [toShow, toHide] = btn.querySelectorAll('.icon');
const tree = document.querySelector('#diff-file-tree'); const tree = document.querySelector('#diff-file-tree');

View File

@ -25,7 +25,7 @@ defineProps<{
const store = diffTreeStore(); const store = diffTreeStore();
const collapsed = ref(false); const collapsed = ref(false);
function getIconForDiffType(pType) { function getIconForDiffType(pType: number) {
const diffTypes = { const diffTypes = {
1: {name: 'octicon-diff-added', classes: ['text', 'green']}, 1: {name: 'octicon-diff-added', classes: ['text', 'green']},
2: {name: 'octicon-diff-modified', classes: ['text', 'yellow']}, 2: {name: 'octicon-diff-modified', classes: ['text', 'yellow']},
@ -36,7 +36,7 @@ function getIconForDiffType(pType) {
return diffTypes[pType]; return diffTypes[pType];
} }
function fileIcon(file) { function fileIcon(file: File) {
if (file.IsSubmodule) { if (file.IsSubmodule) {
return 'octicon-file-submodule'; return 'octicon-file-submodule';
} }

View File

@ -68,7 +68,7 @@ function toggleActionForm(show: boolean) {
mergeMessageFieldValue.value = mergeStyleDetail.value.mergeMessageFieldText; mergeMessageFieldValue.value = mergeStyleDetail.value.mergeMessageFieldText;
} }
function switchMergeStyle(name, autoMerge = false) { function switchMergeStyle(name: string, autoMerge = false) {
mergeStyle.value = name; mergeStyle.value = name;
autoMergeWhenSucceed.value = autoMerge; autoMergeWhenSucceed.value = autoMerge;
} }

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import {SvgIcon} from '../svg.ts'; import {SvgIcon} from '../svg.ts';
import ActionRunStatus from './ActionRunStatus.vue'; import ActionRunStatus from './ActionRunStatus.vue';
import {createApp} from 'vue'; import {defineComponent, type PropType} from 'vue';
import {createElementFromAttrs, toggleElem} from '../utils/dom.ts'; import {createElementFromAttrs, toggleElem} from '../utils/dom.ts';
import {formatDatetime} from '../utils/time.ts'; import {formatDatetime} from '../utils/time.ts';
import {renderAnsi} from '../render/ansi.ts'; import {renderAnsi} from '../render/ansi.ts';
@ -38,7 +38,7 @@ function parseLineCommand(line: LogLine): LogLineCommand | null {
return null; return null;
} }
function isLogElementInViewport(el: HTMLElement): boolean { function isLogElementInViewport(el: Element): boolean {
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();
return rect.top >= 0 && rect.bottom <= window.innerHeight; // only check height but not width 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}; return {autoScroll: true, expandRunning: false};
} }
const sfc = { export default defineComponent({
name: 'RepoActionView', name: 'RepoActionView',
components: { components: {
SvgIcon, SvgIcon,
ActionRunStatus, ActionRunStatus,
}, },
props: { props: {
runIndex: String, runIndex: {
jobIndex: String, type: String,
actionsURL: String, default: '',
locale: Object,
},
watch: {
optionAlwaysAutoScroll() {
this.saveLocaleStorageOptions();
}, },
optionAlwaysExpandRunning() { jobIndex: {
this.saveLocaleStorageOptions(); type: String,
default: '',
},
actionsURL: {
type: String,
default: '',
},
locale: {
type: Object as PropType<Record<string, string>>,
default: null,
}, },
}, },
@ -102,10 +105,11 @@ const sfc = {
link: '', link: '',
title: '', title: '',
titleHTML: '', titleHTML: '',
status: '', status: 'unknown' as RunStatus,
canCancel: false, canCancel: false,
canApprove: false, canApprove: false,
canRerun: false, canRerun: false,
canDeleteArtifact: false,
done: false, done: false,
workflowID: '', workflowID: '',
workflowLink: '', workflowLink: '',
@ -131,6 +135,7 @@ const sfc = {
branch: { branch: {
name: '', name: '',
link: '', 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 // load job data and then auto-reload periodically
// need to await first loadJob so this.currentJobStepsStates is initialized and can be used in hashChangeListener // need to await first loadJob so this.currentJobStepsStates is initialized and can be used in hashChangeListener
await this.loadJob(); 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` // 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 { getActiveLogsContainer(stepIndex: number): HTMLElement {
const el = this.getJobStepLogsContainer(stepIndex); const el = this.getJobStepLogsContainer(stepIndex);
// @ts-expect-error - _stepLogsActiveContainer is a custom property
return el._stepLogsActiveContainer ?? el; return el._stepLogsActiveContainer ?? el;
}, },
// begin a log group // begin a log group
@ -263,7 +278,7 @@ const sfc = {
const el = this.getJobStepLogsContainer(stepIndex); const el = this.getJobStepLogsContainer(stepIndex);
// if the logs container is empty, then auto-scroll if the step is expanded // if the logs container is empty, then auto-scroll if the step is expanded
if (!el.lastChild) return this.currentJobStepsStates[stepIndex].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[]) { appendLogs(stepIndex: number, startTime: number, logLines: LogLine[]) {
@ -380,7 +395,7 @@ const sfc = {
toggleTimeDisplay(type: string) { toggleTimeDisplay(type: string) {
this.timeVisible[`log-time-${type}`] = !this.timeVisible[`log-time-${type}`]; 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}`]); toggleElem(el, this.timeVisible[`log-time-${type}`]);
} }
}, },
@ -414,59 +429,12 @@ const sfc = {
// so logline can be selected by querySelector // so logline can be selected by querySelector
await this.loadJob(); await this.loadJob();
} }
const logLine = this.$refs.steps.querySelector(selectedLogStep); const logLine = (this.$refs.steps as HTMLElement).querySelector(selectedLogStep);
if (!logLine) return; 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> </script>
<template> <template>
<div class="ui container action-view-container"> <div class="ui container action-view-container">

View File

@ -8,13 +8,15 @@ const colors = ref({
textAltColor: 'white', textAltColor: 'white',
}); });
// possible keys: type ActivityAuthorData = {
// * avatar_link: (...) avatar_link: string;
// * commits: (...) commits: number;
// * home_link: (...) home_link: string;
// * login: (...) login: string;
// * name: (...) name: string;
const activityTopAuthors = window.config.pageData.repoActivityTopAuthors || []; }
const activityTopAuthors: Array<ActivityAuthorData> = window.config.pageData.repoActivityTopAuthors || [];
const graphPoints = computed(() => { const graphPoints = computed(() => {
return activityTopAuthors.map((item) => { return activityTopAuthors.map((item) => {
@ -26,7 +28,7 @@ const graphPoints = computed(() => {
}); });
const graphAuthors = computed(() => { const graphAuthors = computed(() => {
return activityTopAuthors.map((item, idx) => { return activityTopAuthors.map((item, idx: number) => {
return { return {
position: idx + 1, position: idx + 1,
...item, ...item,

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {nextTick} from 'vue'; import {defineComponent, nextTick} from 'vue';
import {SvgIcon} from '../svg.ts'; import {SvgIcon} from '../svg.ts';
import {showErrorToast} from '../modules/toast.ts'; import {showErrorToast} from '../modules/toast.ts';
import {GET} from '../modules/fetch.ts'; import {GET} from '../modules/fetch.ts';
@ -17,51 +17,11 @@ type SelectedTab = 'branches' | 'tags';
type TabLoadingStates = Record<SelectedTab, '' | 'loading' | 'done'> type TabLoadingStates = Record<SelectedTab, '' | 'loading' | 'done'>
const sfc = { export default defineComponent({
components: {SvgIcon}, components: {SvgIcon},
props: { props: {
elRoot: HTMLElement, 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() { data() {
const shouldShowTabBranches = this.elRoot.getAttribute('data-show-tab-branches') === 'true'; const shouldShowTabBranches = this.elRoot.getAttribute('data-show-tab-branches') === 'true';
return { return {
@ -89,7 +49,7 @@ const sfc = {
currentRepoDefaultBranch: this.elRoot.getAttribute('data-current-repo-default-branch'), currentRepoDefaultBranch: this.elRoot.getAttribute('data-current-repo-default-branch'),
currentRepoLink: this.elRoot.getAttribute('data-current-repo-link'), currentRepoLink: this.elRoot.getAttribute('data-current-repo-link'),
currentTreePath: this.elRoot.getAttribute('data-current-tree-path'), 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'), currentRefShortName: this.elRoot.getAttribute('data-current-ref-short-name'),
refLinkTemplate: this.elRoot.getAttribute('data-ref-link-template'), refLinkTemplate: this.elRoot.getAttribute('data-ref-link-template'),
@ -102,6 +62,46 @@ const sfc = {
enableFeed: this.elRoot.getAttribute('data-enable-feed') === 'true', 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() { beforeMount() {
document.body.addEventListener('click', (e) => { document.body.addEventListener('click', (e) => {
if (this.$el.contains(e.target)) return; if (this.$el.contains(e.target)) return;
@ -139,11 +139,11 @@ const sfc = {
} }
}, },
createNewRef() { createNewRef() {
this.$refs.createNewRefForm?.submit(); (this.$refs.createNewRefForm as HTMLFormElement)?.submit();
}, },
focusSearchField() { focusSearchField() {
nextTick(() => { nextTick(() => {
this.$refs.searchField.focus(); (this.$refs.searchField as HTMLInputElement).focus();
}); });
}, },
getSelectedIndexInFiltered() { getSelectedIndexInFiltered() {
@ -154,6 +154,7 @@ const sfc = {
}, },
getActiveItem() { getActiveItem() {
const el = this.$refs[`listItem${this.activeItemIndex}`]; // eslint-disable-line no-jquery/variable-pattern 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; return (el && el.length) ? el[0] : null;
}, },
keydown(e) { keydown(e) {
@ -212,9 +213,7 @@ const sfc = {
} }
}, },
}, },
}; });
export default sfc; // activate IDE's Vue plugin
</script> </script>
<template> <template>
<div class="ui dropdown custom branch-selector-dropdown ellipsis-items-nowrap"> <div class="ui dropdown custom branch-selector-dropdown ellipsis-items-nowrap">

View File

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import {defineComponent, type PropType} from 'vue';
import {SvgIcon} from '../svg.ts'; import {SvgIcon} from '../svg.ts';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { import {
@ -56,11 +57,11 @@ Chart.register(
customEventListener, customEventListener,
); );
export default { export default defineComponent({
components: {ChartLine, SvgIcon}, components: {ChartLine, SvgIcon},
props: { props: {
locale: { locale: {
type: Object, type: Object as PropType<Record<string, any>>,
required: true, required: true,
}, },
repoLink: { repoLink: {
@ -88,7 +89,7 @@ export default {
this.fetchGraphData(); this.fetchGraphData();
fomanticQuery('#repo-contributors').dropdown({ fomanticQuery('#repo-contributors').dropdown({
onChange: (val) => { onChange: (val: string) => {
this.xAxisMin = this.xAxisStart; this.xAxisMin = this.xAxisStart;
this.xAxisMax = this.xAxisEnd; this.xAxisMax = this.xAxisEnd;
this.type = val; this.type = val;
@ -320,7 +321,7 @@ export default {
}; };
}, },
}, },
}; });
</script> </script>
<template> <template>
<div> <div>

View File

@ -6,7 +6,7 @@ export function initCommonOrganization() {
return; 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(); const nameChanged = this.value.toLowerCase() !== this.getAttribute('data-org-name').toLowerCase();
toggleElem('#org-name-change-prompt', nameChanged); toggleElem('#org-name-change-prompt', nameChanged);
}); });

View File

@ -6,7 +6,7 @@ export function initCompWebHookEditor() {
return; return;
} }
for (const input of document.querySelectorAll('.events.checkbox input')) { for (const input of document.querySelectorAll<HTMLInputElement>('.events.checkbox input')) {
input.addEventListener('change', function () { input.addEventListener('change', function () {
if (this.checked) { if (this.checked) {
showElem('.events.fields'); 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 () { input.addEventListener('change', function () {
if (this.checked) { if (this.checked) {
hideElem('.events.fields'); hideElem('.events.fields');
@ -34,7 +34,7 @@ export function initCompWebHookEditor() {
} }
// Test delivery // 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'); this.classList.add('is-loading', 'disabled');
await POST(this.getAttribute('data-link')); await POST(this.getAttribute('data-link'));
setTimeout(() => { setTimeout(() => {

View File

@ -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);
}
}

View File

@ -27,7 +27,7 @@ function initPreInstall() {
const dbName = document.querySelector<HTMLInputElement>('#db_name'); const dbName = document.querySelector<HTMLInputElement>('#db_name');
// Database type change detection. // Database type change detection.
document.querySelector('#db_type').addEventListener('change', function () { document.querySelector<HTMLInputElement>('#db_type').addEventListener('change', function () {
const dbType = this.value; const dbType = this.value;
hideElem('div[data-db-setting-for]'); hideElem('div[data-db-setting-for]');
showElem(`div[data-db-setting-for=${dbType}]`); showElem(`div[data-db-setting-for=${dbType}]`);
@ -59,26 +59,26 @@ function initPreInstall() {
} }
// TODO: better handling of exclusive relations. // 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) { if (this.checked) {
document.querySelector<HTMLInputElement>('#disable-gravatar input').checked = true; document.querySelector<HTMLInputElement>('#disable-gravatar input').checked = true;
document.querySelector<HTMLInputElement>('#federated-avatar-lookup input').checked = false; 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) { if (this.checked) {
document.querySelector<HTMLInputElement>('#federated-avatar-lookup input').checked = false; document.querySelector<HTMLInputElement>('#federated-avatar-lookup input').checked = false;
} else { } else {
document.querySelector<HTMLInputElement>('#offline-mode input').checked = false; 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) { if (this.checked) {
document.querySelector<HTMLInputElement>('#disable-gravatar input').checked = false; document.querySelector<HTMLInputElement>('#disable-gravatar input').checked = false;
document.querySelector<HTMLInputElement>('#offline-mode 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 (this.checked) {
if (!document.querySelector<HTMLInputElement>('#disable-registration input').checked) { if (!document.querySelector<HTMLInputElement>('#disable-registration input').checked) {
document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = true; 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<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) { if (this.checked) {
document.querySelector<HTMLInputElement>('#enable-captcha input').checked = false; document.querySelector<HTMLInputElement>('#enable-captcha input').checked = false;
document.querySelector<HTMLInputElement>('#enable-openid-signup 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<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) { if (this.checked) {
document.querySelector<HTMLInputElement>('#disable-registration input').checked = false; document.querySelector<HTMLInputElement>('#disable-registration input').checked = false;
} }

View File

@ -38,7 +38,7 @@ export function initViewedCheckboxListenerFor() {
// The checkbox consists of a div containing the real checkbox with its label and the CSRF token, // 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 // 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() { checkbox.addEventListener('input', function() {
// Mark the file as viewed visually - will especially change the background // Mark the file as viewed visually - will especially change the background
if (this.checked) { if (this.checked) {

View File

@ -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);
}

View File

@ -2,7 +2,7 @@ import {createTippy} from '../modules/tippy.ts';
import {toggleElem} from '../utils/dom.ts'; import {toggleElem} from '../utils/dom.ts';
export function initRepoEllipsisButton() { 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) { button.addEventListener('click', function (e) {
e.preventDefault(); e.preventDefault();
const expanded = this.getAttribute('aria-expanded') === 'true'; const expanded = this.getAttribute('aria-expanded') === 'true';

View File

@ -89,7 +89,7 @@ export function initRepoTopicBar() {
url: `${appSubUrl}/explore/topics/search?q={query}`, url: `${appSubUrl}/explore/topics/search?q={query}`,
throttle: 500, throttle: 500,
cache: false, cache: false,
onResponse(res) { onResponse(this: any, res: any) {
const formattedResponse = { const formattedResponse = {
success: false, success: false,
results: [], results: [],

View File

@ -216,7 +216,7 @@ export function initRepoIssueCodeCommentCancel() {
export function initRepoPullRequestUpdate() { export function initRepoPullRequestUpdate() {
// Pull Request update button // Pull Request update button
const pullUpdateButton = document.querySelector('.update-button > button'); const pullUpdateButton = document.querySelector<HTMLButtonElement>('.update-button > button');
if (!pullUpdateButton) return; if (!pullUpdateButton) return;
pullUpdateButton.addEventListener('click', async function (e) { pullUpdateButton.addEventListener('click', async function (e) {

View File

@ -79,21 +79,21 @@ function initRepoSettingsGitHook() {
function initRepoSettingsBranches() { function initRepoSettingsBranches() {
if (!document.querySelector('.repository.settings.branches')) return; 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 () { el.addEventListener('change', function () {
const target = document.querySelector(this.getAttribute('data-target')); const target = document.querySelector(this.getAttribute('data-target'));
target?.classList.toggle('disabled', !this.checked); 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 () { el.addEventListener('change', function () {
const target = document.querySelector(this.getAttribute('data-target')); const target = document.querySelector(this.getAttribute('data-target'));
if (this.checked) target?.classList.add('disabled'); // only disable, do not auto enable 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); document.querySelector('#ignore_stale_approvals_box')?.classList.toggle('disabled', this.checked);
}); });

View File

@ -1,6 +1,6 @@
export function initSshKeyFormParser() { export function initSshKeyFormParser() {
// Parse SSH Key // 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 arrays = this.value.split(' ');
const title = document.querySelector<HTMLInputElement>('#ssh-key-title'); const title = document.querySelector<HTMLInputElement>('#ssh-key-title');
if (!title.value && arrays.length === 3 && arrays[2] !== '') { if (!title.value && arrays.length === 3 && arrays[2] !== '') {

View File

@ -13,7 +13,7 @@ export function initUserSettings() {
initUserSettingsAvatarCropper(); initUserSettingsAvatarCropper();
const usernameInput = document.querySelector('#username'); const usernameInput = document.querySelector<HTMLInputElement>('#username');
if (!usernameInput) return; if (!usernameInput) return;
usernameInput.addEventListener('input', function () { usernameInput.addEventListener('input', function () {
const prompt = document.querySelector('#name-change-prompt'); const prompt = document.querySelector('#name-change-prompt');

View File

@ -2,8 +2,7 @@
import './bootstrap.ts'; import './bootstrap.ts';
import './htmx.ts'; import './htmx.ts';
import {initDashboardRepoList} from './components/DashboardRepoList.vue'; import {initDashboardRepoList} from './features/dashboard.ts';
import {initGlobalCopyToClipboardListener} from './features/clipboard.ts'; import {initGlobalCopyToClipboardListener} from './features/clipboard.ts';
import {initContextPopups} from './features/contextpopup.ts'; import {initContextPopups} from './features/contextpopup.ts';
import {initRepoGraphGit} from './features/repo-graph.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 {initRepository, initBranchSelectorTabs} from './features/repo-legacy.ts';
import {initCopyContent} from './features/copycontent.ts'; import {initCopyContent} from './features/copycontent.ts';
import {initCaptcha} from './features/captcha.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 {initGlobalTooltips} from './modules/tippy.ts';
import {initGiteaFomantic} from './modules/fomantic.ts'; import {initGiteaFomantic} from './modules/fomantic.ts';
import {initSubmitEventPolyfill, onDomReady} from './utils/dom.ts'; import {initSubmitEventPolyfill, onDomReady} from './utils/dom.ts';

View File

@ -3,7 +3,7 @@ import {queryElemChildren} from '../../utils/dom.ts';
export function initFomanticDimmer() { export function initFomanticDimmer() {
// stand-in for removed dimmer module // 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') { if (arg0 === 'add content') {
const $el = arg1; const $el = arg1;
const existingDimmer = document.querySelector('body > .ui.dimmer'); const existingDimmer = document.querySelector('body > .ui.dimmer');

View File

@ -17,7 +17,7 @@ export function initAriaDropdownPatch() {
// the patched `$.fn.dropdown` function, it passes the arguments to Fomantic's `$.fn.dropdown` function, and: // 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 does the one-time attaching on the first call
// * it delegates the `onLabelCreate` to the patched `onLabelCreate` to add necessary aria attributes // * 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); const ret = fomanticDropdownFn.apply(this, args);
// if the `$().dropdown()` call is without arguments, or it has non-string (object) argument, // 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 oldFocusSearch = dropdownCall('internal', 'focusSearch');
const oldBlurSearch = dropdownCall('internal', 'blurSearch'); const oldBlurSearch = dropdownCall('internal', 'blurSearch');
// * If the "dropdown icon" is clicked, Fomantic calls "focusSearch", so show the menu // * 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 // * 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'); const oldFilterItems = dropdownCall('internal', 'filterItems');
dropdownCall('internal', 'filterItems', function (...args: any[]) { dropdownCall('internal', 'filterItems', function (this: any, ...args: any[]) {
oldFilterItems.call(this, ...args); oldFilterItems.call(this, ...args);
processMenuItems($dropdown, dropdownCall); processMenuItems($dropdown, dropdownCall);
}); });
const oldShow = dropdownCall('internal', 'show'); const oldShow = dropdownCall('internal', 'show');
dropdownCall('internal', 'show', function (...args: any[]) { dropdownCall('internal', 'show', function (this: any, ...args: any[]) {
oldShow.call(this, ...args); oldShow.call(this, ...args);
processMenuItems($dropdown, dropdownCall); 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 // the `onLabelCreate` is used to add necessary aria attributes for dynamically created selection labels
const dropdownOnLabelCreateOld = dropdownCall('setting', 'onLabelCreate'); 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); const $label = dropdownOnLabelCreateOld.call(this, value, text);
updateSelectionLabel($label[0]); updateSelectionLabel($label[0]);
return $label; return $label;

View File

@ -12,7 +12,7 @@ export function initAriaModalPatch() {
// the patched `$.fn.modal` modal function // the patched `$.fn.modal` modal function
// * it does the one-time attaching on the first call // * 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); const ret = fomanticModalFn.apply(this, args);
if (args[0] === 'show' || args[0]?.autoShow) { if (args[0] === 'show' || args[0]?.autoShow) {
for (const el of this) { for (const el of this) {

View File

@ -121,7 +121,7 @@ function switchTitleToTooltip(target: Element): void {
* Some browsers like PaleMoon don't support "addEventListener('mouseenter', capture)" * 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 * 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); e.target.removeEventListener('mouseover', lazyTooltipOnMouseHover, true);
attachTooltip(this); attachTooltip(this);
} }

View File

@ -1,4 +1,4 @@
import {h} from 'vue'; import {defineComponent, h, type PropType} from 'vue';
import {parseDom, serializeXml} from './utils.ts'; import {parseDom, serializeXml} from './utils.ts';
import giteaDoubleChevronLeft from '../../public/assets/img/svg/gitea-double-chevron-left.svg'; import giteaDoubleChevronLeft from '../../public/assets/img/svg/gitea-double-chevron-left.svg';
import giteaDoubleChevronRight from '../../public/assets/img/svg/gitea-double-chevron-right.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}; return {svgOuter, svgInnerHtml};
} }
export const SvgIcon = { export const SvgIcon = defineComponent({
name: 'SvgIcon', name: 'SvgIcon',
props: { props: {
name: {type: String, required: true}, name: {type: String as PropType<SvgName>, required: true},
size: {type: Number, default: 16}, size: {type: Number, default: 16},
className: {type: String, default: ''}, className: {type: String, default: ''},
symbolId: {type: String}, symbolId: {type: String},
@ -215,7 +215,7 @@ export const SvgIcon = {
attrs[`^height`] = this.size; attrs[`^height`] = this.size;
// make the <SvgIcon class="foo" class-name="bar"> classes work together // make the <SvgIcon class="foo" class-name="bar"> classes work together
const classes = []; const classes: Array<string> = [];
for (const cls of svgOuter.classList) { for (const cls of svgOuter.classList) {
classes.push(cls); classes.push(cls);
} }
@ -234,4 +234,4 @@ export const SvgIcon = {
innerHTML: svgInnerHtml, innerHTML: svgInnerHtml,
}); });
}, },
}; });