mirror of
https://github.com/go-gitea/gitea
synced 2025-12-06 21:08:25 +00:00
Enable TypeScript strictNullChecks (#35843)
A big step towards enabling strict mode in Typescript. There was definitely a good share of potential bugs while refactoring this. When in doubt, I opted to keep the potentially broken behaviour. Notably, the `DOMEvent` type is gone, it was broken and we're better of with type assertions on `e.target`. --------- Signed-off-by: silverwind <me@silverwind.io> Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: delvh <dev.lh@web.de> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -205,7 +205,7 @@ export default defineConfig([
|
|||||||
'@typescript-eslint/no-non-null-asserted-optional-chain': [2],
|
'@typescript-eslint/no-non-null-asserted-optional-chain': [2],
|
||||||
'@typescript-eslint/no-non-null-assertion': [0],
|
'@typescript-eslint/no-non-null-assertion': [0],
|
||||||
'@typescript-eslint/no-redeclare': [0],
|
'@typescript-eslint/no-redeclare': [0],
|
||||||
'@typescript-eslint/no-redundant-type-constituents': [0], // rule does not properly work without strickNullChecks
|
'@typescript-eslint/no-redundant-type-constituents': [2],
|
||||||
'@typescript-eslint/no-require-imports': [2],
|
'@typescript-eslint/no-require-imports': [2],
|
||||||
'@typescript-eslint/no-restricted-imports': [0],
|
'@typescript-eslint/no-restricted-imports': [0],
|
||||||
'@typescript-eslint/no-restricted-types': [0],
|
'@typescript-eslint/no-restricted-types': [0],
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
"strictBindCallApply": true,
|
"strictBindCallApply": true,
|
||||||
"strictBuiltinIteratorReturn": true,
|
"strictBuiltinIteratorReturn": true,
|
||||||
"strictFunctionTypes": true,
|
"strictFunctionTypes": true,
|
||||||
"strictNullChecks": false,
|
"strictNullChecks": true,
|
||||||
"stripInternal": true,
|
"stripInternal": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"types": [
|
"types": [
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export function showGlobalErrorMessage(msg: string, msgType: Intent = 'error') {
|
|||||||
const msgCount = Number(msgDiv.getAttribute(`data-global-error-msg-count`)) + 1;
|
const msgCount = Number(msgDiv.getAttribute(`data-global-error-msg-count`)) + 1;
|
||||||
msgDiv.setAttribute(`data-global-error-msg-compact`, msgCompact);
|
msgDiv.setAttribute(`data-global-error-msg-compact`, msgCompact);
|
||||||
msgDiv.setAttribute(`data-global-error-msg-count`, msgCount.toString());
|
msgDiv.setAttribute(`data-global-error-msg-count`, msgCount.toString());
|
||||||
msgDiv.querySelector('.ui.message').textContent = msg + (msgCount > 1 ? ` (${msgCount})` : '');
|
msgDiv.querySelector('.ui.message')!.textContent = msg + (msgCount > 1 ? ` (${msgCount})` : '');
|
||||||
msgContainer.prepend(msgDiv);
|
msgContainer.prepend(msgDiv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {onMounted, shallowRef} from 'vue';
|
|||||||
import type {Value as HeatmapValue, Locale as HeatmapLocale} from '@silverwind/vue3-calendar-heatmap';
|
import type {Value as HeatmapValue, Locale as HeatmapLocale} from '@silverwind/vue3-calendar-heatmap';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
values?: HeatmapValue[];
|
values: HeatmapValue[];
|
||||||
locale: {
|
locale: {
|
||||||
textTotalContributions: string;
|
textTotalContributions: string;
|
||||||
heatMapLocale: Partial<HeatmapLocale>;
|
heatMapLocale: Partial<HeatmapLocale>;
|
||||||
@@ -28,7 +28,7 @@ const endDate = shallowRef(new Date());
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// work around issue with first legend color being rendered twice and legend cut off
|
// work around issue with first legend color being rendered twice and legend cut off
|
||||||
const legend = document.querySelector<HTMLElement>('.vch__external-legend-wrapper');
|
const legend = document.querySelector<HTMLElement>('.vch__external-legend-wrapper')!;
|
||||||
legend.setAttribute('viewBox', '12 0 80 10');
|
legend.setAttribute('viewBox', '12 0 80 10');
|
||||||
legend.style.marginRight = '-12px';
|
legend.style.marginRight = '-12px';
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,15 +11,17 @@ const props = defineProps<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const loading = shallowRef(false);
|
const loading = shallowRef(false);
|
||||||
const issue = shallowRef<Issue>(null);
|
const issue = shallowRef<Issue | null>(null);
|
||||||
const renderedLabels = shallowRef('');
|
const renderedLabels = shallowRef('');
|
||||||
const errorMessage = shallowRef('');
|
const errorMessage = shallowRef('');
|
||||||
|
|
||||||
const createdAt = computed(() => {
|
const createdAt = computed(() => {
|
||||||
|
if (!issue?.value) return '';
|
||||||
return new Date(issue.value.created_at).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'});
|
return new Date(issue.value.created_at).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'});
|
||||||
});
|
});
|
||||||
|
|
||||||
const body = computed(() => {
|
const body = computed(() => {
|
||||||
|
if (!issue?.value) return '';
|
||||||
const body = issue.value.body.replace(/\n+/g, ' ');
|
const body = issue.value.body.replace(/\n+/g, ' ');
|
||||||
return body.length > 85 ? `${body.substring(0, 85)}…` : body;
|
return body.length > 85 ? `${body.substring(0, 85)}…` : body;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -110,9 +110,9 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
const el = document.querySelector('#dashboard-repo-list');
|
const el = document.querySelector('#dashboard-repo-list')!;
|
||||||
this.changeReposFilter(this.reposFilter);
|
this.changeReposFilter(this.reposFilter);
|
||||||
fomanticQuery(el.querySelector('.ui.dropdown')).dropdown();
|
fomanticQuery(el.querySelector('.ui.dropdown')!).dropdown();
|
||||||
|
|
||||||
this.textArchivedFilterTitles = {
|
this.textArchivedFilterTitles = {
|
||||||
'archived': this.textShowOnlyArchived,
|
'archived': this.textShowOnlyArchived,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type CommitListResult = {
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {SvgIcon},
|
components: {SvgIcon},
|
||||||
data: () => {
|
data: () => {
|
||||||
const el = document.querySelector('#diff-commit-select');
|
const el = document.querySelector('#diff-commit-select')!;
|
||||||
return {
|
return {
|
||||||
menuVisible: false,
|
menuVisible: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
@@ -35,7 +35,7 @@ export default defineComponent({
|
|||||||
mergeBase: el.getAttribute('data-merge-base'),
|
mergeBase: el.getAttribute('data-merge-base'),
|
||||||
commits: [] as Array<Commit>,
|
commits: [] as Array<Commit>,
|
||||||
hoverActivated: false,
|
hoverActivated: false,
|
||||||
lastReviewCommitSha: '',
|
lastReviewCommitSha: '' as string | null,
|
||||||
uniqueIdMenu: generateElemId('diff-commit-selector-menu-'),
|
uniqueIdMenu: generateElemId('diff-commit-selector-menu-'),
|
||||||
uniqueIdShowAll: generateElemId('diff-commit-selector-show-all-'),
|
uniqueIdShowAll: generateElemId('diff-commit-selector-show-all-'),
|
||||||
};
|
};
|
||||||
@@ -165,7 +165,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
/** Called when user clicks on since last review */
|
/** Called when user clicks on since last review */
|
||||||
changesSinceLastReviewClick() {
|
changesSinceLastReviewClick() {
|
||||||
window.location.assign(`${this.issueLink}/files/${this.lastReviewCommitSha}..${this.commits.at(-1).id}${this.queryParams}`);
|
window.location.assign(`${this.issueLink}/files/${this.lastReviewCommitSha}..${this.commits.at(-1)!.id}${this.queryParams}`);
|
||||||
},
|
},
|
||||||
/** Clicking on a single commit opens this specific commit */
|
/** Clicking on a single commit opens this specific commit */
|
||||||
commitClicked(commitId: string, newWindow = false) {
|
commitClicked(commitId: string, newWindow = false) {
|
||||||
@@ -193,7 +193,7 @@ export default defineComponent({
|
|||||||
// find all selected commits and generate a link
|
// find all selected commits and generate a link
|
||||||
const firstSelected = this.commits.findIndex((x) => x.selected);
|
const firstSelected = this.commits.findIndex((x) => x.selected);
|
||||||
const lastSelected = this.commits.findLastIndex((x) => x.selected);
|
const lastSelected = this.commits.findLastIndex((x) => x.selected);
|
||||||
let beforeCommitID: string;
|
let beforeCommitID: string | null = null;
|
||||||
if (firstSelected === 0) {
|
if (firstSelected === 0) {
|
||||||
beforeCommitID = this.mergeBase;
|
beforeCommitID = this.mergeBase;
|
||||||
} else {
|
} else {
|
||||||
@@ -204,7 +204,7 @@ export default defineComponent({
|
|||||||
if (firstSelected === lastSelected) {
|
if (firstSelected === lastSelected) {
|
||||||
// if the start and end are the same, we show this single commit
|
// if the start and end are the same, we show this single commit
|
||||||
window.location.assign(`${this.issueLink}/commits/${afterCommitID}${this.queryParams}`);
|
window.location.assign(`${this.issueLink}/commits/${afterCommitID}${this.queryParams}`);
|
||||||
} else if (beforeCommitID === this.mergeBase && afterCommitID === this.commits.at(-1).id) {
|
} else if (beforeCommitID === this.mergeBase && afterCommitID === this.commits.at(-1)!.id) {
|
||||||
// if the first commit is selected and the last commit is selected, we show all commits
|
// if the first commit is selected and the last commit is selected, we show all commits
|
||||||
window.location.assign(`${this.issueLink}/files${this.queryParams}`);
|
window.location.assign(`${this.issueLink}/files${this.queryParams}`);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ const store = diffTreeStore();
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// Default to true if unset
|
// Default to true if unset
|
||||||
store.fileTreeIsVisible = localStorage.getItem(LOCAL_STORAGE_KEY) !== 'false';
|
store.fileTreeIsVisible = localStorage.getItem(LOCAL_STORAGE_KEY) !== 'false';
|
||||||
document.querySelector('.diff-toggle-file-tree-button').addEventListener('click', toggleVisibility);
|
document.querySelector('.diff-toggle-file-tree-button')!.addEventListener('click', toggleVisibility);
|
||||||
|
|
||||||
hashChangeListener();
|
hashChangeListener();
|
||||||
window.addEventListener('hashchange', hashChangeListener);
|
window.addEventListener('hashchange', hashChangeListener);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.querySelector('.diff-toggle-file-tree-button').removeEventListener('click', toggleVisibility);
|
document.querySelector('.diff-toggle-file-tree-button')!.removeEventListener('click', toggleVisibility);
|
||||||
window.removeEventListener('hashchange', hashChangeListener);
|
window.removeEventListener('hashchange', hashChangeListener);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ function expandSelectedFile() {
|
|||||||
if (store.selectedItem) {
|
if (store.selectedItem) {
|
||||||
const box = document.querySelector(store.selectedItem);
|
const box = document.querySelector(store.selectedItem);
|
||||||
const folded = box?.getAttribute('data-folded') === 'true';
|
const folded = box?.getAttribute('data-folded') === 'true';
|
||||||
if (folded) setFileFolding(box, box.querySelector('.fold-file'), false);
|
if (folded) setFileFolding(box, box.querySelector('.fold-file')!, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,10 +48,10 @@ function updateVisibility(visible: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateState(visible: boolean) {
|
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')!;
|
||||||
const newTooltip = btn.getAttribute(visible ? 'data-hide-text' : 'data-show-text');
|
const newTooltip = btn.getAttribute(visible ? 'data-hide-text' : 'data-show-text')!;
|
||||||
btn.setAttribute('data-tooltip-content', newTooltip);
|
btn.setAttribute('data-tooltip-content', newTooltip);
|
||||||
toggleElem(tree, visible);
|
toggleElem(tree, visible);
|
||||||
toggleElem(toShow, !visible);
|
toggleElem(toShow, !visible);
|
||||||
|
|||||||
@@ -402,7 +402,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// auto-scroll to the last log line of the last step
|
// auto-scroll to the last log line of the last step
|
||||||
let autoScrollJobStepElement: HTMLElement;
|
let autoScrollJobStepElement: HTMLElement | undefined;
|
||||||
for (let stepIndex = 0; stepIndex < this.currentJob.steps.length; stepIndex++) {
|
for (let stepIndex = 0; stepIndex < this.currentJob.steps.length; stepIndex++) {
|
||||||
if (!autoScrollStepIndexes.get(stepIndex)) continue;
|
if (!autoScrollStepIndexes.get(stepIndex)) continue;
|
||||||
autoScrollJobStepElement = this.getJobStepLogsContainer(stepIndex);
|
autoScrollJobStepElement = this.getJobStepLogsContainer(stepIndex);
|
||||||
@@ -468,7 +468,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
const logLine = this.elStepsContainer().querySelector(selectedLogStep);
|
const logLine = this.elStepsContainer().querySelector(selectedLogStep);
|
||||||
if (!logLine) return;
|
if (!logLine) return;
|
||||||
logLine.querySelector<HTMLAnchorElement>('.line-num').click();
|
logLine.querySelector<HTMLAnchorElement>('.line-num')!.click();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// @ts-expect-error - module exports no types
|
// @ts-expect-error - module exports no types
|
||||||
import {VueBarGraph} from 'vue-bar-graph';
|
import {VueBarGraph} from 'vue-bar-graph';
|
||||||
import {computed, onMounted, shallowRef, useTemplateRef} from 'vue';
|
import {computed, onMounted, shallowRef, useTemplateRef, type ShallowRef} from 'vue';
|
||||||
|
|
||||||
const colors = shallowRef({
|
const colors = shallowRef({
|
||||||
barColor: 'green',
|
barColor: 'green',
|
||||||
@@ -41,8 +41,8 @@ const graphWidth = computed(() => {
|
|||||||
return activityTopAuthors.length * 40;
|
return activityTopAuthors.length * 40;
|
||||||
});
|
});
|
||||||
|
|
||||||
const styleElement = useTemplateRef('styleElement');
|
const styleElement = useTemplateRef('styleElement') as Readonly<ShallowRef<HTMLDivElement>>;
|
||||||
const altStyleElement = useTemplateRef('altStyleElement');
|
const altStyleElement = useTemplateRef('altStyleElement') as Readonly<ShallowRef<HTMLDivElement>>;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const refStyle = window.getComputedStyle(styleElement.value);
|
const refStyle = window.getComputedStyle(styleElement.value);
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ type TabLoadingStates = Record<SelectedTab, '' | 'loading' | 'done'>
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {SvgIcon},
|
components: {SvgIcon},
|
||||||
props: {
|
props: {
|
||||||
elRoot: HTMLElement,
|
elRoot: {
|
||||||
|
type: HTMLElement,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
const shouldShowTabBranches = this.elRoot.getAttribute('data-show-tab-branches') === 'true';
|
const shouldShowTabBranches = this.elRoot.getAttribute('data-show-tab-branches') === 'true';
|
||||||
@@ -33,28 +36,28 @@ export default defineComponent({
|
|||||||
activeItemIndex: 0,
|
activeItemIndex: 0,
|
||||||
tabLoadingStates: {} as TabLoadingStates,
|
tabLoadingStates: {} as TabLoadingStates,
|
||||||
|
|
||||||
textReleaseCompare: this.elRoot.getAttribute('data-text-release-compare'),
|
textReleaseCompare: this.elRoot.getAttribute('data-text-release-compare')!,
|
||||||
textBranches: this.elRoot.getAttribute('data-text-branches'),
|
textBranches: this.elRoot.getAttribute('data-text-branches')!,
|
||||||
textTags: this.elRoot.getAttribute('data-text-tags'),
|
textTags: this.elRoot.getAttribute('data-text-tags')!,
|
||||||
textFilterBranch: this.elRoot.getAttribute('data-text-filter-branch'),
|
textFilterBranch: this.elRoot.getAttribute('data-text-filter-branch')!,
|
||||||
textFilterTag: this.elRoot.getAttribute('data-text-filter-tag'),
|
textFilterTag: this.elRoot.getAttribute('data-text-filter-tag')!,
|
||||||
textDefaultBranchLabel: this.elRoot.getAttribute('data-text-default-branch-label'),
|
textDefaultBranchLabel: this.elRoot.getAttribute('data-text-default-branch-label')!,
|
||||||
textCreateTag: this.elRoot.getAttribute('data-text-create-tag'),
|
textCreateTag: this.elRoot.getAttribute('data-text-create-tag')!,
|
||||||
textCreateBranch: this.elRoot.getAttribute('data-text-create-branch'),
|
textCreateBranch: this.elRoot.getAttribute('data-text-create-branch')!,
|
||||||
textCreateRefFrom: this.elRoot.getAttribute('data-text-create-ref-from'),
|
textCreateRefFrom: this.elRoot.getAttribute('data-text-create-ref-from')!,
|
||||||
textNoResults: this.elRoot.getAttribute('data-text-no-results'),
|
textNoResults: this.elRoot.getAttribute('data-text-no-results')!,
|
||||||
textViewAllBranches: this.elRoot.getAttribute('data-text-view-all-branches'),
|
textViewAllBranches: this.elRoot.getAttribute('data-text-view-all-branches')!,
|
||||||
textViewAllTags: this.elRoot.getAttribute('data-text-view-all-tags'),
|
textViewAllTags: this.elRoot.getAttribute('data-text-view-all-tags')!,
|
||||||
|
|
||||||
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') as GitRefType,
|
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')!,
|
||||||
refFormActionTemplate: this.elRoot.getAttribute('data-ref-form-action-template'),
|
refFormActionTemplate: this.elRoot.getAttribute('data-ref-form-action-template')!,
|
||||||
dropdownFixedText: this.elRoot.getAttribute('data-dropdown-fixed-text'),
|
dropdownFixedText: this.elRoot.getAttribute('data-dropdown-fixed-text')!,
|
||||||
showTabBranches: shouldShowTabBranches,
|
showTabBranches: shouldShowTabBranches,
|
||||||
showTabTags: this.elRoot.getAttribute('data-show-tab-tags') === 'true',
|
showTabTags: this.elRoot.getAttribute('data-show-tab-tags') === 'true',
|
||||||
allowCreateNewRef: this.elRoot.getAttribute('data-allow-create-new-ref') === 'true',
|
allowCreateNewRef: this.elRoot.getAttribute('data-allow-create-new-ref') === 'true',
|
||||||
@@ -92,7 +95,7 @@ export default defineComponent({
|
|||||||
}).length;
|
}).length;
|
||||||
},
|
},
|
||||||
createNewRefFormActionUrl() {
|
createNewRefFormActionUrl() {
|
||||||
return `${this.currentRepoLink}/branches/_new/${this.currentRefType}/${pathEscapeSegments(this.currentRefShortName)}`;
|
return `${this.currentRepoLink}/branches/_new/${this.currentRefType}/${pathEscapeSegments(this.currentRefShortName!)}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ export default defineComponent({
|
|||||||
user.max_contribution_type = 0;
|
user.max_contribution_type = 0;
|
||||||
const filteredWeeks = user.weeks.filter((week: Record<string, number>) => {
|
const filteredWeeks = user.weeks.filter((week: Record<string, number>) => {
|
||||||
const oneWeek = 7 * 24 * 60 * 60 * 1000;
|
const oneWeek = 7 * 24 * 60 * 60 * 1000;
|
||||||
if (week.week >= this.xAxisMin - oneWeek && week.week <= this.xAxisMax + oneWeek) {
|
if (week.week >= this.xAxisMin! - oneWeek && week.week <= this.xAxisMax! + oneWeek) {
|
||||||
user.total_commits += week.commits;
|
user.total_commits += week.commits;
|
||||||
user.total_additions += week.additions;
|
user.total_additions += week.additions;
|
||||||
user.total_deletions += week.deletions;
|
user.total_deletions += week.deletions;
|
||||||
@@ -238,8 +238,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
updateOtherCharts({chart}: {chart: Chart}, reset: boolean = false) {
|
updateOtherCharts({chart}: {chart: Chart}, reset: boolean = false) {
|
||||||
const minVal = Number(chart.options.scales.x.min);
|
const minVal = Number(chart.options.scales?.x?.min);
|
||||||
const maxVal = Number(chart.options.scales.x.max);
|
const maxVal = Number(chart.options.scales?.x?.max);
|
||||||
if (reset) {
|
if (reset) {
|
||||||
this.xAxisMin = this.xAxisStart;
|
this.xAxisMin = this.xAxisStart;
|
||||||
this.xAxisMax = this.xAxisEnd;
|
this.xAxisMax = this.xAxisEnd;
|
||||||
@@ -302,8 +302,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
min: this.xAxisMin,
|
min: this.xAxisMin ?? undefined,
|
||||||
max: this.xAxisMax,
|
max: this.xAxisMax ?? undefined,
|
||||||
type: 'time',
|
type: 'time',
|
||||||
grid: {
|
grid: {
|
||||||
display: false,
|
display: false,
|
||||||
@@ -334,7 +334,7 @@ export default defineComponent({
|
|||||||
<div class="ui header tw-flex tw-items-center tw-justify-between">
|
<div class="ui header tw-flex tw-items-center tw-justify-between">
|
||||||
<div>
|
<div>
|
||||||
<relative-time
|
<relative-time
|
||||||
v-if="xAxisMin > 0"
|
v-if="xAxisMin && xAxisMin > 0"
|
||||||
format="datetime"
|
format="datetime"
|
||||||
year="numeric"
|
year="numeric"
|
||||||
month="short"
|
month="short"
|
||||||
@@ -346,7 +346,7 @@ export default defineComponent({
|
|||||||
</relative-time>
|
</relative-time>
|
||||||
{{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: "-" }}
|
{{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: "-" }}
|
||||||
<relative-time
|
<relative-time
|
||||||
v-if="xAxisMax > 0"
|
v-if="xAxisMax && xAxisMax > 0"
|
||||||
format="datetime"
|
format="datetime"
|
||||||
year="numeric"
|
year="numeric"
|
||||||
month="short"
|
month="short"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, watch, nextTick, useTemplateRef, onMounted, onUnmounted } from 'vue';
|
import {ref, computed, watch, nextTick, useTemplateRef, onMounted, onUnmounted, type ShallowRef} from 'vue';
|
||||||
import {generateElemId} from '../utils/dom.ts';
|
import {generateElemId} from '../utils/dom.ts';
|
||||||
import {GET} from '../modules/fetch.ts';
|
import {GET} from '../modules/fetch.ts';
|
||||||
import {filterRepoFilesWeighted} from '../features/repo-findfile.ts';
|
import {filterRepoFilesWeighted} from '../features/repo-findfile.ts';
|
||||||
@@ -15,8 +15,8 @@ const props = defineProps({
|
|||||||
placeholder: { type: String, required: true },
|
placeholder: { type: String, required: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const refElemInput = useTemplateRef<HTMLInputElement>('searchInput');
|
const refElemInput = useTemplateRef('searchInput') as Readonly<ShallowRef<HTMLInputElement>>;
|
||||||
const refElemPopup = useTemplateRef<HTMLElement>('searchPopup');
|
const refElemPopup = useTemplateRef('searchPopup') as Readonly<ShallowRef<HTMLDivElement>>;
|
||||||
|
|
||||||
const searchQuery = ref('');
|
const searchQuery = ref('');
|
||||||
const allFiles = ref<string[]>([]);
|
const allFiles = ref<string[]>([]);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ViewFileTreeItem from './ViewFileTreeItem.vue';
|
import ViewFileTreeItem from './ViewFileTreeItem.vue';
|
||||||
import {onMounted, useTemplateRef} from 'vue';
|
import {onMounted, useTemplateRef, type ShallowRef} from 'vue';
|
||||||
import {createViewFileTreeStore} from './ViewFileTreeStore.ts';
|
import {createViewFileTreeStore} from './ViewFileTreeStore.ts';
|
||||||
|
|
||||||
const elRoot = useTemplateRef('elRoot');
|
const elRoot = useTemplateRef('elRoot') as Readonly<ShallowRef<HTMLDivElement>>;;
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
repoLink: {type: String, required: true},
|
repoLink: {type: String, required: true},
|
||||||
@@ -24,7 +24,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="view-file-tree-items" ref="elRoot">
|
<div class="view-file-tree-items" ref="elRoot">
|
||||||
<ViewFileTreeItem v-for="item in store.rootFiles" :key="item.name" :item="item" :store="store"/>
|
<ViewFileTreeItem v-for="item in store.rootFiles" :key="item.entryName" :item="item" :store="store"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {isPlainClick} from '../utils/dom.ts';
|
|||||||
import {shallowRef} from 'vue';
|
import {shallowRef} from 'vue';
|
||||||
import {type createViewFileTreeStore} from './ViewFileTreeStore.ts';
|
import {type createViewFileTreeStore} from './ViewFileTreeStore.ts';
|
||||||
|
|
||||||
type Item = {
|
export type Item = {
|
||||||
entryName: string;
|
entryName: string;
|
||||||
entryMode: 'blob' | 'exec' | 'tree' | 'commit' | 'symlink' | 'unknown';
|
entryMode: 'blob' | 'exec' | 'tree' | 'commit' | 'symlink' | 'unknown';
|
||||||
entryIcon: string;
|
entryIcon: string;
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import {GET} from '../modules/fetch.ts';
|
|||||||
import {pathEscapeSegments} from '../utils/url.ts';
|
import {pathEscapeSegments} from '../utils/url.ts';
|
||||||
import {createElementFromHTML} from '../utils/dom.ts';
|
import {createElementFromHTML} from '../utils/dom.ts';
|
||||||
import {html} from '../utils/html.ts';
|
import {html} from '../utils/html.ts';
|
||||||
|
import type {Item} from './ViewFileTreeItem.vue';
|
||||||
|
|
||||||
export function createViewFileTreeStore(props: {repoLink: string, treePath: string, currentRefNameSubURL: string}) {
|
export function createViewFileTreeStore(props: {repoLink: string, treePath: string, currentRefNameSubURL: string}) {
|
||||||
const store = reactive({
|
const store = reactive({
|
||||||
rootFiles: [],
|
rootFiles: [] as Array<Item>,
|
||||||
selectedItem: props.treePath,
|
selectedItem: props.treePath,
|
||||||
|
|
||||||
async loadChildren(treePath: string, subPath: string = '') {
|
async loadChildren(treePath: string, subPath: string = '') {
|
||||||
@@ -28,7 +29,7 @@ export function createViewFileTreeStore(props: {repoLink: string, treePath: stri
|
|||||||
const u = new URL(url, window.origin);
|
const u = new URL(url, window.origin);
|
||||||
u.searchParams.set('only_content', 'true');
|
u.searchParams.set('only_content', 'true');
|
||||||
const response = await GET(u.href);
|
const response = await GET(u.href);
|
||||||
const elViewContent = document.querySelector('.repo-view-content');
|
const elViewContent = document.querySelector('.repo-view-content')!;
|
||||||
elViewContent.innerHTML = await response.text();
|
elViewContent.innerHTML = await response.text();
|
||||||
const elViewContentData = elViewContent.querySelector('.repo-view-content-data');
|
const elViewContentData = elViewContent.querySelector('.repo-view-content-data');
|
||||||
if (!elViewContentData) return; // if error occurs, there is no such element
|
if (!elViewContentData) return; // if error occurs, there is no such element
|
||||||
@@ -39,7 +40,7 @@ export function createViewFileTreeStore(props: {repoLink: string, treePath: stri
|
|||||||
|
|
||||||
async navigateTreeView(treePath: string) {
|
async navigateTreeView(treePath: string) {
|
||||||
const url = store.buildTreePathWebUrl(treePath);
|
const url = store.buildTreePathWebUrl(treePath);
|
||||||
window.history.pushState({treePath, url}, null, url);
|
window.history.pushState({treePath, url}, '', url);
|
||||||
store.selectedItem = treePath;
|
store.selectedItem = treePath;
|
||||||
await store.loadViewContent(url);
|
await store.loadViewContent(url);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ function initAdminAuthentication() {
|
|||||||
|
|
||||||
function onUsePagedSearchChange() {
|
function onUsePagedSearchChange() {
|
||||||
const searchPageSizeElements = document.querySelectorAll<HTMLDivElement>('.search-page-size');
|
const searchPageSizeElements = document.querySelectorAll<HTMLDivElement>('.search-page-size');
|
||||||
if (document.querySelector<HTMLInputElement>('#use_paged_search').checked) {
|
if (document.querySelector<HTMLInputElement>('#use_paged_search')!.checked) {
|
||||||
showElem('.search-page-size');
|
showElem('.search-page-size');
|
||||||
for (const el of searchPageSizeElements) {
|
for (const el of searchPageSizeElements) {
|
||||||
el.querySelector('input')?.setAttribute('required', 'required');
|
el.querySelector('input')?.setAttribute('required', 'required');
|
||||||
@@ -82,10 +82,10 @@ function initAdminAuthentication() {
|
|||||||
input.removeAttribute('required');
|
input.removeAttribute('required');
|
||||||
}
|
}
|
||||||
|
|
||||||
const provider = document.querySelector<HTMLInputElement>('#oauth2_provider').value;
|
const provider = document.querySelector<HTMLInputElement>('#oauth2_provider')!.value;
|
||||||
switch (provider) {
|
switch (provider) {
|
||||||
case 'openidConnect':
|
case 'openidConnect':
|
||||||
document.querySelector<HTMLInputElement>('.open_id_connect_auto_discovery_url input').setAttribute('required', 'required');
|
document.querySelector<HTMLInputElement>('.open_id_connect_auto_discovery_url input')!.setAttribute('required', 'required');
|
||||||
showElem('.open_id_connect_auto_discovery_url');
|
showElem('.open_id_connect_auto_discovery_url');
|
||||||
break;
|
break;
|
||||||
default: {
|
default: {
|
||||||
@@ -97,7 +97,7 @@ function initAdminAuthentication() {
|
|||||||
showElem('.oauth2_use_custom_url'); // show the checkbox
|
showElem('.oauth2_use_custom_url'); // show the checkbox
|
||||||
}
|
}
|
||||||
if (mustProvideCustomURLs) {
|
if (mustProvideCustomURLs) {
|
||||||
document.querySelector<HTMLInputElement>('#oauth2_use_custom_url').checked = true; // make the checkbox checked
|
document.querySelector<HTMLInputElement>('#oauth2_use_custom_url')!.checked = true; // make the checkbox checked
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -109,17 +109,17 @@ function initAdminAuthentication() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onOAuth2UseCustomURLChange(applyDefaultValues: boolean) {
|
function onOAuth2UseCustomURLChange(applyDefaultValues: boolean) {
|
||||||
const provider = document.querySelector<HTMLInputElement>('#oauth2_provider').value;
|
const provider = document.querySelector<HTMLInputElement>('#oauth2_provider')!.value;
|
||||||
hideElem('.oauth2_use_custom_url_field');
|
hideElem('.oauth2_use_custom_url_field');
|
||||||
for (const input of document.querySelectorAll<HTMLInputElement>('.oauth2_use_custom_url_field input[required]')) {
|
for (const input of document.querySelectorAll<HTMLInputElement>('.oauth2_use_custom_url_field input[required]')) {
|
||||||
input.removeAttribute('required');
|
input.removeAttribute('required');
|
||||||
}
|
}
|
||||||
|
|
||||||
const elProviderCustomUrlSettings = document.querySelector(`#${provider}_customURLSettings`);
|
const elProviderCustomUrlSettings = document.querySelector(`#${provider}_customURLSettings`);
|
||||||
if (elProviderCustomUrlSettings && document.querySelector<HTMLInputElement>('#oauth2_use_custom_url').checked) {
|
if (elProviderCustomUrlSettings && document.querySelector<HTMLInputElement>('#oauth2_use_custom_url')!.checked) {
|
||||||
for (const custom of ['token_url', 'auth_url', 'profile_url', 'email_url', 'tenant']) {
|
for (const custom of ['token_url', 'auth_url', 'profile_url', 'email_url', 'tenant']) {
|
||||||
if (applyDefaultValues) {
|
if (applyDefaultValues) {
|
||||||
document.querySelector<HTMLInputElement>(`#oauth2_${custom}`).value = document.querySelector<HTMLInputElement>(`#${provider}_${custom}`).value;
|
document.querySelector<HTMLInputElement>(`#oauth2_${custom}`)!.value = document.querySelector<HTMLInputElement>(`#${provider}_${custom}`)!.value;
|
||||||
}
|
}
|
||||||
const customInput = document.querySelector(`#${provider}_${custom}`);
|
const customInput = document.querySelector(`#${provider}_${custom}`);
|
||||||
if (customInput && customInput.getAttribute('data-available') === 'true') {
|
if (customInput && customInput.getAttribute('data-available') === 'true') {
|
||||||
@@ -134,10 +134,10 @@ function initAdminAuthentication() {
|
|||||||
|
|
||||||
function onEnableLdapGroupsChange() {
|
function onEnableLdapGroupsChange() {
|
||||||
const checked = document.querySelector<HTMLInputElement>('.js-ldap-group-toggle')?.checked;
|
const checked = document.querySelector<HTMLInputElement>('.js-ldap-group-toggle')?.checked;
|
||||||
toggleElem(document.querySelector('#ldap-group-options'), checked);
|
toggleElem(document.querySelector('#ldap-group-options')!, checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
const elAuthType = document.querySelector<HTMLInputElement>('#auth_type');
|
const elAuthType = document.querySelector<HTMLInputElement>('#auth_type')!;
|
||||||
|
|
||||||
// New authentication
|
// New authentication
|
||||||
if (isNewPage) {
|
if (isNewPage) {
|
||||||
@@ -208,14 +208,14 @@ function initAdminAuthentication() {
|
|||||||
document.querySelector<HTMLInputElement>('#oauth2_provider')?.addEventListener('change', () => onOAuth2Change(true));
|
document.querySelector<HTMLInputElement>('#oauth2_provider')?.addEventListener('change', () => onOAuth2Change(true));
|
||||||
document.querySelector<HTMLInputElement>('#oauth2_use_custom_url')?.addEventListener('change', () => onOAuth2UseCustomURLChange(true));
|
document.querySelector<HTMLInputElement>('#oauth2_use_custom_url')?.addEventListener('change', () => onOAuth2UseCustomURLChange(true));
|
||||||
|
|
||||||
document.querySelector('.js-ldap-group-toggle').addEventListener('change', onEnableLdapGroupsChange);
|
document.querySelector('.js-ldap-group-toggle')!.addEventListener('change', onEnableLdapGroupsChange);
|
||||||
}
|
}
|
||||||
// Edit authentication
|
// Edit authentication
|
||||||
if (isEditPage) {
|
if (isEditPage) {
|
||||||
const authType = elAuthType.value;
|
const authType = elAuthType.value;
|
||||||
if (authType === '2' || authType === '5') {
|
if (authType === '2' || authType === '5') {
|
||||||
document.querySelector<HTMLInputElement>('#security_protocol')?.addEventListener('change', onSecurityProtocolChange);
|
document.querySelector<HTMLInputElement>('#security_protocol')?.addEventListener('change', onSecurityProtocolChange);
|
||||||
document.querySelector('.js-ldap-group-toggle').addEventListener('change', onEnableLdapGroupsChange);
|
document.querySelector('.js-ldap-group-toggle')!.addEventListener('change', onEnableLdapGroupsChange);
|
||||||
onEnableLdapGroupsChange();
|
onEnableLdapGroupsChange();
|
||||||
if (authType === '2') {
|
if (authType === '2') {
|
||||||
document.querySelector<HTMLInputElement>('#use_paged_search')?.addEventListener('change', onUsePagedSearchChange);
|
document.querySelector<HTMLInputElement>('#use_paged_search')?.addEventListener('change', onUsePagedSearchChange);
|
||||||
@@ -227,10 +227,10 @@ function initAdminAuthentication() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const elAuthName = document.querySelector<HTMLInputElement>('#auth_name');
|
const elAuthName = document.querySelector<HTMLInputElement>('#auth_name')!;
|
||||||
const onAuthNameChange = function () {
|
const onAuthNameChange = function () {
|
||||||
// appSubUrl is either empty or is a path that starts with `/` and doesn't have a trailing slash.
|
// appSubUrl is either empty or is a path that starts with `/` and doesn't have a trailing slash.
|
||||||
document.querySelector('#oauth2-callback-url').textContent = `${window.location.origin}${appSubUrl}/user/oauth2/${encodeURIComponent(elAuthName.value)}/callback`;
|
document.querySelector('#oauth2-callback-url')!.textContent = `${window.location.origin}${appSubUrl}/user/oauth2/${encodeURIComponent(elAuthName.value)}/callback`;
|
||||||
};
|
};
|
||||||
elAuthName.addEventListener('input', onAuthNameChange);
|
elAuthName.addEventListener('input', onAuthNameChange);
|
||||||
onAuthNameChange();
|
onAuthNameChange();
|
||||||
@@ -240,13 +240,13 @@ function initAdminNotice() {
|
|||||||
const pageContent = document.querySelector('.page-content.admin.notice');
|
const pageContent = document.querySelector('.page-content.admin.notice');
|
||||||
if (!pageContent) return;
|
if (!pageContent) return;
|
||||||
|
|
||||||
const detailModal = document.querySelector<HTMLDivElement>('#detail-modal');
|
const detailModal = document.querySelector<HTMLDivElement>('#detail-modal')!;
|
||||||
|
|
||||||
// Attach view detail modals
|
// Attach view detail modals
|
||||||
queryElems(pageContent, '.view-detail', (el) => el.addEventListener('click', (e) => {
|
queryElems(pageContent, '.view-detail', (el) => el.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const elNoticeDesc = el.closest('tr').querySelector('.notice-description');
|
const elNoticeDesc = el.closest('tr')!.querySelector('.notice-description')!;
|
||||||
const elModalDesc = detailModal.querySelector('.content pre');
|
const elModalDesc = detailModal.querySelector('.content pre')!;
|
||||||
elModalDesc.textContent = elNoticeDesc.textContent;
|
elModalDesc.textContent = elNoticeDesc.textContent;
|
||||||
fomanticQuery(detailModal).modal('show');
|
fomanticQuery(detailModal).modal('show');
|
||||||
}));
|
}));
|
||||||
@@ -280,10 +280,10 @@ function initAdminNotice() {
|
|||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
for (const checkbox of checkboxes) {
|
for (const checkbox of checkboxes) {
|
||||||
if (checkbox.checked) {
|
if (checkbox.checked) {
|
||||||
data.append('ids[]', checkbox.closest('.ui.checkbox').getAttribute('data-id'));
|
data.append('ids[]', checkbox.closest('.ui.checkbox')!.getAttribute('data-id')!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await POST(this.getAttribute('data-link'), {data});
|
await POST(this.getAttribute('data-link')!, {data});
|
||||||
window.location.href = this.getAttribute('data-redirect');
|
window.location.href = this.getAttribute('data-redirect')!;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function initAdminConfigs(): void {
|
|||||||
el.addEventListener('change', async () => {
|
el.addEventListener('change', async () => {
|
||||||
try {
|
try {
|
||||||
const resp = await POST(`${appSubUrl}/-/admin/config`, {
|
const resp = await POST(`${appSubUrl}/-/admin/config`, {
|
||||||
data: new URLSearchParams({key: el.getAttribute('data-config-dyn-key'), value: String(el.checked)}),
|
data: new URLSearchParams({key: el.getAttribute('data-config-dyn-key')!, value: String(el.checked)}),
|
||||||
});
|
});
|
||||||
const json: Record<string, any> = await resp.json();
|
const json: Record<string, any> = await resp.json();
|
||||||
if (json.errorMessage) throw new Error(json.errorMessage);
|
if (json.errorMessage) throw new Error(json.errorMessage);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export async function initAdminSelfCheck() {
|
|||||||
const elCheckByFrontend = document.querySelector('#self-check-by-frontend');
|
const elCheckByFrontend = document.querySelector('#self-check-by-frontend');
|
||||||
if (!elCheckByFrontend) return;
|
if (!elCheckByFrontend) return;
|
||||||
|
|
||||||
const elContent = document.querySelector<HTMLDivElement>('.page-content.admin .admin-setting-content');
|
const elContent = document.querySelector<HTMLDivElement>('.page-content.admin .admin-setting-content')!;
|
||||||
|
|
||||||
// send frontend self-check request
|
// send frontend self-check request
|
||||||
const resp = await POST(`${appSubUrl}/-/admin/self_check`, {
|
const resp = await POST(`${appSubUrl}/-/admin/self_check`, {
|
||||||
@@ -27,5 +27,5 @@ export async function initAdminSelfCheck() {
|
|||||||
|
|
||||||
// only show the "no problem" if there is no visible "self-check-problem"
|
// only show the "no problem" if there is no visible "self-check-problem"
|
||||||
const hasProblem = Boolean(elContent.querySelectorAll('.self-check-problem:not(.tw-hidden)').length);
|
const hasProblem = Boolean(elContent.querySelectorAll('.self-check-problem:not(.tw-hidden)').length);
|
||||||
toggleElem(elContent.querySelector('.self-check-no-problem'), !hasProblem);
|
toggleElem(elContent.querySelector('.self-check-no-problem')!, !hasProblem);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export async function initCaptcha() {
|
|||||||
const captchaEl = document.querySelector('#captcha');
|
const captchaEl = document.querySelector('#captcha');
|
||||||
if (!captchaEl) return;
|
if (!captchaEl) return;
|
||||||
|
|
||||||
const siteKey = captchaEl.getAttribute('data-sitekey');
|
const siteKey = captchaEl.getAttribute('data-sitekey')!;
|
||||||
const isDark = isDarkTheme();
|
const isDark = isDarkTheme();
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
@@ -43,7 +43,7 @@ export async function initCaptcha() {
|
|||||||
|
|
||||||
// @ts-expect-error TS2540: Cannot assign to 'INPUT_NAME' because it is a read-only property.
|
// @ts-expect-error TS2540: Cannot assign to 'INPUT_NAME' because it is a read-only property.
|
||||||
mCaptcha.INPUT_NAME = 'm-captcha-response';
|
mCaptcha.INPUT_NAME = 'm-captcha-response';
|
||||||
const instanceURL = captchaEl.getAttribute('data-instance-url');
|
const instanceURL = captchaEl.getAttribute('data-instance-url')!;
|
||||||
|
|
||||||
new mCaptcha.default({
|
new mCaptcha.default({
|
||||||
siteKey: {
|
siteKey: {
|
||||||
|
|||||||
@@ -31,15 +31,15 @@ export async function initCitationFileCopyContent() {
|
|||||||
|
|
||||||
if (!pageData.citationFileContent) return;
|
if (!pageData.citationFileContent) return;
|
||||||
|
|
||||||
const citationCopyApa = document.querySelector<HTMLButtonElement>('#citation-copy-apa');
|
const citationCopyApa = document.querySelector<HTMLButtonElement>('#citation-copy-apa')!;
|
||||||
const citationCopyBibtex = document.querySelector<HTMLButtonElement>('#citation-copy-bibtex');
|
const citationCopyBibtex = document.querySelector<HTMLButtonElement>('#citation-copy-bibtex')!;
|
||||||
const inputContent = document.querySelector<HTMLInputElement>('#citation-copy-content');
|
const inputContent = document.querySelector<HTMLInputElement>('#citation-copy-content');
|
||||||
|
|
||||||
if ((!citationCopyApa && !citationCopyBibtex) || !inputContent) return;
|
if ((!citationCopyApa && !citationCopyBibtex) || !inputContent) return;
|
||||||
|
|
||||||
const updateUi = () => {
|
const updateUi = () => {
|
||||||
const isBibtex = (localStorage.getItem('citation-copy-format') || defaultCitationFormat) === 'bibtex';
|
const isBibtex = (localStorage.getItem('citation-copy-format') || defaultCitationFormat) === 'bibtex';
|
||||||
const copyContent = (isBibtex ? citationCopyBibtex : citationCopyApa).getAttribute('data-text');
|
const copyContent = (isBibtex ? citationCopyBibtex : citationCopyApa).getAttribute('data-text')!;
|
||||||
inputContent.value = copyContent;
|
inputContent.value = copyContent;
|
||||||
citationCopyBibtex.classList.toggle('primary', isBibtex);
|
citationCopyBibtex.classList.toggle('primary', isBibtex);
|
||||||
citationCopyApa.classList.toggle('primary', !isBibtex);
|
citationCopyApa.classList.toggle('primary', !isBibtex);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {showTemporaryTooltip} from '../modules/tippy.ts';
|
import {showTemporaryTooltip} from '../modules/tippy.ts';
|
||||||
import {toAbsoluteUrl} from '../utils.ts';
|
import {toAbsoluteUrl} from '../utils.ts';
|
||||||
import {clippie} from 'clippie';
|
import {clippie} from 'clippie';
|
||||||
import type {DOMEvent} from '../utils/dom.ts';
|
|
||||||
|
|
||||||
const {copy_success, copy_error} = window.config.i18n;
|
const {copy_success, copy_error} = window.config.i18n;
|
||||||
|
|
||||||
@@ -10,15 +9,15 @@ const {copy_success, copy_error} = window.config.i18n;
|
|||||||
// - data-clipboard-target: Holds a selector for a <input> or <textarea> whose content is copied
|
// - data-clipboard-target: Holds a selector for a <input> or <textarea> whose content is copied
|
||||||
// - data-clipboard-text-type: When set to 'url' will convert relative to absolute urls
|
// - data-clipboard-text-type: When set to 'url' will convert relative to absolute urls
|
||||||
export function initGlobalCopyToClipboardListener() {
|
export function initGlobalCopyToClipboardListener() {
|
||||||
document.addEventListener('click', async (e: DOMEvent<MouseEvent>) => {
|
document.addEventListener('click', async (e) => {
|
||||||
const target = e.target.closest('[data-clipboard-text], [data-clipboard-target]');
|
const target = (e.target as HTMLElement).closest('[data-clipboard-text], [data-clipboard-target]');
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
let text = target.getAttribute('data-clipboard-text');
|
let text = target.getAttribute('data-clipboard-text');
|
||||||
if (!text) {
|
if (!text) {
|
||||||
text = document.querySelector<HTMLInputElement>(target.getAttribute('data-clipboard-target'))?.value;
|
text = document.querySelector<HTMLInputElement>(target.getAttribute('data-clipboard-target')!)?.value ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text && target.getAttribute('data-clipboard-text-type') === 'url') {
|
if (text && target.getAttribute('data-clipboard-text-type') === 'url') {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {createTippy} from '../modules/tippy.ts';
|
import {createTippy} from '../modules/tippy.ts';
|
||||||
import type {DOMEvent} from '../utils/dom.ts';
|
|
||||||
import {registerGlobalInitFunc} from '../modules/observer.ts';
|
import {registerGlobalInitFunc} from '../modules/observer.ts';
|
||||||
|
|
||||||
export async function initColorPickers() {
|
export async function initColorPickers() {
|
||||||
@@ -25,7 +24,7 @@ function updatePicker(el: HTMLElement, newValue: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initPicker(el: HTMLElement): void {
|
function initPicker(el: HTMLElement): void {
|
||||||
const input = el.querySelector('input');
|
const input = el.querySelector('input')!;
|
||||||
|
|
||||||
const square = document.createElement('div');
|
const square = document.createElement('div');
|
||||||
square.classList.add('preview-square');
|
square.classList.add('preview-square');
|
||||||
@@ -39,9 +38,9 @@ function initPicker(el: HTMLElement): void {
|
|||||||
updateSquare(square, e.detail.value);
|
updateSquare(square, e.detail.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
input.addEventListener('input', (e: DOMEvent<Event, HTMLInputElement>) => {
|
input.addEventListener('input', (e) => {
|
||||||
updateSquare(square, e.target.value);
|
updateSquare(square, (e.target as HTMLInputElement).value);
|
||||||
updatePicker(picker, e.target.value);
|
updatePicker(picker, (e.target as HTMLInputElement).value);
|
||||||
});
|
});
|
||||||
|
|
||||||
createTippy(input, {
|
createTippy(input, {
|
||||||
@@ -62,13 +61,13 @@ function initPicker(el: HTMLElement): void {
|
|||||||
input.dispatchEvent(new Event('input', {bubbles: true}));
|
input.dispatchEvent(new Event('input', {bubbles: true}));
|
||||||
updateSquare(square, color);
|
updateSquare(square, color);
|
||||||
};
|
};
|
||||||
el.querySelector('.generate-random-color').addEventListener('click', () => {
|
el.querySelector('.generate-random-color')!.addEventListener('click', () => {
|
||||||
const newValue = `#${Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0')}`;
|
const newValue = `#${Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0')}`;
|
||||||
setSelectedColor(newValue);
|
setSelectedColor(newValue);
|
||||||
});
|
});
|
||||||
for (const colorEl of el.querySelectorAll<HTMLElement>('.precolors .color')) {
|
for (const colorEl of el.querySelectorAll<HTMLElement>('.precolors .color')) {
|
||||||
colorEl.addEventListener('click', (e: DOMEvent<MouseEvent, HTMLAnchorElement>) => {
|
colorEl.addEventListener('click', (e) => {
|
||||||
const newValue = e.target.getAttribute('data-color-hex');
|
const newValue = (e.target as HTMLElement).getAttribute('data-color-hex')!;
|
||||||
setSelectedColor(newValue);
|
setSelectedColor(newValue);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function initGlobalDeleteButton(): void {
|
|||||||
const dataObj = btn.dataset;
|
const dataObj = btn.dataset;
|
||||||
|
|
||||||
const modalId = btn.getAttribute('data-modal-id');
|
const modalId = btn.getAttribute('data-modal-id');
|
||||||
const modal = document.querySelector(`.delete.modal${modalId ? `#${modalId}` : ''}`);
|
const modal = document.querySelector(`.delete.modal${modalId ? `#${modalId}` : ''}`)!;
|
||||||
|
|
||||||
// set the modal "display name" by `data-name`
|
// set the modal "display name" by `data-name`
|
||||||
const modalNameEl = modal.querySelector('.name');
|
const modalNameEl = modal.querySelector('.name');
|
||||||
@@ -37,7 +37,7 @@ export function initGlobalDeleteButton(): void {
|
|||||||
for (const [key, value] of Object.entries(dataObj)) {
|
for (const [key, value] of Object.entries(dataObj)) {
|
||||||
if (key.startsWith('data')) {
|
if (key.startsWith('data')) {
|
||||||
const textEl = modal.querySelector(`.${key}`);
|
const textEl = modal.querySelector(`.${key}`);
|
||||||
if (textEl) textEl.textContent = value;
|
if (textEl) textEl.textContent = value ?? null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ export function initGlobalDeleteButton(): void {
|
|||||||
onApprove: () => {
|
onApprove: () => {
|
||||||
// if `data-type="form"` exists, then submit the form by the selector provided by `data-form="..."`
|
// if `data-type="form"` exists, then submit the form by the selector provided by `data-form="..."`
|
||||||
if (btn.getAttribute('data-type') === 'form') {
|
if (btn.getAttribute('data-type') === 'form') {
|
||||||
const formSelector = btn.getAttribute('data-form');
|
const formSelector = btn.getAttribute('data-form')!;
|
||||||
const form = document.querySelector<HTMLFormElement>(formSelector);
|
const form = document.querySelector<HTMLFormElement>(formSelector);
|
||||||
if (!form) throw new Error(`no form named ${formSelector} found`);
|
if (!form) throw new Error(`no form named ${formSelector} found`);
|
||||||
modal.classList.add('is-loading'); // the form is not in the modal, so also add loading indicator to the modal
|
modal.classList.add('is-loading'); // the form is not in the modal, so also add loading indicator to the modal
|
||||||
@@ -59,14 +59,14 @@ export function initGlobalDeleteButton(): void {
|
|||||||
const postData = new FormData();
|
const postData = new FormData();
|
||||||
for (const [key, value] of Object.entries(dataObj)) {
|
for (const [key, value] of Object.entries(dataObj)) {
|
||||||
if (key.startsWith('data')) { // for data-data-xxx (HTML) -> dataXxx (form)
|
if (key.startsWith('data')) { // for data-data-xxx (HTML) -> dataXxx (form)
|
||||||
postData.append(key.slice(4), value);
|
postData.append(key.slice(4), String(value));
|
||||||
}
|
}
|
||||||
if (key === 'id') { // for data-id="..."
|
if (key === 'id') { // for data-id="..."
|
||||||
postData.append('id', value);
|
postData.append('id', String(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(async () => {
|
(async () => {
|
||||||
const response = await POST(btn.getAttribute('data-url'), {data: postData});
|
const response = await POST(btn.getAttribute('data-url')!, {data: postData});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
window.location.href = data.redirect;
|
window.location.href = data.redirect;
|
||||||
@@ -84,7 +84,7 @@ function onShowPanelClick(el: HTMLElement, e: MouseEvent) {
|
|||||||
// a '.show-panel' element can show a panel, by `data-panel="selector"`
|
// a '.show-panel' element can show a panel, by `data-panel="selector"`
|
||||||
// if it has "toggle" class, it toggles the panel
|
// if it has "toggle" class, it toggles the panel
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const sel = el.getAttribute('data-panel');
|
const sel = el.getAttribute('data-panel')!;
|
||||||
const elems = el.classList.contains('toggle') ? toggleElem(sel) : showElem(sel);
|
const elems = el.classList.contains('toggle') ? toggleElem(sel) : showElem(sel);
|
||||||
for (const elem of elems) {
|
for (const elem of elems) {
|
||||||
if (isElemVisible(elem as HTMLElement)) {
|
if (isElemVisible(elem as HTMLElement)) {
|
||||||
@@ -103,7 +103,7 @@ function onHidePanelClick(el: HTMLElement, e: MouseEvent) {
|
|||||||
}
|
}
|
||||||
sel = el.getAttribute('data-panel-closest');
|
sel = el.getAttribute('data-panel-closest');
|
||||||
if (sel) {
|
if (sel) {
|
||||||
hideElem((el.parentNode as HTMLElement).closest(sel));
|
hideElem((el.parentNode as HTMLElement).closest(sel)!);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new Error('no panel to hide'); // should never happen, otherwise there is a bug in code
|
throw new Error('no panel to hide'); // should never happen, otherwise there is a bug in code
|
||||||
@@ -141,7 +141,7 @@ function onShowModalClick(el: HTMLElement, e: MouseEvent) {
|
|||||||
// * Then, try to query 'target' as HTML tag
|
// * Then, try to query 'target' as HTML tag
|
||||||
// If there is a ".{prop-name}" part like "data-modal-form.action", the "form" element's "action" property will be set, the "prop-name" will be camel-cased to "propName".
|
// If there is a ".{prop-name}" part like "data-modal-form.action", the "form" element's "action" property will be set, the "prop-name" will be camel-cased to "propName".
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const modalSelector = el.getAttribute('data-modal');
|
const modalSelector = el.getAttribute('data-modal')!;
|
||||||
const elModal = document.querySelector(modalSelector);
|
const elModal = document.querySelector(modalSelector);
|
||||||
if (!elModal) throw new Error('no modal for this action');
|
if (!elModal) throw new Error('no modal for this action');
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ async function onLinkActionClick(el: HTMLElement, e: Event) {
|
|||||||
// If the "link-action" has "data-modal-confirm" attribute, a "confirm modal dialog" will be shown before taking action.
|
// If the "link-action" has "data-modal-confirm" attribute, a "confirm modal dialog" will be shown before taking action.
|
||||||
// Attribute "data-modal-confirm" can be a modal element by "#the-modal-id", or a string content for the modal dialog.
|
// Attribute "data-modal-confirm" can be a modal element by "#the-modal-id", or a string content for the modal dialog.
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const url = el.getAttribute('data-url');
|
const url = el.getAttribute('data-url')!;
|
||||||
const doRequest = async () => {
|
const doRequest = async () => {
|
||||||
if ('disabled' in el) el.disabled = true; // el could be A or BUTTON, but "A" doesn't have the "disabled" attribute
|
if ('disabled' in el) el.disabled = true; // el could be A or BUTTON, but "A" doesn't have the "disabled" attribute
|
||||||
await fetchActionDoRequest(el, url, {method: el.getAttribute('data-link-action-method') || 'POST'});
|
await fetchActionDoRequest(el, url, {method: el.getAttribute('data-link-action-method') || 'POST'});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {applyAreYouSure, initAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
import {applyAreYouSure, initAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||||
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.ts';
|
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.ts';
|
||||||
import {queryElems, type DOMEvent} from '../utils/dom.ts';
|
import {queryElems} from '../utils/dom.ts';
|
||||||
import {initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
import {initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
||||||
|
|
||||||
export function initGlobalFormDirtyLeaveConfirm() {
|
export function initGlobalFormDirtyLeaveConfirm() {
|
||||||
@@ -13,14 +13,14 @@ export function initGlobalFormDirtyLeaveConfirm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function initGlobalEnterQuickSubmit() {
|
export function initGlobalEnterQuickSubmit() {
|
||||||
document.addEventListener('keydown', (e: DOMEvent<KeyboardEvent>) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.key !== 'Enter') return;
|
if (e.key !== 'Enter') return;
|
||||||
const hasCtrlOrMeta = ((e.ctrlKey || e.metaKey) && !e.altKey);
|
const hasCtrlOrMeta = ((e.ctrlKey || e.metaKey) && !e.altKey);
|
||||||
if (hasCtrlOrMeta && e.target.matches('textarea')) {
|
if (hasCtrlOrMeta && (e.target as HTMLElement).matches('textarea')) {
|
||||||
if (handleGlobalEnterQuickSubmit(e.target as HTMLElement)) {
|
if (handleGlobalEnterQuickSubmit(e.target as HTMLElement)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
} else if (e.target.matches('input') && !e.target.closest('form')) {
|
} else if ((e.target as HTMLElement).matches('input') && !(e.target as HTMLElement).closest('form')) {
|
||||||
// input in a normal form could handle Enter key by default, so we only handle the input outside a form
|
// input in a normal form could handle Enter key by default, so we only handle the input outside a form
|
||||||
// eslint-disable-next-line unicorn/no-lonely-if
|
// eslint-disable-next-line unicorn/no-lonely-if
|
||||||
if (handleGlobalEnterQuickSubmit(e.target as HTMLElement)) {
|
if (handleGlobalEnterQuickSubmit(e.target as HTMLElement)) {
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ export function initCommonIssueListQuickGoto() {
|
|||||||
const goto = document.querySelector<HTMLElement>('#issue-list-quick-goto');
|
const goto = document.querySelector<HTMLElement>('#issue-list-quick-goto');
|
||||||
if (!goto) return;
|
if (!goto) return;
|
||||||
|
|
||||||
const form = goto.closest('form');
|
const form = goto.closest('form')!;
|
||||||
const input = form.querySelector<HTMLInputElement>('input[name=q]');
|
const input = form.querySelector<HTMLInputElement>('input[name=q]')!;
|
||||||
const repoLink = goto.getAttribute('data-repo-link');
|
const repoLink = goto.getAttribute('data-repo-link')!;
|
||||||
|
|
||||||
form.addEventListener('submit', (e) => {
|
form.addEventListener('submit', (e) => {
|
||||||
// if there is no goto button, or the form is submitted by non-quick-goto elements, submit the form directly
|
// if there is no goto button, or the form is submitted by non-quick-goto elements, submit the form directly
|
||||||
@@ -44,7 +44,10 @@ export function initCommonIssueListQuickGoto() {
|
|||||||
|
|
||||||
// if there is a goto button, use its link
|
// if there is a goto button, use its link
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.location.href = goto.getAttribute('data-issue-goto-link');
|
const link = goto.getAttribute('data-issue-goto-link');
|
||||||
|
if (link) {
|
||||||
|
window.location.href = link;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onInput = async () => {
|
const onInput = async () => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export function initCommonOrganization() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.querySelector<HTMLInputElement>('.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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function initFooterLanguageMenu() {
|
|||||||
const item = (e.target as HTMLElement).closest('.item');
|
const item = (e.target as HTMLElement).closest('.item');
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await GET(item.getAttribute('data-url'));
|
await GET(item.getAttribute('data-url')!);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ function initFooterThemeSelector() {
|
|||||||
apiSettings: {url: `${appSubUrl}/-/web-theme/list`, cache: false},
|
apiSettings: {url: `${appSubUrl}/-/web-theme/list`, cache: false},
|
||||||
});
|
});
|
||||||
addDelegatedEventListener(elDropdown, 'click', '.menu > .item', async (el) => {
|
addDelegatedEventListener(elDropdown, 'click', '.menu > .item', async (el) => {
|
||||||
const themeName = el.getAttribute('data-value');
|
const themeName = el.getAttribute('data-value')!;
|
||||||
await POST(`${appSubUrl}/-/web-theme/apply?theme=${encodeURIComponent(themeName)}`);
|
await POST(`${appSubUrl}/-/web-theme/apply?theme=${encodeURIComponent(themeName)}`);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export class ComboMarkdownEditor {
|
|||||||
textareaMarkdownToolbar: HTMLElement;
|
textareaMarkdownToolbar: HTMLElement;
|
||||||
textareaAutosize: any;
|
textareaAutosize: any;
|
||||||
|
|
||||||
dropzone: HTMLElement;
|
dropzone: HTMLElement | null;
|
||||||
attachedDropzoneInst: any;
|
attachedDropzoneInst: any;
|
||||||
|
|
||||||
previewMode: string;
|
previewMode: string;
|
||||||
@@ -105,7 +105,7 @@ export class ComboMarkdownEditor {
|
|||||||
await this.switchToUserPreference();
|
await this.switchToUserPreference();
|
||||||
}
|
}
|
||||||
|
|
||||||
applyEditorHeights(el: HTMLElement, heights: Heights) {
|
applyEditorHeights(el: HTMLElement, heights: Heights | undefined) {
|
||||||
if (!heights) return;
|
if (!heights) return;
|
||||||
if (heights.minHeight) el.style.minHeight = heights.minHeight;
|
if (heights.minHeight) el.style.minHeight = heights.minHeight;
|
||||||
if (heights.height) el.style.height = heights.height;
|
if (heights.height) el.style.height = heights.height;
|
||||||
@@ -114,14 +114,14 @@ export class ComboMarkdownEditor {
|
|||||||
|
|
||||||
setupContainer() {
|
setupContainer() {
|
||||||
this.supportEasyMDE = this.container.getAttribute('data-support-easy-mde') === 'true';
|
this.supportEasyMDE = this.container.getAttribute('data-support-easy-mde') === 'true';
|
||||||
this.previewMode = this.container.getAttribute('data-content-mode');
|
this.previewMode = this.container.getAttribute('data-content-mode')!;
|
||||||
this.previewUrl = this.container.getAttribute('data-preview-url');
|
this.previewUrl = this.container.getAttribute('data-preview-url')!;
|
||||||
this.previewContext = this.container.getAttribute('data-preview-context');
|
this.previewContext = this.container.getAttribute('data-preview-context')!;
|
||||||
initTextExpander(this.container.querySelector('text-expander'));
|
initTextExpander(this.container.querySelector('text-expander')!);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupTextarea() {
|
setupTextarea() {
|
||||||
this.textarea = this.container.querySelector('.markdown-text-editor');
|
this.textarea = this.container.querySelector('.markdown-text-editor')!;
|
||||||
this.textarea._giteaComboMarkdownEditor = this;
|
this.textarea._giteaComboMarkdownEditor = this;
|
||||||
this.textarea.id = generateElemId(`_combo_markdown_editor_`);
|
this.textarea.id = generateElemId(`_combo_markdown_editor_`);
|
||||||
this.textarea.addEventListener('input', () => triggerEditorContentChanged(this.container));
|
this.textarea.addEventListener('input', () => triggerEditorContentChanged(this.container));
|
||||||
@@ -131,7 +131,7 @@ export class ComboMarkdownEditor {
|
|||||||
this.textareaAutosize = autosize(this.textarea, {viewportMarginBottom: 130});
|
this.textareaAutosize = autosize(this.textarea, {viewportMarginBottom: 130});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.textareaMarkdownToolbar = this.container.querySelector('markdown-toolbar');
|
this.textareaMarkdownToolbar = this.container.querySelector('markdown-toolbar')!;
|
||||||
this.textareaMarkdownToolbar.setAttribute('for', this.textarea.id);
|
this.textareaMarkdownToolbar.setAttribute('for', this.textarea.id);
|
||||||
for (const el of this.textareaMarkdownToolbar.querySelectorAll('.markdown-toolbar-button')) {
|
for (const el of this.textareaMarkdownToolbar.querySelectorAll('.markdown-toolbar-button')) {
|
||||||
// upstream bug: The role code is never executed in base MarkdownButtonElement https://github.com/github/markdown-toolbar-element/issues/70
|
// upstream bug: The role code is never executed in base MarkdownButtonElement https://github.com/github/markdown-toolbar-element/issues/70
|
||||||
@@ -140,9 +140,9 @@ export class ComboMarkdownEditor {
|
|||||||
if (el.nodeName === 'BUTTON' && !el.getAttribute('type')) el.setAttribute('type', 'button');
|
if (el.nodeName === 'BUTTON' && !el.getAttribute('type')) el.setAttribute('type', 'button');
|
||||||
}
|
}
|
||||||
|
|
||||||
const monospaceButton = this.container.querySelector('.markdown-switch-monospace');
|
const monospaceButton = this.container.querySelector('.markdown-switch-monospace')!;
|
||||||
const monospaceEnabled = localStorage?.getItem('markdown-editor-monospace') === 'true';
|
const monospaceEnabled = localStorage?.getItem('markdown-editor-monospace') === 'true';
|
||||||
const monospaceText = monospaceButton.getAttribute(monospaceEnabled ? 'data-disable-text' : 'data-enable-text');
|
const monospaceText = monospaceButton.getAttribute(monospaceEnabled ? 'data-disable-text' : 'data-enable-text')!;
|
||||||
monospaceButton.setAttribute('data-tooltip-content', monospaceText);
|
monospaceButton.setAttribute('data-tooltip-content', monospaceText);
|
||||||
monospaceButton.setAttribute('aria-checked', String(monospaceEnabled));
|
monospaceButton.setAttribute('aria-checked', String(monospaceEnabled));
|
||||||
monospaceButton.addEventListener('click', (e) => {
|
monospaceButton.addEventListener('click', (e) => {
|
||||||
@@ -150,13 +150,13 @@ export class ComboMarkdownEditor {
|
|||||||
const enabled = localStorage?.getItem('markdown-editor-monospace') !== 'true';
|
const enabled = localStorage?.getItem('markdown-editor-monospace') !== 'true';
|
||||||
localStorage.setItem('markdown-editor-monospace', String(enabled));
|
localStorage.setItem('markdown-editor-monospace', String(enabled));
|
||||||
this.textarea.classList.toggle('tw-font-mono', enabled);
|
this.textarea.classList.toggle('tw-font-mono', enabled);
|
||||||
const text = monospaceButton.getAttribute(enabled ? 'data-disable-text' : 'data-enable-text');
|
const text = monospaceButton.getAttribute(enabled ? 'data-disable-text' : 'data-enable-text')!;
|
||||||
monospaceButton.setAttribute('data-tooltip-content', text);
|
monospaceButton.setAttribute('data-tooltip-content', text);
|
||||||
monospaceButton.setAttribute('aria-checked', String(enabled));
|
monospaceButton.setAttribute('aria-checked', String(enabled));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.supportEasyMDE) {
|
if (this.supportEasyMDE) {
|
||||||
const easymdeButton = this.container.querySelector('.markdown-switch-easymde');
|
const easymdeButton = this.container.querySelector('.markdown-switch-easymde')!;
|
||||||
easymdeButton.addEventListener('click', async (e) => {
|
easymdeButton.addEventListener('click', async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.userPreferredEditor = 'easymde';
|
this.userPreferredEditor = 'easymde';
|
||||||
@@ -173,7 +173,7 @@ export class ComboMarkdownEditor {
|
|||||||
async setupDropzone() {
|
async setupDropzone() {
|
||||||
const dropzoneParentContainer = this.container.getAttribute('data-dropzone-parent-container');
|
const dropzoneParentContainer = this.container.getAttribute('data-dropzone-parent-container');
|
||||||
if (!dropzoneParentContainer) return;
|
if (!dropzoneParentContainer) return;
|
||||||
this.dropzone = this.container.closest(this.container.getAttribute('data-dropzone-parent-container'))?.querySelector('.dropzone');
|
this.dropzone = this.container.closest(this.container.getAttribute('data-dropzone-parent-container')!)?.querySelector('.dropzone') ?? null;
|
||||||
if (!this.dropzone) return;
|
if (!this.dropzone) return;
|
||||||
|
|
||||||
this.attachedDropzoneInst = await initDropzone(this.dropzone);
|
this.attachedDropzoneInst = await initDropzone(this.dropzone);
|
||||||
@@ -212,13 +212,14 @@ export class ComboMarkdownEditor {
|
|||||||
// Fomantic Tab requires the "data-tab" to be globally unique.
|
// Fomantic Tab requires the "data-tab" to be globally unique.
|
||||||
// So here it uses our defined "data-tab-for" and "data-tab-panel" to generate the "data-tab" attribute for Fomantic.
|
// So here it uses our defined "data-tab-for" and "data-tab-panel" to generate the "data-tab" attribute for Fomantic.
|
||||||
const tabIdSuffix = generateElemId();
|
const tabIdSuffix = generateElemId();
|
||||||
this.tabEditor = Array.from(tabs).find((tab) => tab.getAttribute('data-tab-for') === 'markdown-writer');
|
const tabsArr = Array.from(tabs);
|
||||||
this.tabPreviewer = Array.from(tabs).find((tab) => tab.getAttribute('data-tab-for') === 'markdown-previewer');
|
this.tabEditor = tabsArr.find((tab) => tab.getAttribute('data-tab-for') === 'markdown-writer')!;
|
||||||
|
this.tabPreviewer = tabsArr.find((tab) => tab.getAttribute('data-tab-for') === 'markdown-previewer')!;
|
||||||
this.tabEditor.setAttribute('data-tab', `markdown-writer-${tabIdSuffix}`);
|
this.tabEditor.setAttribute('data-tab', `markdown-writer-${tabIdSuffix}`);
|
||||||
this.tabPreviewer.setAttribute('data-tab', `markdown-previewer-${tabIdSuffix}`);
|
this.tabPreviewer.setAttribute('data-tab', `markdown-previewer-${tabIdSuffix}`);
|
||||||
|
|
||||||
const panelEditor = this.container.querySelector('.ui.tab[data-tab-panel="markdown-writer"]');
|
const panelEditor = this.container.querySelector('.ui.tab[data-tab-panel="markdown-writer"]')!;
|
||||||
const panelPreviewer = this.container.querySelector('.ui.tab[data-tab-panel="markdown-previewer"]');
|
const panelPreviewer = this.container.querySelector('.ui.tab[data-tab-panel="markdown-previewer"]')!;
|
||||||
panelEditor.setAttribute('data-tab', `markdown-writer-${tabIdSuffix}`);
|
panelEditor.setAttribute('data-tab', `markdown-writer-${tabIdSuffix}`);
|
||||||
panelPreviewer.setAttribute('data-tab', `markdown-previewer-${tabIdSuffix}`);
|
panelPreviewer.setAttribute('data-tab', `markdown-previewer-${tabIdSuffix}`);
|
||||||
|
|
||||||
@@ -254,8 +255,8 @@ export class ComboMarkdownEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initMarkdownButtonTableAdd() {
|
initMarkdownButtonTableAdd() {
|
||||||
const addTableButton = this.container.querySelector('.markdown-button-table-add');
|
const addTableButton = this.container.querySelector('.markdown-button-table-add')!;
|
||||||
const addTablePanel = this.container.querySelector('.markdown-add-table-panel');
|
const addTablePanel = this.container.querySelector('.markdown-add-table-panel')!;
|
||||||
// here the tippy can't attach to the button because the button already owns a tippy for tooltip
|
// here the tippy can't attach to the button because the button already owns a tippy for tooltip
|
||||||
const addTablePanelTippy = createTippy(addTablePanel, {
|
const addTablePanelTippy = createTippy(addTablePanel, {
|
||||||
content: addTablePanel,
|
content: addTablePanel,
|
||||||
@@ -267,9 +268,9 @@ export class ComboMarkdownEditor {
|
|||||||
});
|
});
|
||||||
addTableButton.addEventListener('click', () => addTablePanelTippy.show());
|
addTableButton.addEventListener('click', () => addTablePanelTippy.show());
|
||||||
|
|
||||||
addTablePanel.querySelector('.ui.button.primary').addEventListener('click', () => {
|
addTablePanel.querySelector('.ui.button.primary')!.addEventListener('click', () => {
|
||||||
let rows = parseInt(addTablePanel.querySelector<HTMLInputElement>('[name=rows]').value);
|
let rows = parseInt(addTablePanel.querySelector<HTMLInputElement>('[name=rows]')!.value);
|
||||||
let cols = parseInt(addTablePanel.querySelector<HTMLInputElement>('[name=cols]').value);
|
let cols = parseInt(addTablePanel.querySelector<HTMLInputElement>('[name=cols]')!.value);
|
||||||
rows = Math.max(1, Math.min(100, rows));
|
rows = Math.max(1, Math.min(100, rows));
|
||||||
cols = Math.max(1, Math.min(100, cols));
|
cols = Math.max(1, Math.min(100, cols));
|
||||||
textareaInsertText(this.textarea, `\n${this.generateMarkdownTable(rows, cols)}\n\n`);
|
textareaInsertText(this.textarea, `\n${this.generateMarkdownTable(rows, cols)}\n\n`);
|
||||||
@@ -360,7 +361,7 @@ export class ComboMarkdownEditor {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.applyEditorHeights(this.container.querySelector('.CodeMirror-scroll'), this.options.editorHeights);
|
this.applyEditorHeights(this.container.querySelector('.CodeMirror-scroll')!, this.options.editorHeights);
|
||||||
await attachTribute(this.easyMDE.codemirror.getInputField());
|
await attachTribute(this.easyMDE.codemirror.getInputField());
|
||||||
if (this.dropzone) {
|
if (this.dropzone) {
|
||||||
initEasyMDEPaste(this.easyMDE, this.dropzone);
|
initEasyMDEPaste(this.easyMDE, this.dropzone);
|
||||||
@@ -401,10 +402,10 @@ export class ComboMarkdownEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get userPreferredEditor() {
|
get userPreferredEditor(): string {
|
||||||
return window.localStorage.getItem(`markdown-editor-${this.previewMode ?? 'default'}`);
|
return window.localStorage.getItem(`markdown-editor-${this.previewMode ?? 'default'}`) || '';
|
||||||
}
|
}
|
||||||
set userPreferredEditor(s) {
|
set userPreferredEditor(s: string) {
|
||||||
window.localStorage.setItem(`markdown-editor-${this.previewMode ?? 'default'}`, s);
|
window.localStorage.setItem(`markdown-editor-${this.previewMode ?? 'default'}`, s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {showElem, type DOMEvent} from '../../utils/dom.ts';
|
import {showElem} from '../../utils/dom.ts';
|
||||||
|
|
||||||
type CropperOpts = {
|
type CropperOpts = {
|
||||||
container: HTMLElement,
|
container: HTMLElement,
|
||||||
@@ -17,6 +17,7 @@ async function initCompCropper({container, fileInput, imageSource}: CropperOpts)
|
|||||||
crop() {
|
crop() {
|
||||||
const canvas = cropper.getCroppedCanvas();
|
const canvas = cropper.getCroppedCanvas();
|
||||||
canvas.toBlob((blob) => {
|
canvas.toBlob((blob) => {
|
||||||
|
if (!blob) return;
|
||||||
const croppedFileName = currentFileName.replace(/\.[^.]{3,4}$/, '.png');
|
const croppedFileName = currentFileName.replace(/\.[^.]{3,4}$/, '.png');
|
||||||
const croppedFile = new File([blob], croppedFileName, {type: 'image/png', lastModified: currentFileLastModified});
|
const croppedFile = new File([blob], croppedFileName, {type: 'image/png', lastModified: currentFileLastModified});
|
||||||
const dataTransfer = new DataTransfer();
|
const dataTransfer = new DataTransfer();
|
||||||
@@ -26,9 +27,9 @@ async function initCompCropper({container, fileInput, imageSource}: CropperOpts)
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
fileInput.addEventListener('input', (e: DOMEvent<Event, HTMLInputElement>) => {
|
fileInput.addEventListener('input', (e) => {
|
||||||
const files = e.target.files;
|
const files = (e.target as HTMLInputElement).files;
|
||||||
if (files?.length > 0) {
|
if (files?.length) {
|
||||||
currentFileName = files[0].name;
|
currentFileName = files[0].name;
|
||||||
currentFileLastModified = files[0].lastModified;
|
currentFileLastModified = files[0].lastModified;
|
||||||
const fileURL = URL.createObjectURL(files[0]);
|
const fileURL = URL.createObjectURL(files[0]);
|
||||||
@@ -42,6 +43,6 @@ async function initCompCropper({container, fileInput, imageSource}: CropperOpts)
|
|||||||
export async function initAvatarUploaderWithCropper(fileInput: HTMLInputElement) {
|
export async function initAvatarUploaderWithCropper(fileInput: HTMLInputElement) {
|
||||||
const panel = fileInput.nextElementSibling as HTMLElement;
|
const panel = fileInput.nextElementSibling as HTMLElement;
|
||||||
if (!panel?.matches('.cropper-panel')) throw new Error('Missing cropper panel for avatar uploader');
|
if (!panel?.matches('.cropper-panel')) throw new Error('Missing cropper panel for avatar uploader');
|
||||||
const imageSource = panel.querySelector<HTMLImageElement>('.cropper-source');
|
const imageSource = panel.querySelector<HTMLImageElement>('.cropper-source')!;
|
||||||
await initCompCropper({container: panel, fileInput, imageSource});
|
await initCompCropper({container: panel, fileInput, imageSource});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ test('textareaSplitLines', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('markdownHandleIndention', () => {
|
test('markdownHandleIndention', () => {
|
||||||
const testInput = (input: string, expected?: string) => {
|
const testInput = (input: string, expected: string | null) => {
|
||||||
const inputPos = input.indexOf('|');
|
const inputPos = input.indexOf('|');
|
||||||
input = input.replaceAll('|', '');
|
input = input.replaceAll('|', '');
|
||||||
const ret = markdownHandleIndention({value: input, selStart: inputPos, selEnd: inputPos});
|
const ret = markdownHandleIndention({value: input, selStart: inputPos, selEnd: inputPos});
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ function handleIndentSelection(textarea: HTMLTextAreaElement, e: KeyboardEvent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// re-calculating the selection range
|
// re-calculating the selection range
|
||||||
let newSelStart, newSelEnd;
|
let newSelStart: number | null = null;
|
||||||
|
let newSelEnd: number | null = null;
|
||||||
pos = 0;
|
pos = 0;
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
if (i === selectedLines[0]) {
|
if (i === selectedLines[0]) {
|
||||||
@@ -134,7 +135,7 @@ export function markdownHandleIndention(tvs: TextareaValueSelection): MarkdownHa
|
|||||||
|
|
||||||
// parse the indention
|
// parse the indention
|
||||||
let lineContent = line;
|
let lineContent = line;
|
||||||
const indention = /^\s*/.exec(lineContent)[0];
|
const indention = (/^\s*/.exec(lineContent) || [''])[0];
|
||||||
lineContent = lineContent.slice(indention.length);
|
lineContent = lineContent.slice(indention.length);
|
||||||
if (linesBuf.inlinePos <= indention.length) return unhandled; // if cursor is at the indention, do nothing, let the browser handle it
|
if (linesBuf.inlinePos <= indention.length) return unhandled; // if cursor is at the indention, do nothing, let the browser handle it
|
||||||
|
|
||||||
@@ -177,7 +178,7 @@ export function markdownHandleIndention(tvs: TextareaValueSelection): MarkdownHa
|
|||||||
|
|
||||||
function handleNewline(textarea: HTMLTextAreaElement, e: Event) {
|
function handleNewline(textarea: HTMLTextAreaElement, e: Event) {
|
||||||
const ret = markdownHandleIndention({value: textarea.value, selStart: textarea.selectionStart, selEnd: textarea.selectionEnd});
|
const ret = markdownHandleIndention({value: textarea.value, selStart: textarea.selectionStart, selEnd: textarea.selectionEnd});
|
||||||
if (!ret.handled) return;
|
if (!ret.handled || !ret.valueSelection) return; // FIXME: the "handled" seems redundant, only valueSelection is enough (null for unhandled)
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
textarea.value = ret.valueSelection.value;
|
textarea.value = ret.valueSelection.value;
|
||||||
textarea.setSelectionRange(ret.valueSelection.selStart, ret.valueSelection.selEnd);
|
textarea.setSelectionRange(ret.valueSelection.selStart, ret.valueSelection.selEnd);
|
||||||
|
|||||||
@@ -121,7 +121,10 @@ function getPastedImages(e: ClipboardEvent) {
|
|||||||
const images: Array<File> = [];
|
const images: Array<File> = [];
|
||||||
for (const item of e.clipboardData?.items ?? []) {
|
for (const item of e.clipboardData?.items ?? []) {
|
||||||
if (item.type?.startsWith('image/')) {
|
if (item.type?.startsWith('image/')) {
|
||||||
images.push(item.getAsFile());
|
const file = item.getAsFile();
|
||||||
|
if (file) {
|
||||||
|
images.push(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return images;
|
return images;
|
||||||
@@ -135,7 +138,7 @@ export function initEasyMDEPaste(easyMDE: EasyMDE, dropzoneEl: HTMLElement) {
|
|||||||
handleUploadFiles(editor, dropzoneEl, images, e);
|
handleUploadFiles(editor, dropzoneEl, images, e);
|
||||||
});
|
});
|
||||||
easyMDE.codemirror.on('drop', (_, e) => {
|
easyMDE.codemirror.on('drop', (_, e) => {
|
||||||
if (!e.dataTransfer.files.length) return;
|
if (!e.dataTransfer?.files.length) return;
|
||||||
handleUploadFiles(editor, dropzoneEl, e.dataTransfer.files, e);
|
handleUploadFiles(editor, dropzoneEl, e.dataTransfer.files, e);
|
||||||
});
|
});
|
||||||
dropzoneEl.dropzone.on(DropzoneCustomEventRemovedFile, ({fileUuid}) => {
|
dropzoneEl.dropzone.on(DropzoneCustomEventRemovedFile, ({fileUuid}) => {
|
||||||
@@ -145,7 +148,7 @@ export function initEasyMDEPaste(easyMDE: EasyMDE, dropzoneEl: HTMLElement) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initTextareaEvents(textarea: HTMLTextAreaElement, dropzoneEl: HTMLElement) {
|
export function initTextareaEvents(textarea: HTMLTextAreaElement, dropzoneEl: HTMLElement | null) {
|
||||||
subscribe(textarea); // enable paste features
|
subscribe(textarea); // enable paste features
|
||||||
textarea.addEventListener('paste', (e: ClipboardEvent) => {
|
textarea.addEventListener('paste', (e: ClipboardEvent) => {
|
||||||
const images = getPastedImages(e);
|
const images = getPastedImages(e);
|
||||||
@@ -154,7 +157,7 @@ export function initTextareaEvents(textarea: HTMLTextAreaElement, dropzoneEl: HT
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
textarea.addEventListener('drop', (e: DragEvent) => {
|
textarea.addEventListener('drop', (e: DragEvent) => {
|
||||||
if (!e.dataTransfer.files.length) return;
|
if (!e.dataTransfer?.files.length) return;
|
||||||
if (!dropzoneEl) return;
|
if (!dropzoneEl) return;
|
||||||
handleUploadFiles(new TextareaEditor(textarea), dropzoneEl, e.dataTransfer.files, e);
|
handleUploadFiles(new TextareaEditor(textarea), dropzoneEl, e.dataTransfer.files, e);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,17 +14,17 @@ export function initCompLabelEdit(pageSelector: string) {
|
|||||||
const elModal = pageContent.querySelector<HTMLElement>('#issue-label-edit-modal');
|
const elModal = pageContent.querySelector<HTMLElement>('#issue-label-edit-modal');
|
||||||
if (!elModal) return;
|
if (!elModal) return;
|
||||||
|
|
||||||
const elLabelId = elModal.querySelector<HTMLInputElement>('input[name="id"]');
|
const elLabelId = elModal.querySelector<HTMLInputElement>('input[name="id"]')!;
|
||||||
const elNameInput = elModal.querySelector<HTMLInputElement>('.label-name-input');
|
const elNameInput = elModal.querySelector<HTMLInputElement>('.label-name-input')!;
|
||||||
const elExclusiveField = elModal.querySelector('.label-exclusive-input-field');
|
const elExclusiveField = elModal.querySelector('.label-exclusive-input-field')!;
|
||||||
const elExclusiveInput = elModal.querySelector<HTMLInputElement>('.label-exclusive-input');
|
const elExclusiveInput = elModal.querySelector<HTMLInputElement>('.label-exclusive-input')!;
|
||||||
const elExclusiveWarning = elModal.querySelector('.label-exclusive-warning');
|
const elExclusiveWarning = elModal.querySelector('.label-exclusive-warning')!;
|
||||||
const elExclusiveOrderField = elModal.querySelector<HTMLInputElement>('.label-exclusive-order-input-field');
|
const elExclusiveOrderField = elModal.querySelector<HTMLInputElement>('.label-exclusive-order-input-field')!;
|
||||||
const elExclusiveOrderInput = elModal.querySelector<HTMLInputElement>('.label-exclusive-order-input');
|
const elExclusiveOrderInput = elModal.querySelector<HTMLInputElement>('.label-exclusive-order-input')!;
|
||||||
const elIsArchivedField = elModal.querySelector('.label-is-archived-input-field');
|
const elIsArchivedField = elModal.querySelector('.label-is-archived-input-field')!;
|
||||||
const elIsArchivedInput = elModal.querySelector<HTMLInputElement>('.label-is-archived-input');
|
const elIsArchivedInput = elModal.querySelector<HTMLInputElement>('.label-is-archived-input')!;
|
||||||
const elDescInput = elModal.querySelector<HTMLInputElement>('.label-desc-input');
|
const elDescInput = elModal.querySelector<HTMLInputElement>('.label-desc-input')!;
|
||||||
const elColorInput = elModal.querySelector<HTMLInputElement>('.color-picker-combo input');
|
const elColorInput = elModal.querySelector<HTMLInputElement>('.color-picker-combo input')!;
|
||||||
|
|
||||||
const syncModalUi = () => {
|
const syncModalUi = () => {
|
||||||
const hasScope = nameHasScope(elNameInput.value);
|
const hasScope = nameHasScope(elNameInput.value);
|
||||||
@@ -37,13 +37,13 @@ export function initCompLabelEdit(pageSelector: string) {
|
|||||||
if (parseInt(elExclusiveOrderInput.value) <= 0) {
|
if (parseInt(elExclusiveOrderInput.value) <= 0) {
|
||||||
elExclusiveOrderInput.style.color = 'var(--color-placeholder-text) !important';
|
elExclusiveOrderInput.style.color = 'var(--color-placeholder-text) !important';
|
||||||
} else {
|
} else {
|
||||||
elExclusiveOrderInput.style.color = null;
|
elExclusiveOrderInput.style.removeProperty('color');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const showLabelEditModal = (btn:HTMLElement) => {
|
const showLabelEditModal = (btn:HTMLElement) => {
|
||||||
// the "btn" should contain the label's attributes by its `data-label-xxx` attributes
|
// the "btn" should contain the label's attributes by its `data-label-xxx` attributes
|
||||||
const form = elModal.querySelector<HTMLFormElement>('form');
|
const form = elModal.querySelector<HTMLFormElement>('form')!;
|
||||||
elLabelId.value = btn.getAttribute('data-label-id') || '';
|
elLabelId.value = btn.getAttribute('data-label-id') || '';
|
||||||
elNameInput.value = btn.getAttribute('data-label-name') || '';
|
elNameInput.value = btn.getAttribute('data-label-name') || '';
|
||||||
elExclusiveOrderInput.value = btn.getAttribute('data-label-exclusive-order') || '0';
|
elExclusiveOrderInput.value = btn.getAttribute('data-label-exclusive-order') || '0';
|
||||||
@@ -59,7 +59,7 @@ export function initCompLabelEdit(pageSelector: string) {
|
|||||||
// if a label was not exclusive but has issues, then it should warn user if it will become exclusive
|
// if a label was not exclusive but has issues, then it should warn user if it will become exclusive
|
||||||
const numIssues = parseInt(btn.getAttribute('data-label-num-issues') || '0');
|
const numIssues = parseInt(btn.getAttribute('data-label-num-issues') || '0');
|
||||||
elModal.toggleAttribute('data-need-warn-exclusive', !elExclusiveInput.checked && numIssues > 0);
|
elModal.toggleAttribute('data-need-warn-exclusive', !elExclusiveInput.checked && numIssues > 0);
|
||||||
elModal.querySelector('.header').textContent = isEdit ? elModal.getAttribute('data-text-edit-label') : elModal.getAttribute('data-text-new-label');
|
elModal.querySelector('.header')!.textContent = isEdit ? elModal.getAttribute('data-text-edit-label') : elModal.getAttribute('data-text-new-label');
|
||||||
|
|
||||||
const curPageLink = elModal.getAttribute('data-current-page-link');
|
const curPageLink = elModal.getAttribute('data-current-page-link');
|
||||||
form.action = isEdit ? `${curPageLink}/edit` : `${curPageLink}/new`;
|
form.action = isEdit ? `${curPageLink}/edit` : `${curPageLink}/new`;
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
import {POST} from '../../modules/fetch.ts';
|
import {POST} from '../../modules/fetch.ts';
|
||||||
import type {DOMEvent} from '../../utils/dom.ts';
|
|
||||||
import {registerGlobalEventFunc} from '../../modules/observer.ts';
|
import {registerGlobalEventFunc} from '../../modules/observer.ts';
|
||||||
|
|
||||||
export function initCompReactionSelector() {
|
export function initCompReactionSelector() {
|
||||||
registerGlobalEventFunc('click', 'onCommentReactionButtonClick', async (target: HTMLElement, e: DOMEvent<MouseEvent>) => {
|
registerGlobalEventFunc('click', 'onCommentReactionButtonClick', async (target: HTMLElement, e: Event) => {
|
||||||
// there are 2 places for the "reaction" buttons, one is the top-right reaction menu, one is the bottom of the comment
|
// there are 2 places for the "reaction" buttons, one is the top-right reaction menu, one is the bottom of the comment
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (target.classList.contains('disabled')) return;
|
if (target.classList.contains('disabled')) return;
|
||||||
|
|
||||||
const actionUrl = target.closest('[data-action-url]').getAttribute('data-action-url');
|
const actionUrl = target.closest('[data-action-url]')!.getAttribute('data-action-url');
|
||||||
const reactionContent = target.getAttribute('data-reaction-content');
|
const reactionContent = target.getAttribute('data-reaction-content')!;
|
||||||
|
|
||||||
const commentContainer = target.closest('.comment-container');
|
const commentContainer = target.closest('.comment-container')!;
|
||||||
|
|
||||||
const bottomReactions = commentContainer.querySelector('.bottom-reactions'); // may not exist if there is no reaction
|
const bottomReactions = commentContainer.querySelector('.bottom-reactions'); // may not exist if there is no reaction
|
||||||
const bottomReactionBtn = bottomReactions?.querySelector(`a[data-reaction-content="${CSS.escape(reactionContent)}"]`);
|
const bottomReactionBtn = bottomReactions?.querySelector(`a[data-reaction-content="${CSS.escape(reactionContent)}"]`);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function initCompSearchUserBox() {
|
|||||||
url: `${appSubUrl}/user/search_candidates?q={query}&orgs=${includeOrgs}`,
|
url: `${appSubUrl}/user/search_candidates?q={query}&orgs=${includeOrgs}`,
|
||||||
onResponse(response: any) {
|
onResponse(response: any) {
|
||||||
const resultItems = [];
|
const resultItems = [];
|
||||||
const searchQuery = searchUserBox.querySelector('input').value;
|
const searchQuery = searchUserBox.querySelector('input')!.value;
|
||||||
const searchQueryUppercase = searchQuery.toUpperCase();
|
const searchQueryUppercase = searchQuery.toUpperCase();
|
||||||
for (const item of response.data) {
|
for (const item of response.data) {
|
||||||
const resultItem = {
|
const resultItem = {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ async function fetchIssueSuggestions(key: string, text: string): Promise<TextExp
|
|||||||
export function initTextExpander(expander: TextExpanderElement) {
|
export function initTextExpander(expander: TextExpanderElement) {
|
||||||
if (!expander) return;
|
if (!expander) return;
|
||||||
|
|
||||||
const textarea = expander.querySelector<HTMLTextAreaElement>('textarea');
|
const textarea = expander.querySelector<HTMLTextAreaElement>('textarea')!;
|
||||||
|
|
||||||
// help to fix the text-expander "multiword+promise" bug: do not show the popup when there is no "#" before current line
|
// help to fix the text-expander "multiword+promise" bug: do not show the popup when there is no "#" before current line
|
||||||
const shouldShowIssueSuggestions = () => {
|
const shouldShowIssueSuggestions = () => {
|
||||||
@@ -64,6 +64,7 @@ export function initTextExpander(expander: TextExpanderElement) {
|
|||||||
}, 300); // to match onInputDebounce delay
|
}, 300); // to match onInputDebounce delay
|
||||||
|
|
||||||
expander.addEventListener('text-expander-change', (e: TextExpanderChangeEvent) => {
|
expander.addEventListener('text-expander-change', (e: TextExpanderChangeEvent) => {
|
||||||
|
if (!e.detail) return;
|
||||||
const {key, text, provide} = e.detail;
|
const {key, text, provide} = e.detail;
|
||||||
if (key === ':') {
|
if (key === ':') {
|
||||||
const matches = matchEmoji(text);
|
const matches = matchEmoji(text);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function initCompWebHookEditor() {
|
|||||||
if (httpMethodInput) {
|
if (httpMethodInput) {
|
||||||
const updateContentType = function () {
|
const updateContentType = function () {
|
||||||
const visible = httpMethodInput.value === 'POST';
|
const visible = httpMethodInput.value === 'POST';
|
||||||
toggleElem(document.querySelector('#content_type').closest('.field'), visible);
|
toggleElem(document.querySelector('#content_type')!.closest('.field')!, visible);
|
||||||
};
|
};
|
||||||
updateContentType();
|
updateContentType();
|
||||||
httpMethodInput.addEventListener('change', updateContentType);
|
httpMethodInput.addEventListener('change', updateContentType);
|
||||||
@@ -36,9 +36,12 @@ export function initCompWebHookEditor() {
|
|||||||
// Test delivery
|
// Test delivery
|
||||||
document.querySelector<HTMLButtonElement>('#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(() => {
|
||||||
window.location.href = this.getAttribute('data-redirect');
|
const redirectUrl = this.getAttribute('data-redirect');
|
||||||
|
if (redirectUrl) {
|
||||||
|
window.location.href = redirectUrl;
|
||||||
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function initCopyContent() {
|
|||||||
btn.classList.add('is-loading', 'loading-icon-2px');
|
btn.classList.add('is-loading', 'loading-icon-2px');
|
||||||
try {
|
try {
|
||||||
const res = await GET(rawFileLink, {credentials: 'include', redirect: 'follow'});
|
const res = await GET(rawFileLink, {credentials: 'include', redirect: 'follow'});
|
||||||
const contentType = res.headers.get('content-type');
|
const contentType = res.headers.get('content-type')!;
|
||||||
|
|
||||||
if (contentType.startsWith('image/') && !contentType.startsWith('image/svg')) {
|
if (contentType.startsWith('image/') && !contentType.startsWith('image/svg')) {
|
||||||
isRasterImage = true;
|
isRasterImage = true;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ async function createDropzone(el: HTMLElement, opts: DropzoneOptions) {
|
|||||||
export function generateMarkdownLinkForAttachment(file: Partial<CustomDropzoneFile>, {width, dppx}: {width?: number, dppx?: number} = {}) {
|
export function generateMarkdownLinkForAttachment(file: Partial<CustomDropzoneFile>, {width, dppx}: {width?: number, dppx?: number} = {}) {
|
||||||
let fileMarkdown = `[${file.name}](/attachments/${file.uuid})`;
|
let fileMarkdown = `[${file.name}](/attachments/${file.uuid})`;
|
||||||
if (isImageFile(file)) {
|
if (isImageFile(file)) {
|
||||||
if (width > 0 && dppx > 1) {
|
if (width && width > 0 && dppx && dppx > 1) {
|
||||||
// Scale down images from HiDPI monitors. This uses the <img> tag because it's the only
|
// Scale down images from HiDPI monitors. This uses the <img> tag because it's the only
|
||||||
// method to change image size in Markdown that is supported by all implementations.
|
// method to change image size in Markdown that is supported by all implementations.
|
||||||
// Make the image link relative to the repo path, then the final URL is "/sub-path/owner/repo/attachments/{uuid}"
|
// Make the image link relative to the repo path, then the final URL is "/sub-path/owner/repo/attachments/{uuid}"
|
||||||
@@ -56,7 +56,7 @@ function addCopyLink(file: Partial<CustomDropzoneFile>) {
|
|||||||
const success = await clippie(generateMarkdownLinkForAttachment(file));
|
const success = await clippie(generateMarkdownLinkForAttachment(file));
|
||||||
showTemporaryTooltip(e.target as Element, success ? i18n.copy_success : i18n.copy_error);
|
showTemporaryTooltip(e.target as Element, success ? i18n.copy_success : i18n.copy_error);
|
||||||
});
|
});
|
||||||
file.previewTemplate.append(copyLinkEl);
|
file.previewTemplate!.append(copyLinkEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileUuidDict = Record<string, {submitted: boolean}>;
|
type FileUuidDict = Record<string, {submitted: boolean}>;
|
||||||
@@ -66,15 +66,15 @@ type FileUuidDict = Record<string, {submitted: boolean}>;
|
|||||||
*/
|
*/
|
||||||
export async function initDropzone(dropzoneEl: HTMLElement) {
|
export async function initDropzone(dropzoneEl: HTMLElement) {
|
||||||
const listAttachmentsUrl = dropzoneEl.closest('[data-attachment-url]')?.getAttribute('data-attachment-url');
|
const listAttachmentsUrl = dropzoneEl.closest('[data-attachment-url]')?.getAttribute('data-attachment-url');
|
||||||
const removeAttachmentUrl = dropzoneEl.getAttribute('data-remove-url');
|
const removeAttachmentUrl = dropzoneEl.getAttribute('data-remove-url')!;
|
||||||
const attachmentBaseLinkUrl = dropzoneEl.getAttribute('data-link-url');
|
const attachmentBaseLinkUrl = dropzoneEl.getAttribute('data-link-url')!;
|
||||||
|
|
||||||
let disableRemovedfileEvent = false; // when resetting the dropzone (removeAllFiles), disable the "removedfile" event
|
let disableRemovedfileEvent = false; // when resetting the dropzone (removeAllFiles), disable the "removedfile" event
|
||||||
let fileUuidDict: FileUuidDict = {}; // to record: if a comment has been saved, then the uploaded files won't be deleted from server when clicking the Remove in the dropzone
|
let fileUuidDict: FileUuidDict = {}; // to record: if a comment has been saved, then the uploaded files won't be deleted from server when clicking the Remove in the dropzone
|
||||||
const opts: Record<string, any> = {
|
const opts: Record<string, any> = {
|
||||||
url: dropzoneEl.getAttribute('data-upload-url'),
|
url: dropzoneEl.getAttribute('data-upload-url'),
|
||||||
headers: {'X-Csrf-Token': csrfToken},
|
headers: {'X-Csrf-Token': csrfToken},
|
||||||
acceptedFiles: ['*/*', ''].includes(dropzoneEl.getAttribute('data-accepts')) ? null : dropzoneEl.getAttribute('data-accepts'),
|
acceptedFiles: ['*/*', ''].includes(dropzoneEl.getAttribute('data-accepts')!) ? null : dropzoneEl.getAttribute('data-accepts'),
|
||||||
addRemoveLinks: true,
|
addRemoveLinks: true,
|
||||||
dictDefaultMessage: dropzoneEl.getAttribute('data-default-message'),
|
dictDefaultMessage: dropzoneEl.getAttribute('data-default-message'),
|
||||||
dictInvalidFileType: dropzoneEl.getAttribute('data-invalid-input-type'),
|
dictInvalidFileType: dropzoneEl.getAttribute('data-invalid-input-type'),
|
||||||
@@ -96,7 +96,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) {
|
|||||||
file.uuid = resp.uuid;
|
file.uuid = resp.uuid;
|
||||||
fileUuidDict[file.uuid] = {submitted: false};
|
fileUuidDict[file.uuid] = {submitted: false};
|
||||||
const input = createElementFromAttrs('input', {name: 'files', type: 'hidden', id: `dropzone-file-${resp.uuid}`, value: resp.uuid});
|
const input = createElementFromAttrs('input', {name: 'files', type: 'hidden', id: `dropzone-file-${resp.uuid}`, value: resp.uuid});
|
||||||
dropzoneEl.querySelector('.files').append(input);
|
dropzoneEl.querySelector('.files')!.append(input);
|
||||||
addCopyLink(file);
|
addCopyLink(file);
|
||||||
dzInst.emit(DropzoneCustomEventUploadDone, {file});
|
dzInst.emit(DropzoneCustomEventUploadDone, {file});
|
||||||
});
|
});
|
||||||
@@ -120,6 +120,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) {
|
|||||||
|
|
||||||
dzInst.on(DropzoneCustomEventReloadFiles, async () => {
|
dzInst.on(DropzoneCustomEventReloadFiles, async () => {
|
||||||
try {
|
try {
|
||||||
|
if (!listAttachmentsUrl) return;
|
||||||
const resp = await GET(listAttachmentsUrl);
|
const resp = await GET(listAttachmentsUrl);
|
||||||
const respData = await resp.json();
|
const respData = await resp.json();
|
||||||
// do not trigger the "removedfile" event, otherwise the attachments would be deleted from server
|
// do not trigger the "removedfile" event, otherwise the attachments would be deleted from server
|
||||||
@@ -127,7 +128,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) {
|
|||||||
dzInst.removeAllFiles(true);
|
dzInst.removeAllFiles(true);
|
||||||
disableRemovedfileEvent = false;
|
disableRemovedfileEvent = false;
|
||||||
|
|
||||||
dropzoneEl.querySelector('.files').innerHTML = '';
|
dropzoneEl.querySelector('.files')!.innerHTML = '';
|
||||||
for (const el of dropzoneEl.querySelectorAll('.dz-preview')) el.remove();
|
for (const el of dropzoneEl.querySelectorAll('.dz-preview')) el.remove();
|
||||||
fileUuidDict = {};
|
fileUuidDict = {};
|
||||||
for (const attachment of respData) {
|
for (const attachment of respData) {
|
||||||
@@ -141,7 +142,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) {
|
|||||||
addCopyLink(file); // it is from server response, so no "type"
|
addCopyLink(file); // it is from server response, so no "type"
|
||||||
fileUuidDict[file.uuid] = {submitted: true};
|
fileUuidDict[file.uuid] = {submitted: true};
|
||||||
const input = createElementFromAttrs('input', {name: 'files', type: 'hidden', id: `dropzone-file-${file.uuid}`, value: file.uuid});
|
const input = createElementFromAttrs('input', {name: 'files', type: 'hidden', id: `dropzone-file-${file.uuid}`, value: file.uuid});
|
||||||
dropzoneEl.querySelector('.files').append(input);
|
dropzoneEl.querySelector('.files')!.append(input);
|
||||||
}
|
}
|
||||||
if (!dropzoneEl.querySelector('.dz-preview')) {
|
if (!dropzoneEl.querySelector('.dz-preview')) {
|
||||||
dropzoneEl.classList.remove('dz-started');
|
dropzoneEl.classList.remove('dz-started');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
class Source {
|
class Source {
|
||||||
url: string;
|
url: string;
|
||||||
eventSource: EventSource;
|
eventSource: EventSource | null;
|
||||||
listening: Record<string, boolean>;
|
listening: Record<string, boolean>;
|
||||||
clients: Array<MessagePort>;
|
clients: Array<MessagePort>;
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ class Source {
|
|||||||
listen(eventType: string) {
|
listen(eventType: string) {
|
||||||
if (this.listening[eventType]) return;
|
if (this.listening[eventType]) return;
|
||||||
this.listening[eventType] = true;
|
this.listening[eventType] = true;
|
||||||
this.eventSource.addEventListener(eventType, (event) => {
|
this.eventSource?.addEventListener(eventType, (event) => {
|
||||||
this.notifyClients({
|
this.notifyClients({
|
||||||
type: eventType,
|
type: eventType,
|
||||||
data: event.data,
|
data: event.data,
|
||||||
@@ -64,7 +64,7 @@ class Source {
|
|||||||
status(port: MessagePort) {
|
status(port: MessagePort) {
|
||||||
port.postMessage({
|
port.postMessage({
|
||||||
type: 'status',
|
type: 'status',
|
||||||
message: `url: ${this.url} readyState: ${this.eventSource.readyState}`,
|
message: `url: ${this.url} readyState: ${this.eventSource?.readyState}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,14 +85,14 @@ self.addEventListener('connect', (e: MessageEvent) => {
|
|||||||
}
|
}
|
||||||
if (event.data.type === 'start') {
|
if (event.data.type === 'start') {
|
||||||
const url = event.data.url;
|
const url = event.data.url;
|
||||||
if (sourcesByUrl.get(url)) {
|
let source = sourcesByUrl.get(url);
|
||||||
|
if (source) {
|
||||||
// we have a Source registered to this url
|
// we have a Source registered to this url
|
||||||
const source = sourcesByUrl.get(url);
|
|
||||||
source.register(port);
|
source.register(port);
|
||||||
sourcesByPort.set(port, source);
|
sourcesByPort.set(port, source);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let source = sourcesByPort.get(port);
|
source = sourcesByPort.get(port);
|
||||||
if (source) {
|
if (source) {
|
||||||
if (source.eventSource && source.url === url) return;
|
if (source.eventSource && source.url === url) return;
|
||||||
|
|
||||||
@@ -111,11 +111,10 @@ self.addEventListener('connect', (e: MessageEvent) => {
|
|||||||
sourcesByUrl.set(url, source);
|
sourcesByUrl.set(url, source);
|
||||||
sourcesByPort.set(port, source);
|
sourcesByPort.set(port, source);
|
||||||
} else if (event.data.type === 'listen') {
|
} else if (event.data.type === 'listen') {
|
||||||
const source = sourcesByPort.get(port);
|
const source = sourcesByPort.get(port)!;
|
||||||
source.listen(event.data.eventType);
|
source.listen(event.data.eventType);
|
||||||
} else if (event.data.type === 'close') {
|
} else if (event.data.type === 'close') {
|
||||||
const source = sourcesByPort.get(port);
|
const source = sourcesByPort.get(port);
|
||||||
|
|
||||||
if (!source) return;
|
if (!source) return;
|
||||||
|
|
||||||
const count = source.deregister(port);
|
const count = source.deregister(port);
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ function findFileRenderPlugin(filename: string, mimeType: string): FileRenderPlu
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showRenderRawFileButton(elFileView: HTMLElement, renderContainer: HTMLElement | null): void {
|
function showRenderRawFileButton(elFileView: HTMLElement, renderContainer: HTMLElement | null): void {
|
||||||
const toggleButtons = elFileView.querySelector('.file-view-toggle-buttons');
|
const toggleButtons = elFileView.querySelector('.file-view-toggle-buttons')!;
|
||||||
showElem(toggleButtons);
|
showElem(toggleButtons);
|
||||||
const displayingRendered = Boolean(renderContainer);
|
const displayingRendered = Boolean(renderContainer);
|
||||||
toggleElemClass(toggleButtons.querySelectorAll('.file-view-toggle-source'), 'active', !displayingRendered); // it may not exist
|
toggleElemClass(toggleButtons.querySelectorAll('.file-view-toggle-source'), 'active', !displayingRendered); // it may not exist
|
||||||
toggleElemClass(toggleButtons.querySelector('.file-view-toggle-rendered'), 'active', displayingRendered);
|
toggleElemClass(toggleButtons.querySelector('.file-view-toggle-rendered')!, 'active', displayingRendered);
|
||||||
// TODO: if there is only one button, hide it?
|
// TODO: if there is only one button, hide it?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ async function renderRawFileToContainer(container: HTMLElement, rawFileLink: str
|
|||||||
export function initRepoFileView(): void {
|
export function initRepoFileView(): void {
|
||||||
registerGlobalInitFunc('initRepoFileView', async (elFileView: HTMLElement) => {
|
registerGlobalInitFunc('initRepoFileView', async (elFileView: HTMLElement) => {
|
||||||
initPluginsOnce();
|
initPluginsOnce();
|
||||||
const rawFileLink = elFileView.getAttribute('data-raw-file-link');
|
const rawFileLink = elFileView.getAttribute('data-raw-file-link')!;
|
||||||
const mimeType = elFileView.getAttribute('data-mime-type') || ''; // not used yet
|
const mimeType = elFileView.getAttribute('data-mime-type') || ''; // not used yet
|
||||||
// TODO: we should also provide the prefetched file head bytes to let the plugin decide whether to render or not
|
// TODO: we should also provide the prefetched file head bytes to let the plugin decide whether to render or not
|
||||||
const plugin = findFileRenderPlugin(basename(rawFileLink), mimeType);
|
const plugin = findFileRenderPlugin(basename(rawFileLink), mimeType);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export function initHeatmap() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const heatmap: Record<string, number> = {};
|
const heatmap: Record<string, number> = {};
|
||||||
for (const {contributions, timestamp} of JSON.parse(el.getAttribute('data-heatmap-data'))) {
|
for (const {contributions, timestamp} of JSON.parse(el.getAttribute('data-heatmap-data')!)) {
|
||||||
// Convert to user timezone and sum contributions by date
|
// Convert to user timezone and sum contributions by date
|
||||||
const dateStr = new Date(timestamp * 1000).toDateString();
|
const dateStr = new Date(timestamp * 1000).toDateString();
|
||||||
heatmap[dateStr] = (heatmap[dateStr] || 0) + contributions;
|
heatmap[dateStr] = (heatmap[dateStr] || 0) + contributions;
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class ImageDiff {
|
|||||||
fomanticQuery(containerEl).find('.ui.menu.tabular .item').tab();
|
fomanticQuery(containerEl).find('.ui.menu.tabular .item').tab();
|
||||||
|
|
||||||
// the container may be hidden by "viewed" checkbox, so use the parent's width for reference
|
// the container may be hidden by "viewed" checkbox, so use the parent's width for reference
|
||||||
this.diffContainerWidth = Math.max(containerEl.closest('.diff-file-box').clientWidth - 300, 100);
|
this.diffContainerWidth = Math.max(containerEl.closest('.diff-file-box')!.clientWidth - 300, 100);
|
||||||
|
|
||||||
const imageInfos = [{
|
const imageInfos = [{
|
||||||
path: containerEl.getAttribute('data-path-after'),
|
path: containerEl.getAttribute('data-path-after'),
|
||||||
@@ -94,20 +94,20 @@ class ImageDiff {
|
|||||||
|
|
||||||
await Promise.all(imageInfos.map(async (info) => {
|
await Promise.all(imageInfos.map(async (info) => {
|
||||||
const [success] = await Promise.all(Array.from(info.images, (img) => {
|
const [success] = await Promise.all(Array.from(info.images, (img) => {
|
||||||
return loadElem(img, info.path);
|
return loadElem(img, info.path!);
|
||||||
}));
|
}));
|
||||||
// only the first images is associated with boundsInfo
|
// only the first images is associated with boundsInfo
|
||||||
if (!success && info.boundsInfo) info.boundsInfo.textContent = '(image error)';
|
if (!success && info.boundsInfo) info.boundsInfo.textContent = '(image error)';
|
||||||
if (info.mime === 'image/svg+xml') {
|
if (info.mime === 'image/svg+xml') {
|
||||||
const resp = await GET(info.path);
|
const resp = await GET(info.path!);
|
||||||
const text = await resp.text();
|
const text = await resp.text();
|
||||||
const bounds = getDefaultSvgBoundsIfUndefined(text, info.path);
|
const bounds = getDefaultSvgBoundsIfUndefined(text, info.path!);
|
||||||
if (bounds) {
|
if (bounds) {
|
||||||
for (const el of info.images) {
|
for (const el of info.images) {
|
||||||
el.setAttribute('width', String(bounds.width));
|
el.setAttribute('width', String(bounds.width));
|
||||||
el.setAttribute('height', String(bounds.height));
|
el.setAttribute('height', String(bounds.height));
|
||||||
}
|
}
|
||||||
hideElem(info.boundsInfo);
|
hideElem(info.boundsInfo!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -213,7 +213,7 @@ class ImageDiff {
|
|||||||
swipe.style.height = `${sizes.maxSize.height * factor + 30}px`;
|
swipe.style.height = `${sizes.maxSize.height * factor + 30}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.containerEl.querySelector('.swipe-bar').addEventListener('mousedown', (e) => {
|
this.containerEl.querySelector('.swipe-bar')!.addEventListener('mousedown', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.initSwipeEventListeners(e.currentTarget as HTMLElement);
|
this.initSwipeEventListeners(e.currentTarget as HTMLElement);
|
||||||
});
|
});
|
||||||
@@ -227,7 +227,7 @@ class ImageDiff {
|
|||||||
const rect = swipeFrame.getBoundingClientRect();
|
const rect = swipeFrame.getBoundingClientRect();
|
||||||
const value = Math.max(0, Math.min(e.clientX - rect.left, width));
|
const value = Math.max(0, Math.min(e.clientX - rect.left, width));
|
||||||
swipeBar.style.left = `${value}px`;
|
swipeBar.style.left = `${value}px`;
|
||||||
this.containerEl.querySelector<HTMLElement>('.swipe-container').style.width = `${swipeFrame.clientWidth - value}px`;
|
this.containerEl.querySelector<HTMLElement>('.swipe-container')!.style.width = `${swipeFrame.clientWidth - value}px`;
|
||||||
};
|
};
|
||||||
const removeEventListeners = () => {
|
const removeEventListeners = () => {
|
||||||
document.removeEventListener('mousemove', onSwipeMouseMove);
|
document.removeEventListener('mousemove', onSwipeMouseMove);
|
||||||
@@ -266,7 +266,7 @@ class ImageDiff {
|
|||||||
overlayFrame.style.height = `${sizes.maxSize.height * factor + 2}px`;
|
overlayFrame.style.height = `${sizes.maxSize.height * factor + 2}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rangeInput = this.containerEl.querySelector<HTMLInputElement>('input[type="range"]');
|
const rangeInput = this.containerEl.querySelector<HTMLInputElement>('input[type="range"]')!;
|
||||||
|
|
||||||
function updateOpacity() {
|
function updateOpacity() {
|
||||||
if (sizes.imageAfter) {
|
if (sizes.imageAfter) {
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ function initPreInstall() {
|
|||||||
mssql: '127.0.0.1:1433',
|
mssql: '127.0.0.1:1433',
|
||||||
};
|
};
|
||||||
|
|
||||||
const dbHost = document.querySelector<HTMLInputElement>('#db_host');
|
const dbHost = document.querySelector<HTMLInputElement>('#db_host')!;
|
||||||
const dbUser = document.querySelector<HTMLInputElement>('#db_user');
|
const dbUser = document.querySelector<HTMLInputElement>('#db_user')!;
|
||||||
const dbName = document.querySelector<HTMLInputElement>('#db_name');
|
const dbName = document.querySelector<HTMLInputElement>('#db_name')!;
|
||||||
|
|
||||||
// Database type change detection.
|
// Database type change detection.
|
||||||
document.querySelector<HTMLInputElement>('#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}]`);
|
||||||
@@ -47,58 +47,58 @@ function initPreInstall() {
|
|||||||
}
|
}
|
||||||
} // else: for SQLite3, the default path is always prepared by backend code (setting)
|
} // else: for SQLite3, the default path is always prepared by backend code (setting)
|
||||||
});
|
});
|
||||||
document.querySelector('#db_type').dispatchEvent(new Event('change'));
|
document.querySelector('#db_type')!.dispatchEvent(new Event('change'));
|
||||||
|
|
||||||
const appUrl = document.querySelector<HTMLInputElement>('#app_url');
|
const appUrl = document.querySelector<HTMLInputElement>('#app_url')!;
|
||||||
if (appUrl.value.includes('://localhost')) {
|
if (appUrl.value.includes('://localhost')) {
|
||||||
appUrl.value = window.location.href;
|
appUrl.value = window.location.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domain = document.querySelector<HTMLInputElement>('#domain');
|
const domain = document.querySelector<HTMLInputElement>('#domain')!;
|
||||||
if (domain.value.trim() === 'localhost') {
|
if (domain.value.trim() === 'localhost') {
|
||||||
domain.value = window.location.hostname;
|
domain.value = window.location.hostname;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: better handling of exclusive relations.
|
// TODO: better handling of exclusive relations.
|
||||||
document.querySelector<HTMLInputElement>('#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<HTMLInputElement>('#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<HTMLInputElement>('#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<HTMLInputElement>('#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;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = false;
|
document.querySelector<HTMLInputElement>('#enable-openid-signup input')!.checked = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.querySelector<HTMLInputElement>('#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;
|
||||||
} else {
|
} else {
|
||||||
document.querySelector<HTMLInputElement>('#enable-openid-signup input').checked = true;
|
document.querySelector<HTMLInputElement>('#enable-openid-signup input')!.checked = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.querySelector<HTMLInputElement>('#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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -107,8 +107,8 @@ function initPostInstall() {
|
|||||||
const el = document.querySelector('#goto-after-install');
|
const el = document.querySelector('#goto-after-install');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
const targetUrl = el.getAttribute('href');
|
const targetUrl = el.getAttribute('href')!;
|
||||||
let tid = setInterval(async () => {
|
let tid: ReturnType<typeof setInterval> | null = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
const resp = await GET(targetUrl);
|
const resp = await GET(targetUrl);
|
||||||
if (tid && resp.status === 200) {
|
if (tid && resp.status === 200) {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export function initNotificationCount() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentCount() {
|
function getCurrentCount() {
|
||||||
return Number(document.querySelector('.notification_count').textContent ?? '0');
|
return Number(document.querySelector('.notification_count')!.textContent ?? '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateNotificationCountWithCallback(callback: (timeout: number, newCount: number) => void, timeout: number, lastCount: number) {
|
async function updateNotificationCountWithCallback(callback: (timeout: number, newCount: number) => void, timeout: number, lastCount: number) {
|
||||||
@@ -131,9 +131,9 @@ async function updateNotificationTable() {
|
|||||||
|
|
||||||
const data = await response.text();
|
const data = await response.text();
|
||||||
const el = createElementFromHTML(data);
|
const el = createElementFromHTML(data);
|
||||||
if (parseInt(el.getAttribute('data-sequence-number')) === notificationSequenceNumber) {
|
if (parseInt(el.getAttribute('data-sequence-number')!) === notificationSequenceNumber) {
|
||||||
notificationDiv.outerHTML = data;
|
notificationDiv.outerHTML = data;
|
||||||
notificationDiv = document.querySelector('#notification_div');
|
notificationDiv = document.querySelector('#notification_div')!;
|
||||||
window.htmx.process(notificationDiv); // when using htmx, we must always remember to process the new content changed by us
|
window.htmx.process(notificationDiv); // when using htmx, we must always remember to process the new content changed by us
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type {DOMEvent} from '../utils/dom.ts';
|
|
||||||
|
|
||||||
export function initOAuth2SettingsDisableCheckbox() {
|
export function initOAuth2SettingsDisableCheckbox() {
|
||||||
for (const el of document.querySelectorAll<HTMLInputElement>('.disable-setting')) {
|
for (const el of document.querySelectorAll<HTMLInputElement>('.disable-setting')) {
|
||||||
el.addEventListener('change', (e: DOMEvent<Event, HTMLInputElement>) => {
|
el.addEventListener('change', (e) => {
|
||||||
document.querySelector(e.target.getAttribute('data-target')).classList.toggle('disabled', e.target.checked);
|
const target = e.target as HTMLInputElement;
|
||||||
|
const dataTarget = target.getAttribute('data-target')!;
|
||||||
|
document.querySelector(dataTarget)!.classList.toggle('disabled', target.checked);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ const collapseFilesBtnSelector = '#collapse-files-btn';
|
|||||||
function refreshViewedFilesSummary() {
|
function refreshViewedFilesSummary() {
|
||||||
const viewedFilesProgress = document.querySelector('#viewed-files-summary');
|
const viewedFilesProgress = document.querySelector('#viewed-files-summary');
|
||||||
viewedFilesProgress?.setAttribute('value', prReview.numberOfViewedFiles);
|
viewedFilesProgress?.setAttribute('value', prReview.numberOfViewedFiles);
|
||||||
const summaryLabel = document.querySelector('#viewed-files-summary-label');
|
const summaryLabel = document.querySelector('#viewed-files-summary-label')!;
|
||||||
if (summaryLabel) summaryLabel.innerHTML = summaryLabel.getAttribute('data-text-changed-template')
|
if (summaryLabel) summaryLabel.innerHTML = summaryLabel.getAttribute('data-text-changed-template')!
|
||||||
.replace('%[1]d', prReview.numberOfViewedFiles)
|
.replace('%[1]d', prReview.numberOfViewedFiles)
|
||||||
.replace('%[2]d', prReview.numberOfFiles);
|
.replace('%[2]d', prReview.numberOfFiles);
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,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<HTMLInputElement>('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) {
|
||||||
@@ -45,10 +45,10 @@ export function initViewedCheckboxListenerFor() {
|
|||||||
|
|
||||||
// Update viewed-files summary and remove "has changed" label if present
|
// Update viewed-files summary and remove "has changed" label if present
|
||||||
refreshViewedFilesSummary();
|
refreshViewedFilesSummary();
|
||||||
const hasChangedLabel = form.parentNode.querySelector('.changed-since-last-review');
|
const hasChangedLabel = form.parentNode!.querySelector('.changed-since-last-review');
|
||||||
hasChangedLabel?.remove();
|
hasChangedLabel?.remove();
|
||||||
|
|
||||||
const fileName = checkbox.getAttribute('name');
|
const fileName = checkbox.getAttribute('name')!;
|
||||||
|
|
||||||
// check if the file is in our diffTreeStore and if we find it -> change the IsViewed status
|
// check if the file is in our diffTreeStore and if we find it -> change the IsViewed status
|
||||||
diffTreeStoreSetViewed(diffTreeStore(), fileName, this.checked);
|
diffTreeStoreSetViewed(diffTreeStore(), fileName, this.checked);
|
||||||
@@ -59,11 +59,11 @@ export function initViewedCheckboxListenerFor() {
|
|||||||
const data: Record<string, any> = {files};
|
const data: Record<string, any> = {files};
|
||||||
const headCommitSHA = form.getAttribute('data-headcommit');
|
const headCommitSHA = form.getAttribute('data-headcommit');
|
||||||
if (headCommitSHA) data.headCommitSHA = headCommitSHA;
|
if (headCommitSHA) data.headCommitSHA = headCommitSHA;
|
||||||
POST(form.getAttribute('data-link'), {data});
|
POST(form.getAttribute('data-link')!, {data});
|
||||||
|
|
||||||
// Fold the file accordingly
|
// Fold the file accordingly
|
||||||
const parentBox = form.closest('.diff-file-header');
|
const parentBox = form.closest('.diff-file-header')!;
|
||||||
setFileFolding(parentBox.closest('.file-content'), parentBox.querySelector('.fold-file'), this.checked);
|
setFileFolding(parentBox.closest('.file-content')!, parentBox.querySelector('.fold-file')!, this.checked);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,14 +72,14 @@ export function initExpandAndCollapseFilesButton() {
|
|||||||
// expand btn
|
// expand btn
|
||||||
document.querySelector(expandFilesBtnSelector)?.addEventListener('click', () => {
|
document.querySelector(expandFilesBtnSelector)?.addEventListener('click', () => {
|
||||||
for (const box of document.querySelectorAll<HTMLElement>('.file-content[data-folded="true"]')) {
|
for (const box of document.querySelectorAll<HTMLElement>('.file-content[data-folded="true"]')) {
|
||||||
setFileFolding(box, box.querySelector('.fold-file'), false);
|
setFileFolding(box, box.querySelector('.fold-file')!, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// collapse btn, need to exclude the div of “show more”
|
// collapse btn, need to exclude the div of “show more”
|
||||||
document.querySelector(collapseFilesBtnSelector)?.addEventListener('click', () => {
|
document.querySelector(collapseFilesBtnSelector)?.addEventListener('click', () => {
|
||||||
for (const box of document.querySelectorAll<HTMLElement>('.file-content:not([data-folded="true"])')) {
|
for (const box of document.querySelectorAll<HTMLElement>('.file-content:not([data-folded="true"])')) {
|
||||||
if (box.getAttribute('id') === 'diff-incomplete') continue;
|
if (box.getAttribute('id') === 'diff-incomplete') continue;
|
||||||
setFileFolding(box, box.querySelector('.fold-file'), true);
|
setFileFolding(box, box.querySelector('.fold-file')!, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ function initRepoCreateBranchButton() {
|
|||||||
modalForm.action = `${modalForm.getAttribute('data-base-action')}${el.getAttribute('data-branch-from-urlcomponent')}`;
|
modalForm.action = `${modalForm.getAttribute('data-base-action')}${el.getAttribute('data-branch-from-urlcomponent')}`;
|
||||||
|
|
||||||
const fromSpanName = el.getAttribute('data-modal-from-span') || '#modal-create-branch-from-span';
|
const fromSpanName = el.getAttribute('data-modal-from-span') || '#modal-create-branch-from-span';
|
||||||
document.querySelector(fromSpanName).textContent = el.getAttribute('data-branch-from');
|
document.querySelector(fromSpanName)!.textContent = el.getAttribute('data-branch-from');
|
||||||
|
|
||||||
fomanticQuery(el.getAttribute('data-modal')).modal('show');
|
fomanticQuery(el.getAttribute('data-modal')!).modal('show');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,17 +26,17 @@ function initRepoCreateBranchButton() {
|
|||||||
function initRepoRenameBranchButton() {
|
function initRepoRenameBranchButton() {
|
||||||
for (const el of document.querySelectorAll('.show-rename-branch-modal')) {
|
for (const el of document.querySelectorAll('.show-rename-branch-modal')) {
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
const target = el.getAttribute('data-modal');
|
const target = el.getAttribute('data-modal')!;
|
||||||
const modal = document.querySelector(target);
|
const modal = document.querySelector(target)!;
|
||||||
const oldBranchName = el.getAttribute('data-old-branch-name');
|
const oldBranchName = el.getAttribute('data-old-branch-name')!;
|
||||||
modal.querySelector<HTMLInputElement>('input[name=from]').value = oldBranchName;
|
modal.querySelector<HTMLInputElement>('input[name=from]')!.value = oldBranchName;
|
||||||
|
|
||||||
// display the warning that the branch which is chosen is the default branch
|
// display the warning that the branch which is chosen is the default branch
|
||||||
const warn = modal.querySelector('.default-branch-warning');
|
const warn = modal.querySelector('.default-branch-warning')!;
|
||||||
toggleElem(warn, el.getAttribute('data-is-default-branch') === 'true');
|
toggleElem(warn, el.getAttribute('data-is-default-branch') === 'true');
|
||||||
|
|
||||||
const text = modal.querySelector('[data-rename-branch-to]');
|
const text = modal.querySelector('[data-rename-branch-to]')!;
|
||||||
text.textContent = text.getAttribute('data-rename-branch-to').replace('%s', oldBranchName);
|
text.textContent = text.getAttribute('data-rename-branch-to')!.replace('%s', oldBranchName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import {addDelegatedEventListener} from '../utils/dom.ts';
|
|||||||
|
|
||||||
function changeHash(hash: string) {
|
function changeHash(hash: string) {
|
||||||
if (window.history.pushState) {
|
if (window.history.pushState) {
|
||||||
window.history.pushState(null, null, hash);
|
window.history.pushState(null, '', hash);
|
||||||
} else {
|
} else {
|
||||||
window.location.hash = hash;
|
window.location.hash = hash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// it selects the code lines defined by range: `L1-L3` (3 lines) or `L2` (singe line)
|
// it selects the code lines defined by range: `L1-L3` (3 lines) or `L2` (singe line)
|
||||||
function selectRange(range: string): Element {
|
function selectRange(range: string): Element | null {
|
||||||
for (const el of document.querySelectorAll('.code-view tr.active')) el.classList.remove('active');
|
for (const el of document.querySelectorAll('.code-view tr.active')) el.classList.remove('active');
|
||||||
const elLineNums = document.querySelectorAll(`.code-view td.lines-num span[data-line-number]`);
|
const elLineNums = document.querySelectorAll(`.code-view td.lines-num span[data-line-number]`);
|
||||||
|
|
||||||
@@ -23,14 +23,14 @@ function selectRange(range: string): Element {
|
|||||||
const updateIssueHref = function (anchor: string) {
|
const updateIssueHref = function (anchor: string) {
|
||||||
if (!refInNewIssue) return;
|
if (!refInNewIssue) return;
|
||||||
const urlIssueNew = refInNewIssue.getAttribute('data-url-issue-new');
|
const urlIssueNew = refInNewIssue.getAttribute('data-url-issue-new');
|
||||||
const urlParamBodyLink = refInNewIssue.getAttribute('data-url-param-body-link');
|
const urlParamBodyLink = refInNewIssue.getAttribute('data-url-param-body-link')!;
|
||||||
const issueContent = `${toAbsoluteUrl(urlParamBodyLink)}#${anchor}`; // the default content for issue body
|
const issueContent = `${toAbsoluteUrl(urlParamBodyLink)}#${anchor}`; // the default content for issue body
|
||||||
refInNewIssue.setAttribute('href', `${urlIssueNew}?body=${encodeURIComponent(issueContent)}`);
|
refInNewIssue.setAttribute('href', `${urlIssueNew}?body=${encodeURIComponent(issueContent)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateViewGitBlameFragment = function (anchor: string) {
|
const updateViewGitBlameFragment = function (anchor: string) {
|
||||||
if (!viewGitBlame) return;
|
if (!viewGitBlame) return;
|
||||||
let href = viewGitBlame.getAttribute('href');
|
let href = viewGitBlame.getAttribute('href')!;
|
||||||
href = `${href.replace(/#L\d+$|#L\d+-L\d+$/, '')}`;
|
href = `${href.replace(/#L\d+$|#L\d+-L\d+$/, '')}`;
|
||||||
if (anchor.length !== 0) {
|
if (anchor.length !== 0) {
|
||||||
href = `${href}#${anchor}`;
|
href = `${href}#${anchor}`;
|
||||||
@@ -40,7 +40,7 @@ function selectRange(range: string): Element {
|
|||||||
|
|
||||||
const updateCopyPermalinkUrl = function (anchor: string) {
|
const updateCopyPermalinkUrl = function (anchor: string) {
|
||||||
if (!copyPermalink) return;
|
if (!copyPermalink) return;
|
||||||
let link = copyPermalink.getAttribute('data-url');
|
let link = copyPermalink.getAttribute('data-url')!;
|
||||||
link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`;
|
link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`;
|
||||||
copyPermalink.setAttribute('data-clipboard-text', link);
|
copyPermalink.setAttribute('data-clipboard-text', link);
|
||||||
copyPermalink.setAttribute('data-clipboard-text-type', 'url');
|
copyPermalink.setAttribute('data-clipboard-text-type', 'url');
|
||||||
@@ -63,7 +63,7 @@ function selectRange(range: string): Element {
|
|||||||
|
|
||||||
const first = elLineNums[startLineNum - 1] ?? null;
|
const first = elLineNums[startLineNum - 1] ?? null;
|
||||||
for (let i = startLineNum - 1; i <= stopLineNum - 1 && i < elLineNums.length; i++) {
|
for (let i = startLineNum - 1; i <= stopLineNum - 1 && i < elLineNums.length; i++) {
|
||||||
elLineNums[i].closest('tr').classList.add('active');
|
elLineNums[i].closest('tr')!.classList.add('active');
|
||||||
}
|
}
|
||||||
changeHash(`#${range}`);
|
changeHash(`#${range}`);
|
||||||
updateIssueHref(range);
|
updateIssueHref(range);
|
||||||
@@ -85,14 +85,14 @@ function showLineButton() {
|
|||||||
const tr = document.querySelector('.code-view tr.active');
|
const tr = document.querySelector('.code-view tr.active');
|
||||||
if (!tr) return;
|
if (!tr) return;
|
||||||
|
|
||||||
const td = tr.querySelector('td.lines-num');
|
const td = tr.querySelector('td.lines-num')!;
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.classList.add('code-line-button', 'ui', 'basic', 'button');
|
btn.classList.add('code-line-button', 'ui', 'basic', 'button');
|
||||||
btn.innerHTML = svg('octicon-kebab-horizontal');
|
btn.innerHTML = svg('octicon-kebab-horizontal');
|
||||||
td.prepend(btn);
|
td.prepend(btn);
|
||||||
|
|
||||||
// put a copy of the menu back into DOM for the next click
|
// put a copy of the menu back into DOM for the next click
|
||||||
btn.closest('.code-view').append(menu.cloneNode(true));
|
btn.closest('.code-view')!.append(menu.cloneNode(true));
|
||||||
|
|
||||||
createTippy(btn, {
|
createTippy(btn, {
|
||||||
theme: 'menu',
|
theme: 'menu',
|
||||||
@@ -117,16 +117,16 @@ export function initRepoCodeView() {
|
|||||||
if (!document.querySelector('.repo-view-container .file-view')) return;
|
if (!document.querySelector('.repo-view-container .file-view')) return;
|
||||||
|
|
||||||
// "file code view" and "blame" pages need this "line number button" feature
|
// "file code view" and "blame" pages need this "line number button" feature
|
||||||
let selRangeStart: string;
|
let selRangeStart: string | undefined;
|
||||||
addDelegatedEventListener(document, 'click', '.code-view .lines-num span', (el: HTMLElement, e: KeyboardEvent) => {
|
addDelegatedEventListener(document, 'click', '.code-view .lines-num span', (el: HTMLElement, e: KeyboardEvent) => {
|
||||||
if (!selRangeStart || !e.shiftKey) {
|
if (!selRangeStart || !e.shiftKey) {
|
||||||
selRangeStart = el.getAttribute('id');
|
selRangeStart = el.getAttribute('id')!;
|
||||||
selectRange(selRangeStart);
|
selectRange(selRangeStart);
|
||||||
} else {
|
} else {
|
||||||
const selRangeStop = el.getAttribute('id');
|
const selRangeStop = el.getAttribute('id');
|
||||||
selectRange(`${selRangeStart}-${selRangeStop}`);
|
selectRange(`${selRangeStart}-${selRangeStop}`);
|
||||||
}
|
}
|
||||||
window.getSelection().removeAllRanges();
|
window.getSelection()!.removeAllRanges();
|
||||||
showLineButton();
|
showLineButton();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ export function initRepoEllipsisButton() {
|
|||||||
registerGlobalEventFunc('click', 'onRepoEllipsisButtonClick', async (el: HTMLInputElement, e: Event) => {
|
registerGlobalEventFunc('click', 'onRepoEllipsisButtonClick', async (el: HTMLInputElement, e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const expanded = el.getAttribute('aria-expanded') === 'true';
|
const expanded = el.getAttribute('aria-expanded') === 'true';
|
||||||
toggleElem(el.parentElement.querySelector('.commit-body'));
|
toggleElem(el.parentElement!.querySelector('.commit-body')!);
|
||||||
el.setAttribute('aria-expanded', String(!expanded));
|
el.setAttribute('aria-expanded', String(!expanded));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initCommitStatuses() {
|
export function initCommitStatuses() {
|
||||||
registerGlobalInitFunc('initCommitStatuses', (el: HTMLElement) => {
|
registerGlobalInitFunc('initCommitStatuses', (el: HTMLElement) => {
|
||||||
const nextEl = el.nextElementSibling;
|
const nextEl = el.nextElementSibling!;
|
||||||
if (!nextEl.matches('.tippy-target')) throw new Error('Expected next element to be a tippy target');
|
if (!nextEl.matches('.tippy-target')) throw new Error('Expected next element to be a tippy target');
|
||||||
createTippy(el, {
|
createTippy(el, {
|
||||||
content: nextEl,
|
content: nextEl,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {queryElems, type DOMEvent} from '../utils/dom.ts';
|
import {queryElems} from '../utils/dom.ts';
|
||||||
import {POST} from '../modules/fetch.ts';
|
import {POST} from '../modules/fetch.ts';
|
||||||
import {showErrorToast} from '../modules/toast.ts';
|
import {showErrorToast} from '../modules/toast.ts';
|
||||||
import {sleep} from '../utils.ts';
|
import {sleep} from '../utils.ts';
|
||||||
@@ -7,10 +7,10 @@ import {createApp} from 'vue';
|
|||||||
import {toOriginUrl} from '../utils/url.ts';
|
import {toOriginUrl} from '../utils/url.ts';
|
||||||
import {createTippy} from '../modules/tippy.ts';
|
import {createTippy} from '../modules/tippy.ts';
|
||||||
|
|
||||||
async function onDownloadArchive(e: DOMEvent<MouseEvent>) {
|
async function onDownloadArchive(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// there are many places using the "archive-link", eg: the dropdown on the repo code page, the release list
|
// there are many places using the "archive-link", eg: the dropdown on the repo code page, the release list
|
||||||
const el = e.target.closest<HTMLAnchorElement>('a.archive-link[href]');
|
const el = (e.target as HTMLElement).closest<HTMLAnchorElement>('a.archive-link[href]')!;
|
||||||
const targetLoading = el.closest('.ui.dropdown') ?? el;
|
const targetLoading = el.closest('.ui.dropdown') ?? el;
|
||||||
targetLoading.classList.add('is-loading', 'loading-icon-2px');
|
targetLoading.classList.add('is-loading', 'loading-icon-2px');
|
||||||
try {
|
try {
|
||||||
@@ -51,13 +51,13 @@ export function substituteRepoOpenWithUrl(tmpl: string, url: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initCloneSchemeUrlSelection(parent: Element) {
|
function initCloneSchemeUrlSelection(parent: Element) {
|
||||||
const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url');
|
const elCloneUrlInput = parent.querySelector<HTMLInputElement>('.repo-clone-url')!;
|
||||||
|
|
||||||
const tabHttps = parent.querySelector('.repo-clone-https');
|
const tabHttps = parent.querySelector('.repo-clone-https');
|
||||||
const tabSsh = parent.querySelector('.repo-clone-ssh');
|
const tabSsh = parent.querySelector('.repo-clone-ssh');
|
||||||
const tabTea = parent.querySelector('.repo-clone-tea');
|
const tabTea = parent.querySelector('.repo-clone-tea');
|
||||||
const updateClonePanelUi = function() {
|
const updateClonePanelUi = function() {
|
||||||
let scheme = localStorage.getItem('repo-clone-protocol');
|
let scheme = localStorage.getItem('repo-clone-protocol')!;
|
||||||
if (!['https', 'ssh', 'tea'].includes(scheme)) {
|
if (!['https', 'ssh', 'tea'].includes(scheme)) {
|
||||||
scheme = 'https';
|
scheme = 'https';
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ function initCloneSchemeUrlSelection(parent: Element) {
|
|||||||
tabTea.classList.toggle('active', isTea);
|
tabTea.classList.toggle('active', isTea);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tab: Element;
|
let tab: Element | null = null;
|
||||||
if (isHttps) {
|
if (isHttps) {
|
||||||
tab = tabHttps;
|
tab = tabHttps;
|
||||||
} else if (isSsh) {
|
} else if (isSsh) {
|
||||||
@@ -97,7 +97,7 @@ function initCloneSchemeUrlSelection(parent: Element) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!tab) return;
|
if (!tab) return;
|
||||||
const link = toOriginUrl(tab.getAttribute('data-link'));
|
const link = toOriginUrl(tab.getAttribute('data-link')!);
|
||||||
|
|
||||||
for (const el of document.querySelectorAll('.js-clone-url')) {
|
for (const el of document.querySelectorAll('.js-clone-url')) {
|
||||||
if (el.nodeName === 'INPUT') {
|
if (el.nodeName === 'INPUT') {
|
||||||
@@ -107,7 +107,7 @@ function initCloneSchemeUrlSelection(parent: Element) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const el of parent.querySelectorAll<HTMLAnchorElement>('.js-clone-url-editor')) {
|
for (const el of parent.querySelectorAll<HTMLAnchorElement>('.js-clone-url-editor')) {
|
||||||
el.href = substituteRepoOpenWithUrl(el.getAttribute('data-href-template'), link);
|
el.href = substituteRepoOpenWithUrl(el.getAttribute('data-href-template')!, link);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ function initCloneSchemeUrlSelection(parent: Element) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initClonePanelButton(btn: HTMLButtonElement) {
|
function initClonePanelButton(btn: HTMLButtonElement) {
|
||||||
const elPanel = btn.nextElementSibling;
|
const elPanel = btn.nextElementSibling!;
|
||||||
// "init" must be before the "createTippy" otherwise the "tippy-target" will be removed from the document
|
// "init" must be before the "createTippy" otherwise the "tippy-target" will be removed from the document
|
||||||
initCloneSchemeUrlSelection(elPanel);
|
initCloneSchemeUrlSelection(elPanel);
|
||||||
createTippy(btn, {
|
createTippy(btn, {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {GET} from '../modules/fetch.ts';
|
|||||||
async function loadBranchesAndTags(area: Element, loadingButton: Element) {
|
async function loadBranchesAndTags(area: Element, loadingButton: Element) {
|
||||||
loadingButton.classList.add('disabled');
|
loadingButton.classList.add('disabled');
|
||||||
try {
|
try {
|
||||||
const res = await GET(loadingButton.getAttribute('data-fetch-url'));
|
const res = await GET(loadingButton.getAttribute('data-fetch-url')!);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
hideElem(loadingButton);
|
hideElem(loadingButton);
|
||||||
addTags(area, data.tags);
|
addTags(area, data.tags);
|
||||||
@@ -16,8 +16,8 @@ async function loadBranchesAndTags(area: Element, loadingButton: Element) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addTags(area: Element, tags: Array<Record<string, any>>) {
|
function addTags(area: Element, tags: Array<Record<string, any>>) {
|
||||||
const tagArea = area.querySelector('.tag-area');
|
const tagArea = area.querySelector('.tag-area')!;
|
||||||
toggleElem(tagArea.parentElement, tags.length > 0);
|
toggleElem(tagArea.parentElement!, tags.length > 0);
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
addLink(tagArea, tag.web_link, tag.name);
|
addLink(tagArea, tag.web_link, tag.name);
|
||||||
}
|
}
|
||||||
@@ -25,15 +25,15 @@ function addTags(area: Element, tags: Array<Record<string, any>>) {
|
|||||||
|
|
||||||
function addBranches(area: Element, branches: Array<Record<string, any>>, defaultBranch: string) {
|
function addBranches(area: Element, branches: Array<Record<string, any>>, defaultBranch: string) {
|
||||||
const defaultBranchTooltip = area.getAttribute('data-text-default-branch-tooltip');
|
const defaultBranchTooltip = area.getAttribute('data-text-default-branch-tooltip');
|
||||||
const branchArea = area.querySelector('.branch-area');
|
const branchArea = area.querySelector('.branch-area')!;
|
||||||
toggleElem(branchArea.parentElement, branches.length > 0);
|
toggleElem(branchArea.parentElement!, branches.length > 0);
|
||||||
for (const branch of branches) {
|
for (const branch of branches) {
|
||||||
const tooltip = defaultBranch === branch.name ? defaultBranchTooltip : null;
|
const tooltip = defaultBranch === branch.name ? defaultBranchTooltip : null;
|
||||||
addLink(branchArea, branch.web_link, branch.name, tooltip);
|
addLink(branchArea, branch.web_link, branch.name, tooltip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLink(parent: Element, href: string, text: string, tooltip?: string) {
|
function addLink(parent: Element, href: string, text: string, tooltip: string | null = null) {
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.classList.add('muted', 'tw-px-1');
|
link.classList.add('muted', 'tw-px-1');
|
||||||
link.href = href;
|
link.href = href;
|
||||||
@@ -47,7 +47,7 @@ function addLink(parent: Element, href: string, text: string, tooltip?: string)
|
|||||||
|
|
||||||
export function initRepoDiffCommitBranchesAndTags() {
|
export function initRepoDiffCommitBranchesAndTags() {
|
||||||
for (const area of document.querySelectorAll('.branch-and-tag-area')) {
|
for (const area of document.querySelectorAll('.branch-and-tag-area')) {
|
||||||
const btn = area.querySelector('.load-branches-and-tags');
|
const btn = area.querySelector('.load-branches-and-tags')!;
|
||||||
btn.addEventListener('click', () => loadBranchesAndTags(area, btn));
|
btn.addEventListener('click', () => loadBranchesAndTags(area, btn));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function initRepoDiffFileBox(el: HTMLElement) {
|
|||||||
queryElemSiblings(btn, '.file-view-toggle', (el) => el.classList.remove('active'));
|
queryElemSiblings(btn, '.file-view-toggle', (el) => el.classList.remove('active'));
|
||||||
btn.classList.add('active');
|
btn.classList.add('active');
|
||||||
|
|
||||||
const target = document.querySelector(btn.getAttribute('data-toggle-selector'));
|
const target = document.querySelector(btn.getAttribute('data-toggle-selector')!);
|
||||||
if (!target) throw new Error('Target element not found');
|
if (!target) throw new Error('Target element not found');
|
||||||
|
|
||||||
hideElem(queryElemSiblings(target));
|
hideElem(queryElemSiblings(target));
|
||||||
@@ -31,7 +31,7 @@ function initRepoDiffConversationForm() {
|
|||||||
// This listener is for "reply form" only, it should clearly distinguish different forms in the future.
|
// This listener is for "reply form" only, it should clearly distinguish different forms in the future.
|
||||||
addDelegatedEventListener<HTMLFormElement, SubmitEvent>(document, 'submit', '.conversation-holder form', async (form, e) => {
|
addDelegatedEventListener<HTMLFormElement, SubmitEvent>(document, 'submit', '.conversation-holder form', async (form, e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const textArea = form.querySelector<HTMLTextAreaElement>('textarea');
|
const textArea = form.querySelector<HTMLTextAreaElement>('textarea')!;
|
||||||
if (!validateTextareaNonEmpty(textArea)) return;
|
if (!validateTextareaNonEmpty(textArea)) return;
|
||||||
if (form.classList.contains('is-loading')) return;
|
if (form.classList.contains('is-loading')) return;
|
||||||
|
|
||||||
@@ -49,14 +49,15 @@ function initRepoDiffConversationForm() {
|
|||||||
// on the diff page, the form is inside a "tr" and need to get the line-type ahead
|
// on the diff page, the form is inside a "tr" and need to get the line-type ahead
|
||||||
// but on the conversation page, there is no parent "tr"
|
// but on the conversation page, there is no parent "tr"
|
||||||
const trLineType = form.closest('tr')?.getAttribute('data-line-type');
|
const trLineType = form.closest('tr')?.getAttribute('data-line-type');
|
||||||
const response = await POST(form.getAttribute('action'), {data: formData});
|
const response = await POST(form.getAttribute('action')!, {data: formData});
|
||||||
const newConversationHolder = createElementFromHTML(await response.text());
|
const newConversationHolder = createElementFromHTML(await response.text());
|
||||||
const path = newConversationHolder.getAttribute('data-path');
|
const path = newConversationHolder.getAttribute('data-path');
|
||||||
const side = newConversationHolder.getAttribute('data-side');
|
const side = newConversationHolder.getAttribute('data-side');
|
||||||
const idx = newConversationHolder.getAttribute('data-idx');
|
const idx = newConversationHolder.getAttribute('data-idx');
|
||||||
|
|
||||||
form.closest('.conversation-holder').replaceWith(newConversationHolder);
|
form.closest('.conversation-holder')!.replaceWith(newConversationHolder);
|
||||||
form = null; // prevent further usage of the form because it should have been replaced
|
// @ts-expect-error -- prevent further usage of the form because it should have been replaced
|
||||||
|
form = null;
|
||||||
|
|
||||||
if (trLineType) {
|
if (trLineType) {
|
||||||
// if there is a line-type for the "tr", it means the form is on the diff page
|
// if there is a line-type for the "tr", it means the form is on the diff page
|
||||||
@@ -74,10 +75,10 @@ function initRepoDiffConversationForm() {
|
|||||||
|
|
||||||
// the default behavior is to add a pending review, so if no submitter, it also means "pending_review"
|
// the default behavior is to add a pending review, so if no submitter, it also means "pending_review"
|
||||||
if (!submitter || submitter?.matches('button[name="pending_review"]')) {
|
if (!submitter || submitter?.matches('button[name="pending_review"]')) {
|
||||||
const reviewBox = document.querySelector('#review-box');
|
const reviewBox = document.querySelector('#review-box')!;
|
||||||
const counter = reviewBox?.querySelector('.review-comments-counter');
|
const counter = reviewBox?.querySelector('.review-comments-counter');
|
||||||
if (!counter) return;
|
if (!counter) return;
|
||||||
const num = parseInt(counter.getAttribute('data-pending-comment-number')) + 1 || 1;
|
const num = parseInt(counter.getAttribute('data-pending-comment-number')!) + 1 || 1;
|
||||||
counter.setAttribute('data-pending-comment-number', String(num));
|
counter.setAttribute('data-pending-comment-number', String(num));
|
||||||
counter.textContent = String(num);
|
counter.textContent = String(num);
|
||||||
animateOnce(reviewBox, 'pulse-1p5-200');
|
animateOnce(reviewBox, 'pulse-1p5-200');
|
||||||
@@ -92,10 +93,10 @@ function initRepoDiffConversationForm() {
|
|||||||
|
|
||||||
addDelegatedEventListener(document, 'click', '.resolve-conversation', async (el, e) => {
|
addDelegatedEventListener(document, 'click', '.resolve-conversation', async (el, e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const comment_id = el.getAttribute('data-comment-id');
|
const comment_id = el.getAttribute('data-comment-id')!;
|
||||||
const origin = el.getAttribute('data-origin');
|
const origin = el.getAttribute('data-origin')!;
|
||||||
const action = el.getAttribute('data-action');
|
const action = el.getAttribute('data-action')!;
|
||||||
const url = el.getAttribute('data-update-url');
|
const url = el.getAttribute('data-update-url')!;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await POST(url, {data: new URLSearchParams({origin, action, comment_id})});
|
const response = await POST(url, {data: new URLSearchParams({origin, action, comment_id})});
|
||||||
@@ -119,14 +120,14 @@ function initRepoDiffConversationNav() {
|
|||||||
addDelegatedEventListener(document, 'click', '.previous-conversation, .next-conversation', (el, e) => {
|
addDelegatedEventListener(document, 'click', '.previous-conversation, .next-conversation', (el, e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const isPrevious = el.matches('.previous-conversation');
|
const isPrevious = el.matches('.previous-conversation');
|
||||||
const elCurConversation = el.closest('.comment-code-cloud');
|
const elCurConversation = el.closest('.comment-code-cloud')!;
|
||||||
const elAllConversations = document.querySelectorAll('.comment-code-cloud:not(.tw-hidden)');
|
const elAllConversations = document.querySelectorAll('.comment-code-cloud:not(.tw-hidden)');
|
||||||
const index = Array.from(elAllConversations).indexOf(elCurConversation);
|
const index = Array.from(elAllConversations).indexOf(elCurConversation);
|
||||||
const previousIndex = index > 0 ? index - 1 : elAllConversations.length - 1;
|
const previousIndex = index > 0 ? index - 1 : elAllConversations.length - 1;
|
||||||
const nextIndex = index < elAllConversations.length - 1 ? index + 1 : 0;
|
const nextIndex = index < elAllConversations.length - 1 ? index + 1 : 0;
|
||||||
const navIndex = isPrevious ? previousIndex : nextIndex;
|
const navIndex = isPrevious ? previousIndex : nextIndex;
|
||||||
const elNavConversation = elAllConversations[navIndex];
|
const elNavConversation = elAllConversations[navIndex];
|
||||||
const anchor = elNavConversation.querySelector('.comment').id;
|
const anchor = elNavConversation.querySelector('.comment')!.id;
|
||||||
window.location.href = `#${anchor}`;
|
window.location.href = `#${anchor}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -162,15 +163,15 @@ async function loadMoreFiles(btn: Element): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
btn.classList.add('disabled');
|
btn.classList.add('disabled');
|
||||||
const url = btn.getAttribute('data-href');
|
const url = btn.getAttribute('data-href')!;
|
||||||
try {
|
try {
|
||||||
const response = await GET(url);
|
const response = await GET(url);
|
||||||
const resp = await response.text();
|
const resp = await response.text();
|
||||||
const respDoc = parseDom(resp, 'text/html');
|
const respDoc = parseDom(resp, 'text/html');
|
||||||
const respFileBoxes = respDoc.querySelector('#diff-file-boxes');
|
const respFileBoxes = respDoc.querySelector('#diff-file-boxes')!;
|
||||||
// the response is a full HTML page, we need to extract the relevant contents:
|
// the response is a full HTML page, we need to extract the relevant contents:
|
||||||
// * append the newly loaded file list items to the existing list
|
// * append the newly loaded file list items to the existing list
|
||||||
document.querySelector('#diff-incomplete').replaceWith(...Array.from(respFileBoxes.children));
|
document.querySelector('#diff-incomplete')!.replaceWith(...Array.from(respFileBoxes.children));
|
||||||
onShowMoreFiles();
|
onShowMoreFiles();
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -193,15 +194,15 @@ function initRepoDiffShowMore() {
|
|||||||
if (el.classList.contains('disabled')) return;
|
if (el.classList.contains('disabled')) return;
|
||||||
|
|
||||||
el.classList.add('disabled');
|
el.classList.add('disabled');
|
||||||
const url = el.getAttribute('data-href');
|
const url = el.getAttribute('data-href')!;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await GET(url);
|
const response = await GET(url);
|
||||||
const resp = await response.text();
|
const resp = await response.text();
|
||||||
const respDoc = parseDom(resp, 'text/html');
|
const respDoc = parseDom(resp, 'text/html');
|
||||||
const respFileBody = respDoc.querySelector('#diff-file-boxes .diff-file-body .file-body');
|
const respFileBody = respDoc.querySelector('#diff-file-boxes .diff-file-body .file-body')!;
|
||||||
const respFileBodyChildren = Array.from(respFileBody.children); // respFileBody.children will be empty after replaceWith
|
const respFileBodyChildren = Array.from(respFileBody.children); // respFileBody.children will be empty after replaceWith
|
||||||
el.parentElement.replaceWith(...respFileBodyChildren);
|
el.parentElement!.replaceWith(...respFileBodyChildren);
|
||||||
for (const el of respFileBodyChildren) window.htmx.process(el);
|
for (const el of respFileBodyChildren) window.htmx.process(el);
|
||||||
// FIXME: calling onShowMoreFiles is not quite right here.
|
// FIXME: calling onShowMoreFiles is not quite right here.
|
||||||
// But since onShowMoreFiles mixes "init diff box" and "init diff body" together,
|
// But since onShowMoreFiles mixes "init diff box" and "init diff body" together,
|
||||||
@@ -287,6 +288,6 @@ export function initRepoDiffView() {
|
|||||||
|
|
||||||
registerGlobalSelectorFunc('#diff-file-boxes .diff-file-box', initRepoDiffFileBox);
|
registerGlobalSelectorFunc('#diff-file-boxes .diff-file-box', initRepoDiffFileBox);
|
||||||
addDelegatedEventListener(document, 'click', '.fold-file', (el) => {
|
addDelegatedEventListener(document, 'click', '.fold-file', (el) => {
|
||||||
invertFileFolding(el.closest('.file-content'), el);
|
invertFileFolding(el.closest('.file-content')!, el);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {fomanticQuery} from '../modules/fomantic/base.ts';
|
|||||||
import {submitFormFetchAction} from './common-fetch-action.ts';
|
import {submitFormFetchAction} from './common-fetch-action.ts';
|
||||||
|
|
||||||
function initEditPreviewTab(elForm: HTMLFormElement) {
|
function initEditPreviewTab(elForm: HTMLFormElement) {
|
||||||
const elTabMenu = elForm.querySelector('.repo-editor-menu');
|
const elTabMenu = elForm.querySelector('.repo-editor-menu')!;
|
||||||
fomanticQuery(elTabMenu.querySelectorAll('.item')).tab();
|
fomanticQuery(elTabMenu.querySelectorAll('.item')).tab();
|
||||||
|
|
||||||
const elPreviewTab = elTabMenu.querySelector('a[data-tab="preview"]');
|
const elPreviewTab = elTabMenu.querySelector('a[data-tab="preview"]');
|
||||||
@@ -17,15 +17,15 @@ function initEditPreviewTab(elForm: HTMLFormElement) {
|
|||||||
if (!elPreviewTab || !elPreviewPanel) return;
|
if (!elPreviewTab || !elPreviewPanel) return;
|
||||||
|
|
||||||
elPreviewTab.addEventListener('click', async () => {
|
elPreviewTab.addEventListener('click', async () => {
|
||||||
const elTreePath = elForm.querySelector<HTMLInputElement>('input#tree_path');
|
const elTreePath = elForm.querySelector<HTMLInputElement>('input#tree_path')!;
|
||||||
const previewUrl = elPreviewTab.getAttribute('data-preview-url');
|
const previewUrl = elPreviewTab.getAttribute('data-preview-url')!;
|
||||||
const previewContextRef = elPreviewTab.getAttribute('data-preview-context-ref');
|
const previewContextRef = elPreviewTab.getAttribute('data-preview-context-ref');
|
||||||
let previewContext = `${previewContextRef}/${elTreePath.value}`;
|
let previewContext = `${previewContextRef}/${elTreePath.value}`;
|
||||||
previewContext = previewContext.substring(0, previewContext.lastIndexOf('/'));
|
previewContext = previewContext.substring(0, previewContext.lastIndexOf('/'));
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('mode', 'file');
|
formData.append('mode', 'file');
|
||||||
formData.append('context', previewContext);
|
formData.append('context', previewContext);
|
||||||
formData.append('text', elForm.querySelector<HTMLTextAreaElement>('.tab[data-tab="write"] textarea').value);
|
formData.append('text', elForm.querySelector<HTMLTextAreaElement>('.tab[data-tab="write"] textarea')!.value);
|
||||||
formData.append('file_path', elTreePath.value);
|
formData.append('file_path', elTreePath.value);
|
||||||
const response = await POST(previewUrl, {data: formData});
|
const response = await POST(previewUrl, {data: formData});
|
||||||
const data = await response.text();
|
const data = await response.text();
|
||||||
@@ -41,16 +41,16 @@ export function initRepoEditor() {
|
|||||||
el.addEventListener('input', () => {
|
el.addEventListener('input', () => {
|
||||||
if (el.value === 'commit-to-new-branch') {
|
if (el.value === 'commit-to-new-branch') {
|
||||||
showElem('.quick-pull-branch-name');
|
showElem('.quick-pull-branch-name');
|
||||||
document.querySelector<HTMLInputElement>('.quick-pull-branch-name input').required = true;
|
document.querySelector<HTMLInputElement>('.quick-pull-branch-name input')!.required = true;
|
||||||
} else {
|
} else {
|
||||||
hideElem('.quick-pull-branch-name');
|
hideElem('.quick-pull-branch-name');
|
||||||
document.querySelector<HTMLInputElement>('.quick-pull-branch-name input').required = false;
|
document.querySelector<HTMLInputElement>('.quick-pull-branch-name input')!.required = false;
|
||||||
}
|
}
|
||||||
document.querySelector('#commit-button').textContent = el.getAttribute('data-button-text');
|
document.querySelector('#commit-button')!.textContent = el.getAttribute('data-button-text');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const filenameInput = document.querySelector<HTMLInputElement>('#file-name');
|
const filenameInput = document.querySelector<HTMLInputElement>('#file-name')!;
|
||||||
if (!filenameInput) return;
|
if (!filenameInput) return;
|
||||||
function joinTreePath() {
|
function joinTreePath() {
|
||||||
const parts = [];
|
const parts = [];
|
||||||
@@ -61,7 +61,7 @@ export function initRepoEditor() {
|
|||||||
if (filenameInput.value) {
|
if (filenameInput.value) {
|
||||||
parts.push(filenameInput.value);
|
parts.push(filenameInput.value);
|
||||||
}
|
}
|
||||||
document.querySelector<HTMLInputElement>('#tree_path').value = parts.join('/');
|
document.querySelector<HTMLInputElement>('#tree_path')!.value = parts.join('/');
|
||||||
}
|
}
|
||||||
filenameInput.addEventListener('input', function () {
|
filenameInput.addEventListener('input', function () {
|
||||||
const parts = filenameInput.value.split('/');
|
const parts = filenameInput.value.split('/');
|
||||||
@@ -76,8 +76,8 @@ export function initRepoEditor() {
|
|||||||
if (trimValue === '..') {
|
if (trimValue === '..') {
|
||||||
// remove previous tree path
|
// remove previous tree path
|
||||||
if (links.length > 0) {
|
if (links.length > 0) {
|
||||||
const link = links.pop();
|
const link = links.pop()!;
|
||||||
const divider = dividers.pop();
|
const divider = dividers.pop()!;
|
||||||
link.remove();
|
link.remove();
|
||||||
divider.remove();
|
divider.remove();
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ export function initRepoEditor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
containSpace = containSpace || Array.from(links).some((link) => {
|
containSpace = containSpace || Array.from(links).some((link) => {
|
||||||
const value = link.querySelector('a').textContent;
|
const value = link.querySelector('a')!.textContent;
|
||||||
return value.trim() !== value;
|
return value.trim() !== value;
|
||||||
});
|
});
|
||||||
containSpace = containSpace || parts[parts.length - 1].trim() !== parts[parts.length - 1];
|
containSpace = containSpace || parts[parts.length - 1].trim() !== parts[parts.length - 1];
|
||||||
@@ -115,7 +115,7 @@ export function initRepoEditor() {
|
|||||||
warningDiv.innerHTML = html`<p>File path contains leading or trailing whitespace.</p>`;
|
warningDiv.innerHTML = html`<p>File path contains leading or trailing whitespace.</p>`;
|
||||||
// Add display 'block' because display is set to 'none' in formantic\build\semantic.css
|
// Add display 'block' because display is set to 'none' in formantic\build\semantic.css
|
||||||
warningDiv.style.display = 'block';
|
warningDiv.style.display = 'block';
|
||||||
const inputContainer = document.querySelector('.repo-editor-header');
|
const inputContainer = document.querySelector('.repo-editor-header')!;
|
||||||
inputContainer.insertAdjacentElement('beforebegin', warningDiv);
|
inputContainer.insertAdjacentElement('beforebegin', warningDiv);
|
||||||
}
|
}
|
||||||
showElem(warningDiv);
|
showElem(warningDiv);
|
||||||
@@ -132,7 +132,7 @@ export function initRepoEditor() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const lastSection = sections[sections.length - 1];
|
const lastSection = sections[sections.length - 1];
|
||||||
const lastDivider = dividers.length ? dividers[dividers.length - 1] : null;
|
const lastDivider = dividers.length ? dividers[dividers.length - 1] : null;
|
||||||
const value = lastSection.querySelector('a').textContent;
|
const value = lastSection.querySelector('a')!.textContent;
|
||||||
filenameInput.value = value + filenameInput.value;
|
filenameInput.value = value + filenameInput.value;
|
||||||
this.setSelectionRange(value.length, value.length);
|
this.setSelectionRange(value.length, value.length);
|
||||||
lastDivider?.remove();
|
lastDivider?.remove();
|
||||||
@@ -141,7 +141,7 @@ export function initRepoEditor() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const elForm = document.querySelector<HTMLFormElement>('.repository.editor .edit.form');
|
const elForm = document.querySelector<HTMLFormElement>('.repository.editor .edit.form')!;
|
||||||
|
|
||||||
// on the upload page, there is no editor(textarea)
|
// on the upload page, there is no editor(textarea)
|
||||||
const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area');
|
const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area');
|
||||||
@@ -149,7 +149,7 @@ export function initRepoEditor() {
|
|||||||
|
|
||||||
// Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
|
// Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
|
||||||
// to enable or disable the commit button
|
// to enable or disable the commit button
|
||||||
const commitButton = document.querySelector<HTMLButtonElement>('#commit-button');
|
const commitButton = document.querySelector<HTMLButtonElement>('#commit-button')!;
|
||||||
const dirtyFileClass = 'dirty-file';
|
const dirtyFileClass = 'dirty-file';
|
||||||
|
|
||||||
const syncCommitButtonState = () => {
|
const syncCommitButtonState = () => {
|
||||||
@@ -184,8 +184,8 @@ export function initRepoEditor() {
|
|||||||
if (!editArea.value) {
|
if (!editArea.value) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (await confirmModal({
|
if (await confirmModal({
|
||||||
header: elForm.getAttribute('data-text-empty-confirm-header'),
|
header: elForm.getAttribute('data-text-empty-confirm-header')!,
|
||||||
content: elForm.getAttribute('data-text-empty-confirm-content'),
|
content: elForm.getAttribute('data-text-empty-confirm-content')!,
|
||||||
})) {
|
})) {
|
||||||
ignoreAreYouSure(elForm);
|
ignoreAreYouSure(elForm);
|
||||||
submitFormFetchAction(elForm);
|
submitFormFetchAction(elForm);
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ export function initRepoGraphGit() {
|
|||||||
const graphContainer = document.querySelector<HTMLElement>('#git-graph-container');
|
const graphContainer = document.querySelector<HTMLElement>('#git-graph-container');
|
||||||
if (!graphContainer) return;
|
if (!graphContainer) return;
|
||||||
|
|
||||||
const elColorMonochrome = document.querySelector<HTMLElement>('#flow-color-monochrome');
|
const elColorMonochrome = document.querySelector<HTMLElement>('#flow-color-monochrome')!;
|
||||||
const elColorColored = document.querySelector<HTMLElement>('#flow-color-colored');
|
const elColorColored = document.querySelector<HTMLElement>('#flow-color-colored')!;
|
||||||
const toggleColorMode = (mode: 'monochrome' | 'colored') => {
|
const toggleColorMode = (mode: 'monochrome' | 'colored') => {
|
||||||
toggleElemClass(graphContainer, 'monochrome', mode === 'monochrome');
|
toggleElemClass(graphContainer, 'monochrome', mode === 'monochrome');
|
||||||
toggleElemClass(graphContainer, 'colored', mode === 'colored');
|
toggleElemClass(graphContainer, 'colored', mode === 'colored');
|
||||||
@@ -31,7 +31,7 @@ export function initRepoGraphGit() {
|
|||||||
elColorMonochrome.addEventListener('click', () => toggleColorMode('monochrome'));
|
elColorMonochrome.addEventListener('click', () => toggleColorMode('monochrome'));
|
||||||
elColorColored.addEventListener('click', () => toggleColorMode('colored'));
|
elColorColored.addEventListener('click', () => toggleColorMode('colored'));
|
||||||
|
|
||||||
const elGraphBody = document.querySelector<HTMLElement>('#git-graph-body');
|
const elGraphBody = document.querySelector<HTMLElement>('#git-graph-body')!;
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
const params = url.searchParams;
|
const params = url.searchParams;
|
||||||
const loadGitGraph = async () => {
|
const loadGitGraph = async () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {stripTags} from '../utils.ts';
|
import {stripTags} from '../utils.ts';
|
||||||
import {hideElem, queryElemChildren, showElem, type DOMEvent} from '../utils/dom.ts';
|
import {hideElem, queryElemChildren, showElem} from '../utils/dom.ts';
|
||||||
import {POST} from '../modules/fetch.ts';
|
import {POST} from '../modules/fetch.ts';
|
||||||
import {showErrorToast, type Toast} from '../modules/toast.ts';
|
import {showErrorToast, type Toast} from '../modules/toast.ts';
|
||||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||||
@@ -10,32 +10,32 @@ export function initRepoTopicBar() {
|
|||||||
const mgrBtn = document.querySelector<HTMLButtonElement>('#manage_topic');
|
const mgrBtn = document.querySelector<HTMLButtonElement>('#manage_topic');
|
||||||
if (!mgrBtn) return;
|
if (!mgrBtn) return;
|
||||||
|
|
||||||
const editDiv = document.querySelector('#topic_edit');
|
const editDiv = document.querySelector('#topic_edit')!;
|
||||||
const viewDiv = document.querySelector('#repo-topics');
|
const viewDiv = document.querySelector('#repo-topics')!;
|
||||||
const topicDropdown = editDiv.querySelector('.ui.dropdown');
|
const topicDropdown = editDiv.querySelector('.ui.dropdown')!;
|
||||||
let lastErrorToast: Toast;
|
let lastErrorToast: Toast | null = null;
|
||||||
|
|
||||||
mgrBtn.addEventListener('click', () => {
|
mgrBtn.addEventListener('click', () => {
|
||||||
hideElem([viewDiv, mgrBtn]);
|
hideElem([viewDiv, mgrBtn]);
|
||||||
showElem(editDiv);
|
showElem(editDiv);
|
||||||
topicDropdown.querySelector<HTMLInputElement>('input.search').focus();
|
topicDropdown.querySelector<HTMLInputElement>('input.search')!.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelector('#cancel_topic_edit').addEventListener('click', () => {
|
document.querySelector('#cancel_topic_edit')!.addEventListener('click', () => {
|
||||||
lastErrorToast?.hideToast();
|
lastErrorToast?.hideToast();
|
||||||
hideElem(editDiv);
|
hideElem(editDiv);
|
||||||
showElem([viewDiv, mgrBtn]);
|
showElem([viewDiv, mgrBtn]);
|
||||||
mgrBtn.focus();
|
mgrBtn.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelector<HTMLButtonElement>('#save_topic').addEventListener('click', async (e: DOMEvent<MouseEvent, HTMLButtonElement>) => {
|
document.querySelector<HTMLButtonElement>('#save_topic')!.addEventListener('click', async (e) => {
|
||||||
lastErrorToast?.hideToast();
|
lastErrorToast?.hideToast();
|
||||||
const topics = editDiv.querySelector<HTMLInputElement>('input[name=topics]').value;
|
const topics = editDiv.querySelector<HTMLInputElement>('input[name=topics]')!.value;
|
||||||
|
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
data.append('topics', topics);
|
data.append('topics', topics);
|
||||||
|
|
||||||
const response = await POST(e.target.getAttribute('data-link'), {data});
|
const response = await POST((e.target as HTMLElement).getAttribute('data-link')!, {data});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function showContentHistoryDetail(issueBaseUrl: string, commentId: string, histo
|
|||||||
<div class="comment-diff-data is-loading"></div>
|
<div class="comment-diff-data is-loading"></div>
|
||||||
</div>`);
|
</div>`);
|
||||||
document.body.append(elDetailDialog);
|
document.body.append(elDetailDialog);
|
||||||
const elOptionsDropdown = elDetailDialog.querySelector('.ui.dropdown.dialog-header-options');
|
const elOptionsDropdown = elDetailDialog.querySelector('.ui.dropdown.dialog-header-options')!;
|
||||||
const $fomanticDialog = fomanticQuery(elDetailDialog);
|
const $fomanticDialog = fomanticQuery(elDetailDialog);
|
||||||
const $fomanticDropdownOptions = fomanticQuery(elOptionsDropdown);
|
const $fomanticDropdownOptions = fomanticQuery(elOptionsDropdown);
|
||||||
$fomanticDropdownOptions.dropdown({
|
$fomanticDropdownOptions.dropdown({
|
||||||
@@ -74,7 +74,7 @@ function showContentHistoryDetail(issueBaseUrl: string, commentId: string, histo
|
|||||||
const response = await GET(url);
|
const response = await GET(url);
|
||||||
const resp = await response.json();
|
const resp = await response.json();
|
||||||
|
|
||||||
const commentDiffData = elDetailDialog.querySelector('.comment-diff-data');
|
const commentDiffData = elDetailDialog.querySelector('.comment-diff-data')!;
|
||||||
commentDiffData.classList.remove('is-loading');
|
commentDiffData.classList.remove('is-loading');
|
||||||
commentDiffData.innerHTML = resp.diffHtml;
|
commentDiffData.innerHTML = resp.diffHtml;
|
||||||
// there is only one option "item[data-option-item=delete]", so the dropdown can be entirely shown/hidden.
|
// there is only one option "item[data-option-item=delete]", so the dropdown can be entirely shown/hidden.
|
||||||
@@ -92,7 +92,7 @@ function showContentHistoryDetail(issueBaseUrl: string, commentId: string, histo
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showContentHistoryMenu(issueBaseUrl: string, elCommentItem: Element, commentId: string) {
|
function showContentHistoryMenu(issueBaseUrl: string, elCommentItem: Element, commentId: string) {
|
||||||
const elHeaderLeft = elCommentItem.querySelector('.comment-header-left');
|
const elHeaderLeft = elCommentItem.querySelector('.comment-header-left')!;
|
||||||
const menuHtml = `
|
const menuHtml = `
|
||||||
<div class="ui dropdown interact-fg content-history-menu" data-comment-id="${commentId}">
|
<div class="ui dropdown interact-fg content-history-menu" data-comment-id="${commentId}">
|
||||||
• ${i18nTextEdited}${svg('octicon-triangle-down', 14, 'dropdown icon')}
|
• ${i18nTextEdited}${svg('octicon-triangle-down', 14, 'dropdown icon')}
|
||||||
@@ -103,7 +103,7 @@ function showContentHistoryMenu(issueBaseUrl: string, elCommentItem: Element, co
|
|||||||
elHeaderLeft.querySelector(`.ui.dropdown.content-history-menu`)?.remove(); // remove the old one if exists
|
elHeaderLeft.querySelector(`.ui.dropdown.content-history-menu`)?.remove(); // remove the old one if exists
|
||||||
elHeaderLeft.append(createElementFromHTML(menuHtml));
|
elHeaderLeft.append(createElementFromHTML(menuHtml));
|
||||||
|
|
||||||
const elDropdown = elHeaderLeft.querySelector('.ui.dropdown.content-history-menu');
|
const elDropdown = elHeaderLeft.querySelector('.ui.dropdown.content-history-menu')!;
|
||||||
const $fomanticDropdown = fomanticQuery(elDropdown);
|
const $fomanticDropdown = fomanticQuery(elDropdown);
|
||||||
$fomanticDropdown.dropdown({
|
$fomanticDropdown.dropdown({
|
||||||
action: 'hide',
|
action: 'hide',
|
||||||
|
|||||||
@@ -2,20 +2,20 @@ import {handleReply} from './repo-issue.ts';
|
|||||||
import {getComboMarkdownEditor, initComboMarkdownEditor, ComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
import {getComboMarkdownEditor, initComboMarkdownEditor, ComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
||||||
import {POST} from '../modules/fetch.ts';
|
import {POST} from '../modules/fetch.ts';
|
||||||
import {showErrorToast} from '../modules/toast.ts';
|
import {showErrorToast} from '../modules/toast.ts';
|
||||||
import {hideElem, querySingleVisibleElem, showElem, type DOMEvent} from '../utils/dom.ts';
|
import {hideElem, querySingleVisibleElem, showElem} from '../utils/dom.ts';
|
||||||
import {triggerUploadStateChanged} from './comp/EditorUpload.ts';
|
import {triggerUploadStateChanged} from './comp/EditorUpload.ts';
|
||||||
import {convertHtmlToMarkdown} from '../markup/html2markdown.ts';
|
import {convertHtmlToMarkdown} from '../markup/html2markdown.ts';
|
||||||
import {applyAreYouSure, reinitializeAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
import {applyAreYouSure, reinitializeAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||||
|
|
||||||
async function tryOnEditContent(e: DOMEvent<MouseEvent>) {
|
async function tryOnEditContent(e: Event) {
|
||||||
const clickTarget = e.target.closest('.edit-content');
|
const clickTarget = (e.target as HTMLElement).closest('.edit-content');
|
||||||
if (!clickTarget) return;
|
if (!clickTarget) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const commentContent = clickTarget.closest('.comment-header').nextElementSibling;
|
const commentContent = clickTarget.closest('.comment-header')!.nextElementSibling!;
|
||||||
const editContentZone = commentContent.querySelector('.edit-content-zone');
|
const editContentZone = commentContent.querySelector('.edit-content-zone')!;
|
||||||
let renderContent = commentContent.querySelector('.render-content');
|
let renderContent = commentContent.querySelector('.render-content')!;
|
||||||
const rawContent = commentContent.querySelector('.raw-content');
|
const rawContent = commentContent.querySelector('.raw-content')!;
|
||||||
|
|
||||||
let comboMarkdownEditor : ComboMarkdownEditor;
|
let comboMarkdownEditor : ComboMarkdownEditor;
|
||||||
|
|
||||||
@@ -37,14 +37,14 @@ async function tryOnEditContent(e: DOMEvent<MouseEvent>) {
|
|||||||
try {
|
try {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
content: comboMarkdownEditor.value(),
|
content: comboMarkdownEditor.value(),
|
||||||
context: editContentZone.getAttribute('data-context'),
|
context: String(editContentZone.getAttribute('data-context')),
|
||||||
content_version: editContentZone.getAttribute('data-content-version'),
|
content_version: String(editContentZone.getAttribute('data-content-version')),
|
||||||
});
|
});
|
||||||
for (const file of comboMarkdownEditor.dropzoneGetFiles() ?? []) {
|
for (const file of comboMarkdownEditor.dropzoneGetFiles() ?? []) {
|
||||||
params.append('files[]', file);
|
params.append('files[]', file);
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await POST(editContentZone.getAttribute('data-update-url'), {data: params});
|
const response = await POST(editContentZone.getAttribute('data-update-url')!, {data: params});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
showErrorToast(data?.errorMessage ?? window.config.i18n.error_occurred);
|
showErrorToast(data?.errorMessage ?? window.config.i18n.error_occurred);
|
||||||
@@ -67,9 +67,9 @@ async function tryOnEditContent(e: DOMEvent<MouseEvent>) {
|
|||||||
commentContent.insertAdjacentHTML('beforeend', data.attachments);
|
commentContent.insertAdjacentHTML('beforeend', data.attachments);
|
||||||
}
|
}
|
||||||
} else if (data.attachments === '') {
|
} else if (data.attachments === '') {
|
||||||
commentContent.querySelector('.dropzone-attachments').remove();
|
commentContent.querySelector('.dropzone-attachments')!.remove();
|
||||||
} else {
|
} else {
|
||||||
commentContent.querySelector('.dropzone-attachments').outerHTML = data.attachments;
|
commentContent.querySelector('.dropzone-attachments')!.outerHTML = data.attachments;
|
||||||
}
|
}
|
||||||
comboMarkdownEditor.dropzoneSubmitReload();
|
comboMarkdownEditor.dropzoneSubmitReload();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -86,12 +86,12 @@ async function tryOnEditContent(e: DOMEvent<MouseEvent>) {
|
|||||||
|
|
||||||
comboMarkdownEditor = getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
|
comboMarkdownEditor = getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
|
||||||
if (!comboMarkdownEditor) {
|
if (!comboMarkdownEditor) {
|
||||||
editContentZone.innerHTML = document.querySelector('#issue-comment-editor-template').innerHTML;
|
editContentZone.innerHTML = document.querySelector('#issue-comment-editor-template')!.innerHTML;
|
||||||
const form = editContentZone.querySelector('form');
|
const form = editContentZone.querySelector('form')!;
|
||||||
applyAreYouSure(form);
|
applyAreYouSure(form);
|
||||||
const saveButton = querySingleVisibleElem<HTMLButtonElement>(editContentZone, '.ui.primary.button');
|
const saveButton = querySingleVisibleElem<HTMLButtonElement>(editContentZone, '.ui.primary.button')!;
|
||||||
const cancelButton = querySingleVisibleElem<HTMLButtonElement>(editContentZone, '.ui.cancel.button');
|
const cancelButton = querySingleVisibleElem<HTMLButtonElement>(editContentZone, '.ui.cancel.button')!;
|
||||||
comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor'));
|
comboMarkdownEditor = await initComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor')!);
|
||||||
const syncUiState = () => saveButton.disabled = comboMarkdownEditor.isUploading();
|
const syncUiState = () => saveButton.disabled = comboMarkdownEditor.isUploading();
|
||||||
comboMarkdownEditor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState);
|
comboMarkdownEditor.container.addEventListener(ComboMarkdownEditor.EventUploadStateChanged, syncUiState);
|
||||||
cancelButton.addEventListener('click', cancelAndReset);
|
cancelButton.addEventListener('click', cancelAndReset);
|
||||||
@@ -109,7 +109,7 @@ async function tryOnEditContent(e: DOMEvent<MouseEvent>) {
|
|||||||
|
|
||||||
function extractSelectedMarkdown(container: HTMLElement) {
|
function extractSelectedMarkdown(container: HTMLElement) {
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
if (!selection.rangeCount) return '';
|
if (!selection?.rangeCount) return '';
|
||||||
const range = selection.getRangeAt(0);
|
const range = selection.getRangeAt(0);
|
||||||
if (!container.contains(range.commonAncestorContainer)) return '';
|
if (!container.contains(range.commonAncestorContainer)) return '';
|
||||||
|
|
||||||
@@ -127,15 +127,15 @@ async function tryOnQuoteReply(e: Event) {
|
|||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const contentToQuoteId = clickTarget.getAttribute('data-target');
|
const contentToQuoteId = clickTarget.getAttribute('data-target');
|
||||||
const targetRawToQuote = document.querySelector<HTMLElement>(`#${contentToQuoteId}.raw-content`);
|
const targetRawToQuote = document.querySelector<HTMLElement>(`#${contentToQuoteId}.raw-content`)!;
|
||||||
const targetMarkupToQuote = targetRawToQuote.parentElement.querySelector<HTMLElement>('.render-content.markup');
|
const targetMarkupToQuote = targetRawToQuote.parentElement!.querySelector<HTMLElement>('.render-content.markup')!;
|
||||||
let contentToQuote = extractSelectedMarkdown(targetMarkupToQuote);
|
let contentToQuote = extractSelectedMarkdown(targetMarkupToQuote);
|
||||||
if (!contentToQuote) contentToQuote = targetRawToQuote.textContent;
|
if (!contentToQuote) contentToQuote = targetRawToQuote.textContent;
|
||||||
const quotedContent = `${contentToQuote.replace(/^/mg, '> ')}\n\n`;
|
const quotedContent = `${contentToQuote.replace(/^/mg, '> ')}\n\n`;
|
||||||
|
|
||||||
let editor;
|
let editor;
|
||||||
if (clickTarget.classList.contains('quote-reply-diff')) {
|
if (clickTarget.classList.contains('quote-reply-diff')) {
|
||||||
const replyBtn = clickTarget.closest('.comment-code-cloud').querySelector<HTMLElement>('button.comment-form-reply');
|
const replyBtn = clickTarget.closest('.comment-code-cloud')!.querySelector<HTMLElement>('button.comment-form-reply')!;
|
||||||
editor = await handleReply(replyBtn);
|
editor = await handleReply(replyBtn);
|
||||||
} else {
|
} else {
|
||||||
// for normal issue/comment page
|
// for normal issue/comment page
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ function initRepoIssueListCheckboxes() {
|
|||||||
toggleElem('#issue-actions', anyChecked);
|
toggleElem('#issue-actions', anyChecked);
|
||||||
// there are two panels but only one select-all checkbox, so move the checkbox to the visible panel
|
// there are two panels but only one select-all checkbox, so move the checkbox to the visible panel
|
||||||
const panels = document.querySelectorAll<HTMLElement>('#issue-filters, #issue-actions');
|
const panels = document.querySelectorAll<HTMLElement>('#issue-filters, #issue-actions');
|
||||||
const visiblePanel = Array.from(panels).find((el) => isElemVisible(el));
|
const visiblePanel = Array.from(panels).find((el) => isElemVisible(el))!;
|
||||||
const toolbarLeft = visiblePanel.querySelector('.issue-list-toolbar-left');
|
const toolbarLeft = visiblePanel.querySelector('.issue-list-toolbar-left')!;
|
||||||
toolbarLeft.prepend(issueSelectAll);
|
toolbarLeft.prepend(issueSelectAll);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -54,12 +54,12 @@ function initRepoIssueListCheckboxes() {
|
|||||||
async (e: MouseEvent) => {
|
async (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const url = el.getAttribute('data-url');
|
const url = el.getAttribute('data-url')!;
|
||||||
let action = el.getAttribute('data-action');
|
let action = el.getAttribute('data-action')!;
|
||||||
let elementId = el.getAttribute('data-element-id');
|
let elementId = el.getAttribute('data-element-id')!;
|
||||||
const issueIDList: string[] = [];
|
const issueIDList: string[] = [];
|
||||||
for (const el of document.querySelectorAll('.issue-checkbox:checked')) {
|
for (const el of document.querySelectorAll('.issue-checkbox:checked')) {
|
||||||
issueIDList.push(el.getAttribute('data-issue-id'));
|
issueIDList.push(el.getAttribute('data-issue-id')!);
|
||||||
}
|
}
|
||||||
const issueIDs = issueIDList.join(',');
|
const issueIDs = issueIDList.join(',');
|
||||||
if (!issueIDs) return;
|
if (!issueIDs) return;
|
||||||
@@ -77,7 +77,7 @@ function initRepoIssueListCheckboxes() {
|
|||||||
|
|
||||||
// for delete
|
// for delete
|
||||||
if (action === 'delete') {
|
if (action === 'delete') {
|
||||||
const confirmText = el.getAttribute('data-action-delete-confirm');
|
const confirmText = el.getAttribute('data-action-delete-confirm')!;
|
||||||
if (!await confirmModal({content: confirmText, confirmButtonColor: 'red'})) {
|
if (!await confirmModal({content: confirmText, confirmButtonColor: 'red'})) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -95,12 +95,12 @@ function initRepoIssueListCheckboxes() {
|
|||||||
|
|
||||||
function initDropdownUserRemoteSearch(el: Element) {
|
function initDropdownUserRemoteSearch(el: Element) {
|
||||||
let searchUrl = el.getAttribute('data-search-url');
|
let searchUrl = el.getAttribute('data-search-url');
|
||||||
const actionJumpUrl = el.getAttribute('data-action-jump-url');
|
const actionJumpUrl = el.getAttribute('data-action-jump-url')!;
|
||||||
let selectedUsername = el.getAttribute('data-selected-username') || '';
|
let selectedUsername = el.getAttribute('data-selected-username') || '';
|
||||||
const $searchDropdown = fomanticQuery(el);
|
const $searchDropdown = fomanticQuery(el);
|
||||||
const elMenu = el.querySelector('.menu');
|
const elMenu = el.querySelector('.menu')!;
|
||||||
const elSearchInput = el.querySelector<HTMLInputElement>('.ui.search input');
|
const elSearchInput = el.querySelector<HTMLInputElement>('.ui.search input')!;
|
||||||
const elItemFromInput = el.querySelector('.menu > .item-from-input');
|
const elItemFromInput = el.querySelector('.menu > .item-from-input')!;
|
||||||
|
|
||||||
$searchDropdown.dropdown('setting', {
|
$searchDropdown.dropdown('setting', {
|
||||||
fullTextSearch: true,
|
fullTextSearch: true,
|
||||||
@@ -183,21 +183,21 @@ function initPinRemoveButton() {
|
|||||||
const id = Number(el.getAttribute('data-issue-id'));
|
const id = Number(el.getAttribute('data-issue-id'));
|
||||||
|
|
||||||
// Send the unpin request
|
// Send the unpin request
|
||||||
const response = await DELETE(el.getAttribute('data-unpin-url'));
|
const response = await DELETE(el.getAttribute('data-unpin-url')!);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
// Delete the tooltip
|
// Delete the tooltip
|
||||||
el._tippy.destroy();
|
el._tippy.destroy();
|
||||||
// Remove the Card
|
// Remove the Card
|
||||||
el.closest(`div.issue-card[data-issue-id="${id}"]`).remove();
|
el.closest(`div.issue-card[data-issue-id="${id}"]`)!.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pinMoveEnd(e: SortableEvent) {
|
async function pinMoveEnd(e: SortableEvent) {
|
||||||
const url = e.item.getAttribute('data-move-url');
|
const url = e.item.getAttribute('data-move-url')!;
|
||||||
const id = Number(e.item.getAttribute('data-issue-id'));
|
const id = Number(e.item.getAttribute('data-issue-id'));
|
||||||
await POST(url, {data: {id, position: e.newIndex + 1}});
|
await POST(url, {data: {id, position: e.newIndex! + 1}});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initIssuePinSort() {
|
async function initIssuePinSort() {
|
||||||
|
|||||||
@@ -8,21 +8,21 @@ function initRepoPullRequestUpdate(el: HTMLElement) {
|
|||||||
const prUpdateButtonContainer = el.querySelector('#update-pr-branch-with-base');
|
const prUpdateButtonContainer = el.querySelector('#update-pr-branch-with-base');
|
||||||
if (!prUpdateButtonContainer) return;
|
if (!prUpdateButtonContainer) return;
|
||||||
|
|
||||||
const prUpdateButton = prUpdateButtonContainer.querySelector<HTMLButtonElement>(':scope > button');
|
const prUpdateButton = prUpdateButtonContainer.querySelector<HTMLButtonElement>(':scope > button')!;
|
||||||
const prUpdateDropdown = prUpdateButtonContainer.querySelector(':scope > .ui.dropdown');
|
const prUpdateDropdown = prUpdateButtonContainer.querySelector(':scope > .ui.dropdown')!;
|
||||||
prUpdateButton.addEventListener('click', async function (e) {
|
prUpdateButton.addEventListener('click', async function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const redirect = this.getAttribute('data-redirect');
|
const redirect = this.getAttribute('data-redirect');
|
||||||
this.classList.add('is-loading');
|
this.classList.add('is-loading');
|
||||||
let response: Response;
|
let response: Response | undefined;
|
||||||
try {
|
try {
|
||||||
response = await POST(this.getAttribute('data-do'));
|
response = await POST(this.getAttribute('data-do')!);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
this.classList.remove('is-loading');
|
this.classList.remove('is-loading');
|
||||||
}
|
}
|
||||||
let data: Record<string, any>;
|
let data: Record<string, any> | undefined;
|
||||||
try {
|
try {
|
||||||
data = await response?.json(); // the response is probably not a JSON
|
data = await response?.json(); // the response is probably not a JSON
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -54,8 +54,8 @@ function initRepoPullRequestUpdate(el: HTMLElement) {
|
|||||||
|
|
||||||
function initRepoPullRequestCommitStatus(el: HTMLElement) {
|
function initRepoPullRequestCommitStatus(el: HTMLElement) {
|
||||||
for (const btn of el.querySelectorAll('.commit-status-hide-checks')) {
|
for (const btn of el.querySelectorAll('.commit-status-hide-checks')) {
|
||||||
const panel = btn.closest('.commit-status-panel');
|
const panel = btn.closest('.commit-status-panel')!;
|
||||||
const list = panel.querySelector<HTMLElement>('.commit-status-list');
|
const list = panel.querySelector<HTMLElement>('.commit-status-list')!;
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle
|
list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle
|
||||||
btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all');
|
btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all');
|
||||||
@@ -96,7 +96,7 @@ export function initRepoPullMergeBox(el: HTMLElement) {
|
|||||||
|
|
||||||
const reloadingInterval = parseInt(reloadingIntervalValue);
|
const reloadingInterval = parseInt(reloadingIntervalValue);
|
||||||
const pullLink = el.getAttribute('data-pull-link');
|
const pullLink = el.getAttribute('data-pull-link');
|
||||||
let timerId: number;
|
let timerId: number | null;
|
||||||
|
|
||||||
let reloadMergeBox: () => Promise<void>;
|
let reloadMergeBox: () => Promise<void>;
|
||||||
const stopReloading = () => {
|
const stopReloading = () => {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ function issueSidebarReloadConfirmDraftComment() {
|
|||||||
// Most users won't feel too sad if they lose a comment with 10 chars, they can re-type these in seconds.
|
// Most users won't feel too sad if they lose a comment with 10 chars, they can re-type these in seconds.
|
||||||
// But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy.
|
// But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy.
|
||||||
if (textarea && textarea.value.trim().length > 10) {
|
if (textarea && textarea.value.trim().length > 10) {
|
||||||
textarea.parentElement.scrollIntoView();
|
textarea.parentElement!.scrollIntoView();
|
||||||
if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) {
|
if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -34,22 +34,22 @@ export class IssueSidebarComboList {
|
|||||||
|
|
||||||
constructor(container: HTMLElement) {
|
constructor(container: HTMLElement) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.updateUrl = container.getAttribute('data-update-url');
|
this.updateUrl = container.getAttribute('data-update-url')!;
|
||||||
this.updateAlgo = container.getAttribute('data-update-algo');
|
this.updateAlgo = container.getAttribute('data-update-algo')!;
|
||||||
this.selectionMode = container.getAttribute('data-selection-mode');
|
this.selectionMode = container.getAttribute('data-selection-mode')!;
|
||||||
if (!['single', 'multiple'].includes(this.selectionMode)) throw new Error(`Invalid data-update-on: ${this.selectionMode}`);
|
if (!['single', 'multiple'].includes(this.selectionMode)) throw new Error(`Invalid data-update-on: ${this.selectionMode}`);
|
||||||
if (!['diff', 'all'].includes(this.updateAlgo)) throw new Error(`Invalid data-update-algo: ${this.updateAlgo}`);
|
if (!['diff', 'all'].includes(this.updateAlgo)) throw new Error(`Invalid data-update-algo: ${this.updateAlgo}`);
|
||||||
this.elDropdown = container.querySelector<HTMLElement>(':scope > .ui.dropdown');
|
this.elDropdown = container.querySelector<HTMLElement>(':scope > .ui.dropdown')!;
|
||||||
this.elList = container.querySelector<HTMLElement>(':scope > .ui.list');
|
this.elList = container.querySelector<HTMLElement>(':scope > .ui.list')!;
|
||||||
this.elComboValue = container.querySelector<HTMLInputElement>(':scope > .combo-value');
|
this.elComboValue = container.querySelector<HTMLInputElement>(':scope > .combo-value')!;
|
||||||
}
|
}
|
||||||
|
|
||||||
collectCheckedValues() {
|
collectCheckedValues() {
|
||||||
return Array.from(this.elDropdown.querySelectorAll('.menu > .item.checked'), (el) => el.getAttribute('data-value'));
|
return Array.from(this.elDropdown.querySelectorAll('.menu > .item.checked'), (el) => el.getAttribute('data-value')!);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUiList(changedValues: Array<string>) {
|
updateUiList(changedValues: Array<string>) {
|
||||||
const elEmptyTip = this.elList.querySelector('.item.empty-list');
|
const elEmptyTip = this.elList.querySelector('.item.empty-list')!;
|
||||||
queryElemChildren(this.elList, '.item:not(.empty-list)', (el) => el.remove());
|
queryElemChildren(this.elList, '.item:not(.empty-list)', (el) => el.remove());
|
||||||
for (const value of changedValues) {
|
for (const value of changedValues) {
|
||||||
const el = this.elDropdown.querySelector<HTMLElement>(`.menu > .item[data-value="${CSS.escape(value)}"]`);
|
const el = this.elDropdown.querySelector<HTMLElement>(`.menu > .item[data-value="${CSS.escape(value)}"]`);
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ function initBranchSelector() {
|
|||||||
if (!elSelectBranch) return;
|
if (!elSelectBranch) return;
|
||||||
|
|
||||||
const urlUpdateIssueRef = elSelectBranch.getAttribute('data-url-update-issueref');
|
const urlUpdateIssueRef = elSelectBranch.getAttribute('data-url-update-issueref');
|
||||||
const elBranchMenu = elSelectBranch.querySelector('.reference-list-menu');
|
const elBranchMenu = elSelectBranch.querySelector('.reference-list-menu')!;
|
||||||
queryElems(elBranchMenu, '.item:not(.no-select)', (el) => el.addEventListener('click', async function (e) {
|
queryElems(elBranchMenu, '.item:not(.no-select)', (el) => el.addEventListener('click', async function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const selectedValue = this.getAttribute('data-id'); // eg: "refs/heads/my-branch"
|
const selectedValue = this.getAttribute('data-id')!; // eg: "refs/heads/my-branch"
|
||||||
const selectedText = this.getAttribute('data-name'); // eg: "my-branch"
|
const selectedText = this.getAttribute('data-name'); // eg: "my-branch"
|
||||||
if (urlUpdateIssueRef) {
|
if (urlUpdateIssueRef) {
|
||||||
// for existing issue, send request to update issue ref, and reload page
|
// for existing issue, send request to update issue ref, and reload page
|
||||||
@@ -23,9 +23,9 @@ function initBranchSelector() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// for new issue, only update UI&form, do not send request/reload
|
// for new issue, only update UI&form, do not send request/reload
|
||||||
const selectedHiddenSelector = this.getAttribute('data-id-selector');
|
const selectedHiddenSelector = this.getAttribute('data-id-selector')!;
|
||||||
document.querySelector<HTMLInputElement>(selectedHiddenSelector).value = selectedValue;
|
document.querySelector<HTMLInputElement>(selectedHiddenSelector)!.value = selectedValue;
|
||||||
elSelectBranch.querySelector('.text-branch-name').textContent = selectedText;
|
elSelectBranch.querySelector('.text-branch-name')!.textContent = selectedText;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ function initBranchSelector() {
|
|||||||
function initRepoIssueDue() {
|
function initRepoIssueDue() {
|
||||||
const form = document.querySelector<HTMLFormElement>('.issue-due-form');
|
const form = document.querySelector<HTMLFormElement>('.issue-due-form');
|
||||||
if (!form) return;
|
if (!form) return;
|
||||||
const deadline = form.querySelector<HTMLInputElement>('input[name=deadline]');
|
const deadline = form.querySelector<HTMLInputElement>('input[name=deadline]')!;
|
||||||
document.querySelector('.issue-due-edit')?.addEventListener('click', () => {
|
document.querySelector('.issue-due-edit')?.addEventListener('click', () => {
|
||||||
toggleElem(form);
|
toggleElem(form);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
queryElems,
|
queryElems,
|
||||||
showElem,
|
showElem,
|
||||||
toggleElem,
|
toggleElem,
|
||||||
type DOMEvent,
|
|
||||||
} from '../utils/dom.ts';
|
} from '../utils/dom.ts';
|
||||||
import {setFileFolding} from './file-fold.ts';
|
import {setFileFolding} from './file-fold.ts';
|
||||||
import {ComboMarkdownEditor, getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
import {ComboMarkdownEditor, getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
||||||
@@ -67,7 +66,7 @@ function initRepoIssueLabelFilter(elDropdown: HTMLElement) {
|
|||||||
const excludeLabel = (e: MouseEvent | KeyboardEvent, item: Element) => {
|
const excludeLabel = (e: MouseEvent | KeyboardEvent, item: Element) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const labelId = item.getAttribute('data-label-id');
|
const labelId = item.getAttribute('data-label-id')!;
|
||||||
let labelIds: string[] = queryLabels ? queryLabels.split(',') : [];
|
let labelIds: string[] = queryLabels ? queryLabels.split(',') : [];
|
||||||
labelIds = labelIds.filter((id) => Math.abs(parseInt(id)) !== Math.abs(parseInt(labelId)));
|
labelIds = labelIds.filter((id) => Math.abs(parseInt(id)) !== Math.abs(parseInt(labelId)));
|
||||||
labelIds.push(`-${labelId}`);
|
labelIds.push(`-${labelId}`);
|
||||||
@@ -89,14 +88,14 @@ function initRepoIssueLabelFilter(elDropdown: HTMLElement) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
// no "labels" query parameter means "all issues"
|
// no "labels" query parameter means "all issues"
|
||||||
elDropdown.querySelector('.label-filter-query-default').classList.toggle('selected', queryLabels === '');
|
elDropdown.querySelector('.label-filter-query-default')!.classList.toggle('selected', queryLabels === '');
|
||||||
// "labels=0" query parameter means "issues without label"
|
// "labels=0" query parameter means "issues without label"
|
||||||
elDropdown.querySelector('.label-filter-query-not-set').classList.toggle('selected', queryLabels === '0');
|
elDropdown.querySelector('.label-filter-query-not-set')!.classList.toggle('selected', queryLabels === '0');
|
||||||
|
|
||||||
// prepare to process "archived" labels
|
// prepare to process "archived" labels
|
||||||
const elShowArchivedLabel = elDropdown.querySelector('.label-filter-archived-toggle');
|
const elShowArchivedLabel = elDropdown.querySelector('.label-filter-archived-toggle');
|
||||||
if (!elShowArchivedLabel) return;
|
if (!elShowArchivedLabel) return;
|
||||||
const elShowArchivedInput = elShowArchivedLabel.querySelector<HTMLInputElement>('input');
|
const elShowArchivedInput = elShowArchivedLabel.querySelector<HTMLInputElement>('input')!;
|
||||||
elShowArchivedInput.checked = showArchivedLabels;
|
elShowArchivedInput.checked = showArchivedLabels;
|
||||||
const archivedLabels = elDropdown.querySelectorAll('.item[data-is-archived]');
|
const archivedLabels = elDropdown.querySelectorAll('.item[data-is-archived]');
|
||||||
// if no archived labels, hide the toggle and return
|
// if no archived labels, hide the toggle and return
|
||||||
@@ -107,7 +106,7 @@ function initRepoIssueLabelFilter(elDropdown: HTMLElement) {
|
|||||||
|
|
||||||
// show the archived labels if the toggle is checked or the label is selected
|
// show the archived labels if the toggle is checked or the label is selected
|
||||||
for (const label of archivedLabels) {
|
for (const label of archivedLabels) {
|
||||||
toggleElem(label, showArchivedLabels || selectedLabelIds.has(label.getAttribute('data-label-id')));
|
toggleElem(label, showArchivedLabels || selectedLabelIds.has(label.getAttribute('data-label-id')!));
|
||||||
}
|
}
|
||||||
// update the url when the toggle is changed and reload
|
// update the url when the toggle is changed and reload
|
||||||
elShowArchivedInput.addEventListener('input', () => {
|
elShowArchivedInput.addEventListener('input', () => {
|
||||||
@@ -127,14 +126,14 @@ export function initRepoIssueFilterItemLabel() {
|
|||||||
|
|
||||||
export function initRepoIssueCommentDelete() {
|
export function initRepoIssueCommentDelete() {
|
||||||
// Delete comment
|
// Delete comment
|
||||||
document.addEventListener('click', async (e: DOMEvent<MouseEvent>) => {
|
document.addEventListener('click', async (e) => {
|
||||||
if (!e.target.matches('.delete-comment')) return;
|
if (!(e.target as HTMLElement).matches('.delete-comment')) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const deleteButton = e.target;
|
const deleteButton = e.target as HTMLElement;
|
||||||
if (window.confirm(deleteButton.getAttribute('data-locale'))) {
|
if (window.confirm(deleteButton.getAttribute('data-locale')!)) {
|
||||||
try {
|
try {
|
||||||
const response = await POST(deleteButton.getAttribute('data-url'));
|
const response = await POST(deleteButton.getAttribute('data-url')!);
|
||||||
if (!response.ok) throw new Error('Failed to delete comment');
|
if (!response.ok) throw new Error('Failed to delete comment');
|
||||||
|
|
||||||
const conversationHolder = deleteButton.closest('.conversation-holder');
|
const conversationHolder = deleteButton.closest('.conversation-holder');
|
||||||
@@ -143,8 +142,8 @@ export function initRepoIssueCommentDelete() {
|
|||||||
|
|
||||||
// Check if this was a pending comment.
|
// Check if this was a pending comment.
|
||||||
if (conversationHolder?.querySelector('.pending-label')) {
|
if (conversationHolder?.querySelector('.pending-label')) {
|
||||||
const counter = document.querySelector('#review-box .review-comments-counter');
|
const counter = document.querySelector('#review-box .review-comments-counter')!;
|
||||||
let num = parseInt(counter?.getAttribute('data-pending-comment-number')) - 1 || 0;
|
let num = parseInt(counter?.getAttribute('data-pending-comment-number') || '') - 1 || 0;
|
||||||
num = Math.max(num, 0);
|
num = Math.max(num, 0);
|
||||||
counter.setAttribute('data-pending-comment-number', String(num));
|
counter.setAttribute('data-pending-comment-number', String(num));
|
||||||
counter.textContent = String(num);
|
counter.textContent = String(num);
|
||||||
@@ -162,9 +161,9 @@ export function initRepoIssueCommentDelete() {
|
|||||||
// on the Conversation page, there is no parent "tr", so no need to do anything for "add-code-comment"
|
// on the Conversation page, there is no parent "tr", so no need to do anything for "add-code-comment"
|
||||||
if (lineType) {
|
if (lineType) {
|
||||||
if (lineType === 'same') {
|
if (lineType === 'same') {
|
||||||
document.querySelector(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).classList.remove('tw-invisible');
|
document.querySelector(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`)!.classList.remove('tw-invisible');
|
||||||
} else {
|
} else {
|
||||||
document.querySelector(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).classList.remove('tw-invisible');
|
document.querySelector(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`)!.classList.remove('tw-invisible');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conversationHolder.remove();
|
conversationHolder.remove();
|
||||||
@@ -184,13 +183,13 @@ export function initRepoIssueCommentDelete() {
|
|||||||
|
|
||||||
export function initRepoIssueCodeCommentCancel() {
|
export function initRepoIssueCodeCommentCancel() {
|
||||||
// Cancel inline code comment
|
// Cancel inline code comment
|
||||||
document.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
|
document.addEventListener('click', (e) => {
|
||||||
if (!e.target.matches('.cancel-code-comment')) return;
|
if (!(e.target as HTMLElement).matches('.cancel-code-comment')) return;
|
||||||
|
|
||||||
const form = e.target.closest('form');
|
const form = (e.target as HTMLElement).closest('form')!;
|
||||||
if (form?.classList.contains('comment-form')) {
|
if (form?.classList.contains('comment-form')) {
|
||||||
hideElem(form);
|
hideElem(form);
|
||||||
showElem(form.closest('.comment-code-cloud')?.querySelectorAll('button.comment-form-reply'));
|
showElem(form.closest('.comment-code-cloud')!.querySelectorAll('button.comment-form-reply'));
|
||||||
} else {
|
} else {
|
||||||
form.closest('.comment-code-cloud')?.remove();
|
form.closest('.comment-code-cloud')?.remove();
|
||||||
}
|
}
|
||||||
@@ -198,9 +197,9 @@ export function initRepoIssueCodeCommentCancel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function initRepoPullRequestAllowMaintainerEdit() {
|
export function initRepoPullRequestAllowMaintainerEdit() {
|
||||||
const wrapper = document.querySelector('#allow-edits-from-maintainers');
|
const wrapper = document.querySelector('#allow-edits-from-maintainers')!;
|
||||||
if (!wrapper) return;
|
if (!wrapper) return;
|
||||||
const checkbox = wrapper.querySelector<HTMLInputElement>('input[type="checkbox"]');
|
const checkbox = wrapper.querySelector<HTMLInputElement>('input[type="checkbox"]')!;
|
||||||
checkbox.addEventListener('input', async () => {
|
checkbox.addEventListener('input', async () => {
|
||||||
const url = `${wrapper.getAttribute('data-url')}/set_allow_maintainer_edit`;
|
const url = `${wrapper.getAttribute('data-url')}/set_allow_maintainer_edit`;
|
||||||
wrapper.classList.add('is-loading');
|
wrapper.classList.add('is-loading');
|
||||||
@@ -216,7 +215,7 @@ export function initRepoPullRequestAllowMaintainerEdit() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
checkbox.checked = !checkbox.checked;
|
checkbox.checked = !checkbox.checked;
|
||||||
console.error(error);
|
console.error(error);
|
||||||
showTemporaryTooltip(wrapper, wrapper.getAttribute('data-prompt-error'));
|
showTemporaryTooltip(wrapper, wrapper.getAttribute('data-prompt-error')!);
|
||||||
} finally {
|
} finally {
|
||||||
wrapper.classList.remove('is-loading');
|
wrapper.classList.remove('is-loading');
|
||||||
}
|
}
|
||||||
@@ -226,7 +225,7 @@ export function initRepoPullRequestAllowMaintainerEdit() {
|
|||||||
export function initRepoIssueComments() {
|
export function initRepoIssueComments() {
|
||||||
if (!document.querySelector('.repository.view.issue .timeline')) return;
|
if (!document.querySelector('.repository.view.issue .timeline')) return;
|
||||||
|
|
||||||
document.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
|
document.addEventListener('click', (e: Event) => {
|
||||||
const urlTarget = document.querySelector(':target');
|
const urlTarget = document.querySelector(':target');
|
||||||
if (!urlTarget) return;
|
if (!urlTarget) return;
|
||||||
|
|
||||||
@@ -235,22 +234,22 @@ export function initRepoIssueComments() {
|
|||||||
|
|
||||||
if (!/^(issue|pull)(comment)?-\d+$/.test(urlTargetId)) return;
|
if (!/^(issue|pull)(comment)?-\d+$/.test(urlTargetId)) return;
|
||||||
|
|
||||||
if (!e.target.closest(`#${urlTargetId}`)) {
|
if (!(e.target as HTMLElement).closest(`#${urlTargetId}`)) {
|
||||||
// if the user clicks outside the comment, remove the hash from the url
|
// if the user clicks outside the comment, remove the hash from the url
|
||||||
// use empty hash and state to avoid scrolling
|
// use empty hash and state to avoid scrolling
|
||||||
window.location.hash = ' ';
|
window.location.hash = ' ';
|
||||||
window.history.pushState(null, null, ' ');
|
window.history.pushState(null, '', ' ');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleReply(el: HTMLElement) {
|
export async function handleReply(el: HTMLElement) {
|
||||||
const form = el.closest('.comment-code-cloud').querySelector('.comment-form');
|
const form = el.closest('.comment-code-cloud')!.querySelector('.comment-form')!;
|
||||||
const textarea = form.querySelector('textarea');
|
const textarea = form.querySelector('textarea');
|
||||||
|
|
||||||
hideElem(el);
|
hideElem(el);
|
||||||
showElem(form);
|
showElem(form);
|
||||||
const editor = getComboMarkdownEditor(textarea) ?? await initComboMarkdownEditor(form.querySelector('.combo-markdown-editor'));
|
const editor = getComboMarkdownEditor(textarea) ?? await initComboMarkdownEditor(form.querySelector('.combo-markdown-editor')!);
|
||||||
editor.focus();
|
editor.focus();
|
||||||
return editor;
|
return editor;
|
||||||
}
|
}
|
||||||
@@ -269,7 +268,7 @@ export function initRepoPullRequestReview() {
|
|||||||
showElem(`#code-comments-${id}, #code-preview-${id}, #hide-outdated-${id}`);
|
showElem(`#code-comments-${id}, #code-preview-${id}, #hide-outdated-${id}`);
|
||||||
// if the comment box is folded, expand it
|
// if the comment box is folded, expand it
|
||||||
if (ancestorDiffBox?.getAttribute('data-folded') === 'true') {
|
if (ancestorDiffBox?.getAttribute('data-folded') === 'true') {
|
||||||
setFileFolding(ancestorDiffBox, ancestorDiffBox.querySelector('.fold-file'), false);
|
setFileFolding(ancestorDiffBox, ancestorDiffBox.querySelector('.fold-file')!, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
|
// set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
|
||||||
@@ -317,18 +316,18 @@ export function initRepoPullRequestReview() {
|
|||||||
interactive: true,
|
interactive: true,
|
||||||
hideOnClick: true,
|
hideOnClick: true,
|
||||||
});
|
});
|
||||||
elReviewPanel.querySelector('.close').addEventListener('click', () => tippy.hide());
|
elReviewPanel.querySelector('.close')!.addEventListener('click', () => tippy.hide());
|
||||||
}
|
}
|
||||||
|
|
||||||
addDelegatedEventListener(document, 'click', '.add-code-comment', async (el, e) => {
|
addDelegatedEventListener(document, 'click', '.add-code-comment', async (el, e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const isSplit = el.closest('.code-diff')?.classList.contains('code-diff-split');
|
const isSplit = el.closest('.code-diff')?.classList.contains('code-diff-split');
|
||||||
const side = el.getAttribute('data-side');
|
const side = el.getAttribute('data-side')!;
|
||||||
const idx = el.getAttribute('data-idx');
|
const idx = el.getAttribute('data-idx')!;
|
||||||
const path = el.closest('[data-path]')?.getAttribute('data-path');
|
const path = el.closest('[data-path]')?.getAttribute('data-path');
|
||||||
const tr = el.closest('tr');
|
const tr = el.closest('tr')!;
|
||||||
const lineType = tr.getAttribute('data-line-type');
|
const lineType = tr.getAttribute('data-line-type')!;
|
||||||
|
|
||||||
let ntr = tr.nextElementSibling;
|
let ntr = tr.nextElementSibling;
|
||||||
if (!ntr?.classList.contains('add-comment')) {
|
if (!ntr?.classList.contains('add-comment')) {
|
||||||
@@ -343,15 +342,15 @@ export function initRepoPullRequestReview() {
|
|||||||
</tr>`);
|
</tr>`);
|
||||||
tr.after(ntr);
|
tr.after(ntr);
|
||||||
}
|
}
|
||||||
const td = ntr.querySelector(`.add-comment-${side}`);
|
const td = ntr.querySelector(`.add-comment-${side}`)!;
|
||||||
const commentCloud = td.querySelector('.comment-code-cloud');
|
const commentCloud = td.querySelector('.comment-code-cloud');
|
||||||
if (!commentCloud && !ntr.querySelector('button[name="pending_review"]')) {
|
if (!commentCloud && !ntr.querySelector('button[name="pending_review"]')) {
|
||||||
const response = await GET(el.closest('[data-new-comment-url]')?.getAttribute('data-new-comment-url'));
|
const response = await GET(el.closest('[data-new-comment-url]')?.getAttribute('data-new-comment-url') ?? '');
|
||||||
td.innerHTML = await response.text();
|
td.innerHTML = await response.text();
|
||||||
td.querySelector<HTMLInputElement>("input[name='line']").value = idx;
|
td.querySelector<HTMLInputElement>("input[name='line']")!.value = idx;
|
||||||
td.querySelector<HTMLInputElement>("input[name='side']").value = (side === 'left' ? 'previous' : 'proposed');
|
td.querySelector<HTMLInputElement>("input[name='side']")!.value = (side === 'left' ? 'previous' : 'proposed');
|
||||||
td.querySelector<HTMLInputElement>("input[name='path']").value = path;
|
td.querySelector<HTMLInputElement>("input[name='path']")!.value = String(path);
|
||||||
const editor = await initComboMarkdownEditor(td.querySelector<HTMLElement>('.combo-markdown-editor'));
|
const editor = await initComboMarkdownEditor(td.querySelector<HTMLElement>('.combo-markdown-editor')!);
|
||||||
editor.focus();
|
editor.focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -360,7 +359,7 @@ export function initRepoPullRequestReview() {
|
|||||||
export function initRepoIssueReferenceIssue() {
|
export function initRepoIssueReferenceIssue() {
|
||||||
const elDropdown = document.querySelector('.issue_reference_repository_search');
|
const elDropdown = document.querySelector('.issue_reference_repository_search');
|
||||||
if (!elDropdown) return;
|
if (!elDropdown) return;
|
||||||
const form = elDropdown.closest('form');
|
const form = elDropdown.closest('form')!;
|
||||||
fomanticQuery(elDropdown).dropdown({
|
fomanticQuery(elDropdown).dropdown({
|
||||||
fullTextSearch: true,
|
fullTextSearch: true,
|
||||||
apiSettings: {
|
apiSettings: {
|
||||||
@@ -389,10 +388,10 @@ export function initRepoIssueReferenceIssue() {
|
|||||||
const target = el.getAttribute('data-target');
|
const target = el.getAttribute('data-target');
|
||||||
const content = document.querySelector(`#${target}`)?.textContent ?? '';
|
const content = document.querySelector(`#${target}`)?.textContent ?? '';
|
||||||
const poster = el.getAttribute('data-poster-username');
|
const poster = el.getAttribute('data-poster-username');
|
||||||
const reference = toAbsoluteUrl(el.getAttribute('data-reference'));
|
const reference = toAbsoluteUrl(el.getAttribute('data-reference')!);
|
||||||
const modalSelector = el.getAttribute('data-modal');
|
const modalSelector = el.getAttribute('data-modal')!;
|
||||||
const modal = document.querySelector(modalSelector);
|
const modal = document.querySelector(modalSelector)!;
|
||||||
const textarea = modal.querySelector<HTMLTextAreaElement>('textarea[name="content"]');
|
const textarea = modal.querySelector<HTMLTextAreaElement>('textarea[name="content"]')!;
|
||||||
textarea.value = `${content}\n\n_Originally posted by @${poster} in ${reference}_`;
|
textarea.value = `${content}\n\n_Originally posted by @${poster} in ${reference}_`;
|
||||||
fomanticQuery(modal).modal('show');
|
fomanticQuery(modal).modal('show');
|
||||||
});
|
});
|
||||||
@@ -402,8 +401,8 @@ export function initRepoIssueWipNewTitle() {
|
|||||||
// Toggle WIP for new PR
|
// Toggle WIP for new PR
|
||||||
queryElems(document, '.title_wip_desc > a', (el) => el.addEventListener('click', (e) => {
|
queryElems(document, '.title_wip_desc > a', (el) => el.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const wipPrefixes = JSON.parse(el.closest('.title_wip_desc').getAttribute('data-wip-prefixes'));
|
const wipPrefixes = JSON.parse(el.closest('.title_wip_desc')!.getAttribute('data-wip-prefixes')!);
|
||||||
const titleInput = document.querySelector<HTMLInputElement>('#issue_title');
|
const titleInput = document.querySelector<HTMLInputElement>('#issue_title')!;
|
||||||
const titleValue = titleInput.value;
|
const titleValue = titleInput.value;
|
||||||
for (const prefix of wipPrefixes) {
|
for (const prefix of wipPrefixes) {
|
||||||
if (titleValue.startsWith(prefix.toUpperCase())) {
|
if (titleValue.startsWith(prefix.toUpperCase())) {
|
||||||
@@ -419,8 +418,8 @@ export function initRepoIssueWipToggle() {
|
|||||||
registerGlobalInitFunc('initPullRequestWipToggle', (toggleWip) => toggleWip.addEventListener('click', async (e) => {
|
registerGlobalInitFunc('initPullRequestWipToggle', (toggleWip) => toggleWip.addEventListener('click', async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const title = toggleWip.getAttribute('data-title');
|
const title = toggleWip.getAttribute('data-title');
|
||||||
const wipPrefix = toggleWip.getAttribute('data-wip-prefix');
|
const wipPrefix = toggleWip.getAttribute('data-wip-prefix')!;
|
||||||
const updateUrl = toggleWip.getAttribute('data-update-url');
|
const updateUrl = toggleWip.getAttribute('data-update-url')!;
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.append('title', title?.startsWith(wipPrefix) ? title.slice(wipPrefix.length).trim() : `${wipPrefix.trim()} ${title}`);
|
params.append('title', title?.startsWith(wipPrefix) ? title.slice(wipPrefix.length).trim() : `${wipPrefix.trim()} ${title}`);
|
||||||
@@ -434,13 +433,13 @@ export function initRepoIssueWipToggle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function initRepoIssueTitleEdit() {
|
export function initRepoIssueTitleEdit() {
|
||||||
const issueTitleDisplay = document.querySelector('#issue-title-display');
|
const issueTitleDisplay = document.querySelector('#issue-title-display')!;
|
||||||
const issueTitleEditor = document.querySelector<HTMLFormElement>('#issue-title-editor');
|
const issueTitleEditor = document.querySelector<HTMLFormElement>('#issue-title-editor');
|
||||||
if (!issueTitleEditor) return;
|
if (!issueTitleEditor) return;
|
||||||
|
|
||||||
const issueTitleInput = issueTitleEditor.querySelector('input');
|
const issueTitleInput = issueTitleEditor.querySelector('input')!;
|
||||||
const oldTitle = issueTitleInput.getAttribute('data-old-title');
|
const oldTitle = issueTitleInput.getAttribute('data-old-title')!;
|
||||||
issueTitleDisplay.querySelector('#issue-title-edit-show').addEventListener('click', () => {
|
issueTitleDisplay.querySelector('#issue-title-edit-show')!.addEventListener('click', () => {
|
||||||
hideElem(issueTitleDisplay);
|
hideElem(issueTitleDisplay);
|
||||||
hideElem('#pull-desc-display');
|
hideElem('#pull-desc-display');
|
||||||
showElem(issueTitleEditor);
|
showElem(issueTitleEditor);
|
||||||
@@ -450,7 +449,7 @@ export function initRepoIssueTitleEdit() {
|
|||||||
}
|
}
|
||||||
issueTitleInput.focus();
|
issueTitleInput.focus();
|
||||||
});
|
});
|
||||||
issueTitleEditor.querySelector('.ui.cancel.button').addEventListener('click', () => {
|
issueTitleEditor.querySelector('.ui.cancel.button')!.addEventListener('click', () => {
|
||||||
hideElem(issueTitleEditor);
|
hideElem(issueTitleEditor);
|
||||||
hideElem('#pull-desc-editor');
|
hideElem('#pull-desc-editor');
|
||||||
showElem(issueTitleDisplay);
|
showElem(issueTitleDisplay);
|
||||||
@@ -460,22 +459,22 @@ export function initRepoIssueTitleEdit() {
|
|||||||
const pullDescEditor = document.querySelector('#pull-desc-editor'); // it may not exist for a merged PR
|
const pullDescEditor = document.querySelector('#pull-desc-editor'); // it may not exist for a merged PR
|
||||||
const prTargetUpdateUrl = pullDescEditor?.getAttribute('data-target-update-url');
|
const prTargetUpdateUrl = pullDescEditor?.getAttribute('data-target-update-url');
|
||||||
|
|
||||||
const editSaveButton = issueTitleEditor.querySelector('.ui.primary.button');
|
const editSaveButton = issueTitleEditor.querySelector('.ui.primary.button')!;
|
||||||
issueTitleEditor.addEventListener('submit', async (e) => {
|
issueTitleEditor.addEventListener('submit', async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newTitle = issueTitleInput.value.trim();
|
const newTitle = issueTitleInput.value.trim();
|
||||||
try {
|
try {
|
||||||
if (newTitle && newTitle !== oldTitle) {
|
if (newTitle && newTitle !== oldTitle) {
|
||||||
const resp = await POST(editSaveButton.getAttribute('data-update-url'), {data: new URLSearchParams({title: newTitle})});
|
const resp = await POST(editSaveButton.getAttribute('data-update-url')!, {data: new URLSearchParams({title: newTitle})});
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
throw new Error(`Failed to update issue title: ${resp.statusText}`);
|
throw new Error(`Failed to update issue title: ${resp.statusText}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (prTargetUpdateUrl) {
|
if (prTargetUpdateUrl) {
|
||||||
const newTargetBranch = document.querySelector('#pull-target-branch').getAttribute('data-branch');
|
const newTargetBranch = document.querySelector('#pull-target-branch')!.getAttribute('data-branch');
|
||||||
const oldTargetBranch = document.querySelector('#branch_target').textContent;
|
const oldTargetBranch = document.querySelector('#branch_target')!.textContent;
|
||||||
if (newTargetBranch !== oldTargetBranch) {
|
if (newTargetBranch !== oldTargetBranch) {
|
||||||
const resp = await POST(prTargetUpdateUrl, {data: new URLSearchParams({target_branch: newTargetBranch})});
|
const resp = await POST(prTargetUpdateUrl, {data: new URLSearchParams({target_branch: String(newTargetBranch)})});
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
throw new Error(`Failed to update PR target branch: ${resp.statusText}`);
|
throw new Error(`Failed to update PR target branch: ${resp.statusText}`);
|
||||||
}
|
}
|
||||||
@@ -491,12 +490,12 @@ export function initRepoIssueTitleEdit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function initRepoIssueBranchSelect() {
|
export function initRepoIssueBranchSelect() {
|
||||||
document.querySelector<HTMLElement>('#branch-select')?.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
|
document.querySelector<HTMLElement>('#branch-select')?.addEventListener('click', (e: Event) => {
|
||||||
const el = e.target.closest('.item[data-branch]');
|
const el = (e.target as HTMLElement).closest('.item[data-branch]');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
const pullTargetBranch = document.querySelector('#pull-target-branch');
|
const pullTargetBranch = document.querySelector('#pull-target-branch')!;
|
||||||
const baseName = pullTargetBranch.getAttribute('data-basename');
|
const baseName = pullTargetBranch.getAttribute('data-basename');
|
||||||
const branchNameNew = el.getAttribute('data-branch');
|
const branchNameNew = el.getAttribute('data-branch')!;
|
||||||
const branchNameOld = pullTargetBranch.getAttribute('data-branch');
|
const branchNameOld = pullTargetBranch.getAttribute('data-branch');
|
||||||
pullTargetBranch.textContent = pullTargetBranch.textContent.replace(`${baseName}:${branchNameOld}`, `${baseName}:${branchNameNew}`);
|
pullTargetBranch.textContent = pullTargetBranch.textContent.replace(`${baseName}:${branchNameOld}`, `${baseName}:${branchNameNew}`);
|
||||||
pullTargetBranch.setAttribute('data-branch', branchNameNew);
|
pullTargetBranch.setAttribute('data-branch', branchNameNew);
|
||||||
@@ -507,7 +506,7 @@ async function initSingleCommentEditor(commentForm: HTMLFormElement) {
|
|||||||
// pages:
|
// pages:
|
||||||
// * normal new issue/pr page: no status-button, no comment-button (there is only a normal submit button which can submit empty content)
|
// * normal new issue/pr page: no status-button, no comment-button (there is only a normal submit button which can submit empty content)
|
||||||
// * issue/pr view page: with comment form, has status-button and comment-button
|
// * issue/pr view page: with comment form, has status-button and comment-button
|
||||||
const editor = await initComboMarkdownEditor(commentForm.querySelector('.combo-markdown-editor'));
|
const editor = await initComboMarkdownEditor(commentForm.querySelector('.combo-markdown-editor')!);
|
||||||
const statusButton = document.querySelector<HTMLButtonElement>('#status-button');
|
const statusButton = document.querySelector<HTMLButtonElement>('#status-button');
|
||||||
const commentButton = document.querySelector<HTMLButtonElement>('#comment-button');
|
const commentButton = document.querySelector<HTMLButtonElement>('#comment-button');
|
||||||
const syncUiState = () => {
|
const syncUiState = () => {
|
||||||
@@ -531,9 +530,9 @@ function initIssueTemplateCommentEditors(commentForm: HTMLFormElement) {
|
|||||||
const comboFields = commentForm.querySelectorAll<HTMLElement>('.combo-editor-dropzone');
|
const comboFields = commentForm.querySelectorAll<HTMLElement>('.combo-editor-dropzone');
|
||||||
|
|
||||||
const initCombo = async (elCombo: HTMLElement) => {
|
const initCombo = async (elCombo: HTMLElement) => {
|
||||||
const fieldTextarea = elCombo.querySelector<HTMLTextAreaElement>('.form-field-real');
|
const fieldTextarea = elCombo.querySelector<HTMLTextAreaElement>('.form-field-real')!;
|
||||||
const dropzoneContainer = elCombo.querySelector<HTMLElement>('.form-field-dropzone');
|
const dropzoneContainer = elCombo.querySelector<HTMLElement>('.form-field-dropzone')!;
|
||||||
const markdownEditor = elCombo.querySelector<HTMLElement>('.combo-markdown-editor');
|
const markdownEditor = elCombo.querySelector<HTMLElement>('.combo-markdown-editor')!;
|
||||||
|
|
||||||
const editor = await initComboMarkdownEditor(markdownEditor);
|
const editor = await initComboMarkdownEditor(markdownEditor);
|
||||||
editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, () => fieldTextarea.value = editor.value());
|
editor.container.addEventListener(ComboMarkdownEditor.EventEditorContentChanged, () => fieldTextarea.value = editor.value());
|
||||||
@@ -544,7 +543,7 @@ function initIssueTemplateCommentEditors(commentForm: HTMLFormElement) {
|
|||||||
hideElem(commentForm.querySelectorAll('.combo-editor-dropzone .combo-markdown-editor'));
|
hideElem(commentForm.querySelectorAll('.combo-editor-dropzone .combo-markdown-editor'));
|
||||||
queryElems(commentForm, '.combo-editor-dropzone .form-field-dropzone', (dropzoneContainer) => {
|
queryElems(commentForm, '.combo-editor-dropzone .form-field-dropzone', (dropzoneContainer) => {
|
||||||
// if "form-field-dropzone" exists, then "dropzone" must also exist
|
// if "form-field-dropzone" exists, then "dropzone" must also exist
|
||||||
const dropzone = dropzoneContainer.querySelector<HTMLElement>('.dropzone').dropzone;
|
const dropzone = dropzoneContainer.querySelector<HTMLElement>('.dropzone')!.dropzone;
|
||||||
const hasUploadedFiles = dropzone.files.length !== 0;
|
const hasUploadedFiles = dropzone.files.length !== 0;
|
||||||
toggleElem(dropzoneContainer, hasUploadedFiles);
|
toggleElem(dropzoneContainer, hasUploadedFiles);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ export function initBranchSelectorTabs() {
|
|||||||
for (const elSelectBranch of elSelectBranches) {
|
for (const elSelectBranch of elSelectBranches) {
|
||||||
queryElems(elSelectBranch, '.reference.column', (el) => el.addEventListener('click', () => {
|
queryElems(elSelectBranch, '.reference.column', (el) => el.addEventListener('click', () => {
|
||||||
hideElem(elSelectBranch.querySelectorAll('.scrolling.reference-list-menu'));
|
hideElem(elSelectBranch.querySelectorAll('.scrolling.reference-list-menu'));
|
||||||
showElem(el.getAttribute('data-target'));
|
showElem(el.getAttribute('data-target')!);
|
||||||
queryElemChildren(el.parentNode, '.branch-tag-item', (el) => el.classList.remove('active'));
|
queryElemChildren(el.parentNode!, '.branch-tag-item', (el) => el.classList.remove('active'));
|
||||||
el.classList.add('active');
|
el.classList.add('active');
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {hideElem, showElem, type DOMEvent} from '../utils/dom.ts';
|
import {hideElem, showElem} from '../utils/dom.ts';
|
||||||
import {GET, POST} from '../modules/fetch.ts';
|
import {GET, POST} from '../modules/fetch.ts';
|
||||||
|
|
||||||
export function initRepoMigrationStatusChecker() {
|
export function initRepoMigrationStatusChecker() {
|
||||||
@@ -18,7 +18,7 @@ export function initRepoMigrationStatusChecker() {
|
|||||||
|
|
||||||
// for all status
|
// for all status
|
||||||
if (data.message) {
|
if (data.message) {
|
||||||
document.querySelector('#repo_migrating_progress_message').textContent = data.message;
|
document.querySelector('#repo_migrating_progress_message')!.textContent = data.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaskStatusFinished
|
// TaskStatusFinished
|
||||||
@@ -34,7 +34,7 @@ export function initRepoMigrationStatusChecker() {
|
|||||||
showElem('#repo_migrating_retry');
|
showElem('#repo_migrating_retry');
|
||||||
showElem('#repo_migrating_failed');
|
showElem('#repo_migrating_failed');
|
||||||
showElem('#repo_migrating_failed_image');
|
showElem('#repo_migrating_failed_image');
|
||||||
document.querySelector('#repo_migrating_failed_error').textContent = data.message;
|
document.querySelector('#repo_migrating_failed_error')!.textContent = data.message;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ export function initRepoMigrationStatusChecker() {
|
|||||||
syncTaskStatus(); // no await
|
syncTaskStatus(); // no await
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doMigrationRetry(e: DOMEvent<MouseEvent>) {
|
async function doMigrationRetry(e: Event) {
|
||||||
await POST(e.target.getAttribute('data-migrating-task-retry-url'));
|
await POST((e.target as HTMLElement).getAttribute('data-migrating-task-retry-url')!);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ const pass = document.querySelector<HTMLInputElement>('#auth_password');
|
|||||||
const token = document.querySelector<HTMLInputElement>('#auth_token');
|
const token = document.querySelector<HTMLInputElement>('#auth_token');
|
||||||
const mirror = document.querySelector<HTMLInputElement>('#mirror');
|
const mirror = document.querySelector<HTMLInputElement>('#mirror');
|
||||||
const lfs = document.querySelector<HTMLInputElement>('#lfs');
|
const lfs = document.querySelector<HTMLInputElement>('#lfs');
|
||||||
const lfsSettings = document.querySelector<HTMLElement>('#lfs_settings');
|
const lfsSettings = document.querySelector<HTMLElement>('#lfs_settings')!;
|
||||||
const lfsEndpoint = document.querySelector<HTMLElement>('#lfs_endpoint');
|
const lfsEndpoint = document.querySelector<HTMLElement>('#lfs_endpoint')!;
|
||||||
const items = document.querySelectorAll<HTMLInputElement>('#migrate_items input[type=checkbox]');
|
const items = document.querySelectorAll<HTMLInputElement>('#migrate_items input[type=checkbox]');
|
||||||
|
|
||||||
export function initRepoMigration() {
|
export function initRepoMigration() {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ export function initRepoMilestone() {
|
|||||||
const page = document.querySelector('.repository.new.milestone');
|
const page = document.querySelector('.repository.new.milestone');
|
||||||
if (!page) return;
|
if (!page) return;
|
||||||
|
|
||||||
const deadline = page.querySelector<HTMLInputElement>('form input[name=deadline]');
|
const deadline = page.querySelector<HTMLInputElement>('form input[name=deadline]')!;
|
||||||
document.querySelector('#milestone-clear-deadline').addEventListener('click', () => {
|
document.querySelector('#milestone-clear-deadline')!.addEventListener('click', () => {
|
||||||
deadline.value = '';
|
deadline.value = '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import {sanitizeRepoName} from './repo-common.ts';
|
|||||||
const {appSubUrl} = window.config;
|
const {appSubUrl} = window.config;
|
||||||
|
|
||||||
function initRepoNewTemplateSearch(form: HTMLFormElement) {
|
function initRepoNewTemplateSearch(form: HTMLFormElement) {
|
||||||
const elSubmitButton = querySingleVisibleElem<HTMLInputElement>(form, '.ui.primary.button');
|
const elSubmitButton = querySingleVisibleElem<HTMLInputElement>(form, '.ui.primary.button')!;
|
||||||
const elCreateRepoErrorMessage = form.querySelector('#create-repo-error-message');
|
const elCreateRepoErrorMessage = form.querySelector('#create-repo-error-message')!;
|
||||||
const elRepoOwnerDropdown = form.querySelector('#repo_owner_dropdown');
|
const elRepoOwnerDropdown = form.querySelector('#repo_owner_dropdown')!;
|
||||||
const elRepoTemplateDropdown = form.querySelector<HTMLInputElement>('#repo_template_search');
|
const elRepoTemplateDropdown = form.querySelector<HTMLInputElement>('#repo_template_search')!;
|
||||||
const inputRepoTemplate = form.querySelector<HTMLInputElement>('#repo_template');
|
const inputRepoTemplate = form.querySelector<HTMLInputElement>('#repo_template')!;
|
||||||
const elTemplateUnits = form.querySelector('#template_units');
|
const elTemplateUnits = form.querySelector('#template_units')!;
|
||||||
const elNonTemplate = form.querySelector('#non_template');
|
const elNonTemplate = form.querySelector('#non_template')!;
|
||||||
const checkTemplate = function () {
|
const checkTemplate = function () {
|
||||||
const hasSelectedTemplate = inputRepoTemplate.value !== '' && inputRepoTemplate.value !== '0';
|
const hasSelectedTemplate = inputRepoTemplate.value !== '' && inputRepoTemplate.value !== '0';
|
||||||
toggleElem(elTemplateUnits, hasSelectedTemplate);
|
toggleElem(elTemplateUnits, hasSelectedTemplate);
|
||||||
@@ -62,10 +62,10 @@ export function initRepoNew() {
|
|||||||
const pageContent = document.querySelector('.page-content.repository.new-repo');
|
const pageContent = document.querySelector('.page-content.repository.new-repo');
|
||||||
if (!pageContent) return;
|
if (!pageContent) return;
|
||||||
|
|
||||||
const form = document.querySelector<HTMLFormElement>('.new-repo-form');
|
const form = document.querySelector<HTMLFormElement>('.new-repo-form')!;
|
||||||
const inputGitIgnores = form.querySelector<HTMLInputElement>('input[name="gitignores"]');
|
const inputGitIgnores = form.querySelector<HTMLInputElement>('input[name="gitignores"]')!;
|
||||||
const inputLicense = form.querySelector<HTMLInputElement>('input[name="license"]');
|
const inputLicense = form.querySelector<HTMLInputElement>('input[name="license"]')!;
|
||||||
const inputAutoInit = form.querySelector<HTMLInputElement>('input[name="auto_init"]');
|
const inputAutoInit = form.querySelector<HTMLInputElement>('input[name="auto_init"]')!;
|
||||||
const updateUiAutoInit = () => {
|
const updateUiAutoInit = () => {
|
||||||
inputAutoInit.checked = Boolean(inputGitIgnores.value || inputLicense.value);
|
inputAutoInit.checked = Boolean(inputGitIgnores.value || inputLicense.value);
|
||||||
};
|
};
|
||||||
@@ -73,13 +73,13 @@ export function initRepoNew() {
|
|||||||
inputLicense.addEventListener('change', updateUiAutoInit);
|
inputLicense.addEventListener('change', updateUiAutoInit);
|
||||||
updateUiAutoInit();
|
updateUiAutoInit();
|
||||||
|
|
||||||
const inputRepoName = form.querySelector<HTMLInputElement>('input[name="repo_name"]');
|
const inputRepoName = form.querySelector<HTMLInputElement>('input[name="repo_name"]')!;
|
||||||
const inputPrivate = form.querySelector<HTMLInputElement>('input[name="private"]');
|
const inputPrivate = form.querySelector<HTMLInputElement>('input[name="private"]')!;
|
||||||
const updateUiRepoName = () => {
|
const updateUiRepoName = () => {
|
||||||
const helps = form.querySelectorAll(`.help[data-help-for-repo-name]`);
|
const helps = form.querySelectorAll(`.help[data-help-for-repo-name]`);
|
||||||
hideElem(helps);
|
hideElem(helps);
|
||||||
let help = form.querySelector(`.help[data-help-for-repo-name="${CSS.escape(inputRepoName.value)}"]`);
|
let help = form.querySelector(`.help[data-help-for-repo-name="${CSS.escape(inputRepoName.value)}"]`);
|
||||||
if (!help) help = form.querySelector(`.help[data-help-for-repo-name=""]`);
|
if (!help) help = form.querySelector(`.help[data-help-for-repo-name=""]`)!;
|
||||||
showElem(help);
|
showElem(help);
|
||||||
const repoNamePreferPrivate: Record<string, boolean> = {'.profile': false, '.profile-private': true};
|
const repoNamePreferPrivate: Record<string, boolean> = {'.profile': false, '.profile-private': true};
|
||||||
const preferPrivate = repoNamePreferPrivate[inputRepoName.value];
|
const preferPrivate = repoNamePreferPrivate[inputRepoName.value];
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import type {SortableEvent} from 'sortablejs';
|
|||||||
import {toggleFullScreen} from '../utils.ts';
|
import {toggleFullScreen} from '../utils.ts';
|
||||||
|
|
||||||
function updateIssueCount(card: HTMLElement): void {
|
function updateIssueCount(card: HTMLElement): void {
|
||||||
const parent = card.parentElement;
|
const parent = card.parentElement!;
|
||||||
const count = parent.querySelectorAll('.issue-card').length;
|
const count = parent.querySelectorAll('.issue-card').length;
|
||||||
parent.querySelector('.project-column-issue-count').textContent = String(count);
|
parent.querySelector('.project-column-issue-count')!.textContent = String(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function moveIssue({item, from, to, oldIndex}: SortableEvent): Promise<void> {
|
async function moveIssue({item, from, to, oldIndex}: SortableEvent): Promise<void> {
|
||||||
@@ -19,7 +19,7 @@ async function moveIssue({item, from, to, oldIndex}: SortableEvent): Promise<voi
|
|||||||
|
|
||||||
const columnSorting = {
|
const columnSorting = {
|
||||||
issues: Array.from(columnCards, (card, i) => ({
|
issues: Array.from(columnCards, (card, i) => ({
|
||||||
issueID: parseInt(card.getAttribute('data-issue')),
|
issueID: parseInt(card.getAttribute('data-issue')!),
|
||||||
sorting: i,
|
sorting: i,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
@@ -30,13 +30,15 @@ async function moveIssue({item, from, to, oldIndex}: SortableEvent): Promise<voi
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
if (oldIndex !== undefined) {
|
||||||
from.insertBefore(item, from.children[oldIndex]);
|
from.insertBefore(item, from.children[oldIndex]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function initRepoProjectSortable(): Promise<void> {
|
async function initRepoProjectSortable(): Promise<void> {
|
||||||
// the HTML layout is: #project-board.board > .project-column .cards > .issue-card
|
// the HTML layout is: #project-board.board > .project-column .cards > .issue-card
|
||||||
const mainBoard = document.querySelector('#project-board');
|
const mainBoard = document.querySelector('#project-board')!;
|
||||||
let boardColumns = mainBoard.querySelectorAll<HTMLElement>('.project-column');
|
let boardColumns = mainBoard.querySelectorAll<HTMLElement>('.project-column');
|
||||||
createSortable(mainBoard, {
|
createSortable(mainBoard, {
|
||||||
group: 'project-column',
|
group: 'project-column',
|
||||||
@@ -49,13 +51,13 @@ async function initRepoProjectSortable(): Promise<void> {
|
|||||||
|
|
||||||
const columnSorting = {
|
const columnSorting = {
|
||||||
columns: Array.from(boardColumns, (column, i) => ({
|
columns: Array.from(boardColumns, (column, i) => ({
|
||||||
columnID: parseInt(column.getAttribute('data-id')),
|
columnID: parseInt(column.getAttribute('data-id')!),
|
||||||
sorting: i,
|
sorting: i,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await POST(mainBoard.getAttribute('data-url'), {
|
await POST(mainBoard.getAttribute('data-url')!, {
|
||||||
data: columnSorting,
|
data: columnSorting,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -65,7 +67,7 @@ async function initRepoProjectSortable(): Promise<void> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const boardColumn of boardColumns) {
|
for (const boardColumn of boardColumns) {
|
||||||
const boardCardList = boardColumn.querySelector('.cards');
|
const boardCardList = boardColumn.querySelector('.cards')!;
|
||||||
createSortable(boardCardList, {
|
createSortable(boardCardList, {
|
||||||
group: 'shared',
|
group: 'shared',
|
||||||
onAdd: moveIssue, // eslint-disable-line @typescript-eslint/no-misused-promises
|
onAdd: moveIssue, // eslint-disable-line @typescript-eslint/no-misused-promises
|
||||||
@@ -77,12 +79,12 @@ async function initRepoProjectSortable(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initRepoProjectColumnEdit(writableProjectBoard: Element): void {
|
function initRepoProjectColumnEdit(writableProjectBoard: Element): void {
|
||||||
const elModal = document.querySelector<HTMLElement>('.ui.modal#project-column-modal-edit');
|
const elModal = document.querySelector<HTMLElement>('.ui.modal#project-column-modal-edit')!;
|
||||||
const elForm = elModal.querySelector<HTMLFormElement>('form');
|
const elForm = elModal.querySelector<HTMLFormElement>('form')!;
|
||||||
|
|
||||||
const elColumnId = elForm.querySelector<HTMLInputElement>('input[name="id"]');
|
const elColumnId = elForm.querySelector<HTMLInputElement>('input[name="id"]')!;
|
||||||
const elColumnTitle = elForm.querySelector<HTMLInputElement>('input[name="title"]');
|
const elColumnTitle = elForm.querySelector<HTMLInputElement>('input[name="title"]')!;
|
||||||
const elColumnColor = elForm.querySelector<HTMLInputElement>('input[name="color"]');
|
const elColumnColor = elForm.querySelector<HTMLInputElement>('input[name="color"]')!;
|
||||||
|
|
||||||
const attrDataColumnId = 'data-modal-project-column-id';
|
const attrDataColumnId = 'data-modal-project-column-id';
|
||||||
const attrDataColumnTitle = 'data-modal-project-column-title-input';
|
const attrDataColumnTitle = 'data-modal-project-column-title-input';
|
||||||
@@ -91,9 +93,9 @@ function initRepoProjectColumnEdit(writableProjectBoard: Element): void {
|
|||||||
// the "new" button is not in project board, so need to query from document
|
// the "new" button is not in project board, so need to query from document
|
||||||
queryElems(document, '.show-project-column-modal-edit', (el) => {
|
queryElems(document, '.show-project-column-modal-edit', (el) => {
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
elColumnId.value = el.getAttribute(attrDataColumnId);
|
elColumnId.value = el.getAttribute(attrDataColumnId)!;
|
||||||
elColumnTitle.value = el.getAttribute(attrDataColumnTitle);
|
elColumnTitle.value = el.getAttribute(attrDataColumnTitle)!;
|
||||||
elColumnColor.value = el.getAttribute(attrDataColumnColor);
|
elColumnColor.value = el.getAttribute(attrDataColumnColor)!;
|
||||||
elColumnColor.dispatchEvent(new Event('input', {bubbles: true})); // trigger the color picker
|
elColumnColor.dispatchEvent(new Event('input', {bubbles: true})); // trigger the color picker
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -116,12 +118,12 @@ function initRepoProjectColumnEdit(writableProjectBoard: Element): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update the newly saved column title and color in the project board (to avoid reload)
|
// update the newly saved column title and color in the project board (to avoid reload)
|
||||||
const elEditButton = writableProjectBoard.querySelector<HTMLButtonElement>(`.show-project-column-modal-edit[${attrDataColumnId}="${columnId}"]`);
|
const elEditButton = writableProjectBoard.querySelector<HTMLButtonElement>(`.show-project-column-modal-edit[${attrDataColumnId}="${columnId}"]`)!;
|
||||||
elEditButton.setAttribute(attrDataColumnTitle, elColumnTitle.value);
|
elEditButton.setAttribute(attrDataColumnTitle, elColumnTitle.value);
|
||||||
elEditButton.setAttribute(attrDataColumnColor, elColumnColor.value);
|
elEditButton.setAttribute(attrDataColumnColor, elColumnColor.value);
|
||||||
|
|
||||||
const elBoardColumn = writableProjectBoard.querySelector<HTMLElement>(`.project-column[data-id="${columnId}"]`);
|
const elBoardColumn = writableProjectBoard.querySelector<HTMLElement>(`.project-column[data-id="${columnId}"]`)!;
|
||||||
const elBoardColumnTitle = elBoardColumn.querySelector<HTMLElement>(`.project-column-title-text`);
|
const elBoardColumnTitle = elBoardColumn.querySelector<HTMLElement>(`.project-column-title-text`)!;
|
||||||
elBoardColumnTitle.textContent = elColumnTitle.value;
|
elBoardColumnTitle.textContent = elColumnTitle.value;
|
||||||
if (elColumnColor.value) {
|
if (elColumnColor.value) {
|
||||||
const textColor = contrastColor(elColumnColor.value);
|
const textColor = contrastColor(elColumnColor.value);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import {hideElem, showElem, type DOMEvent} from '../utils/dom.ts';
|
import {hideElem, showElem} from '../utils/dom.ts';
|
||||||
|
|
||||||
export function initRepoRelease() {
|
export function initRepoRelease() {
|
||||||
document.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
|
document.addEventListener('click', (e: Event) => {
|
||||||
if (e.target.matches('.remove-rel-attach')) {
|
if ((e.target as HTMLElement).matches('.remove-rel-attach')) {
|
||||||
const uuid = e.target.getAttribute('data-uuid');
|
const uuid = (e.target as HTMLElement).getAttribute('data-uuid');
|
||||||
const id = e.target.getAttribute('data-id');
|
const id = (e.target as HTMLElement).getAttribute('data-id');
|
||||||
document.querySelector<HTMLInputElement>(`input[name='attachment-del-${uuid}']`).value = 'true';
|
document.querySelector<HTMLInputElement>(`input[name='attachment-del-${uuid}']`)!.value = 'true';
|
||||||
hideElem(`#attachment-${id}`);
|
hideElem(`#attachment-${id}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -21,17 +21,17 @@ function initTagNameEditor() {
|
|||||||
const el = document.querySelector('#tag-name-editor');
|
const el = document.querySelector('#tag-name-editor');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
const existingTags = JSON.parse(el.getAttribute('data-existing-tags'));
|
const existingTags = JSON.parse(el.getAttribute('data-existing-tags')!);
|
||||||
if (!Array.isArray(existingTags)) return;
|
if (!Array.isArray(existingTags)) return;
|
||||||
|
|
||||||
const defaultTagHelperText = el.getAttribute('data-tag-helper');
|
const defaultTagHelperText = el.getAttribute('data-tag-helper');
|
||||||
const newTagHelperText = el.getAttribute('data-tag-helper-new');
|
const newTagHelperText = el.getAttribute('data-tag-helper-new');
|
||||||
const existingTagHelperText = el.getAttribute('data-tag-helper-existing');
|
const existingTagHelperText = el.getAttribute('data-tag-helper-existing');
|
||||||
|
|
||||||
const tagNameInput = document.querySelector<HTMLInputElement>('#tag-name');
|
const tagNameInput = document.querySelector<HTMLInputElement>('#tag-name')!;
|
||||||
const hideTargetInput = function(tagNameInput: HTMLInputElement) {
|
const hideTargetInput = function(tagNameInput: HTMLInputElement) {
|
||||||
const value = tagNameInput.value;
|
const value = tagNameInput.value;
|
||||||
const tagHelper = document.querySelector('#tag-helper');
|
const tagHelper = document.querySelector('#tag-helper')!;
|
||||||
if (existingTags.includes(value)) {
|
if (existingTags.includes(value)) {
|
||||||
// If the tag already exists, hide the target branch selector.
|
// If the tag already exists, hide the target branch selector.
|
||||||
hideElem('#tag-target-selector');
|
hideElem('#tag-target-selector');
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import type {DOMEvent} from '../utils/dom.ts';
|
|
||||||
|
|
||||||
export function initRepositorySearch() {
|
export function initRepositorySearch() {
|
||||||
const repositorySearchForm = document.querySelector<HTMLFormElement>('#repo-search-form');
|
const repositorySearchForm = document.querySelector<HTMLFormElement>('#repo-search-form');
|
||||||
if (!repositorySearchForm) return;
|
if (!repositorySearchForm) return;
|
||||||
|
|
||||||
repositorySearchForm.addEventListener('change', (e: DOMEvent<Event, HTMLInputElement>) => {
|
repositorySearchForm.addEventListener('change', (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
for (const [key, value] of new FormData(repositorySearchForm).entries()) {
|
for (const [key, value] of new FormData(repositorySearchForm).entries()) {
|
||||||
params.set(key, value.toString());
|
params.set(key, value.toString());
|
||||||
}
|
}
|
||||||
if (e.target.name === 'clear-filter') {
|
if ((e.target as HTMLInputElement).name === 'clear-filter') {
|
||||||
params.delete('archived');
|
params.delete('archived');
|
||||||
params.delete('fork');
|
params.delete('fork');
|
||||||
params.delete('mirror');
|
params.delete('mirror');
|
||||||
|
|||||||
@@ -56,8 +56,10 @@ describe('Repository Branch Settings', () => {
|
|||||||
vi.mocked(POST).mockResolvedValue({ok: true} as Response);
|
vi.mocked(POST).mockResolvedValue({ok: true} as Response);
|
||||||
|
|
||||||
// Mock createSortable to capture and execute the onEnd callback
|
// Mock createSortable to capture and execute the onEnd callback
|
||||||
vi.mocked(createSortable).mockImplementation(async (_el: Element, options: SortableOptions) => {
|
vi.mocked(createSortable).mockImplementation(async (_el: Element, options: SortableOptions | undefined) => {
|
||||||
|
if (options?.onEnd) {
|
||||||
options.onEnd(new Event('SortableEvent') as SortableEvent);
|
options.onEnd(new Event('SortableEvent') as SortableEvent);
|
||||||
|
}
|
||||||
// @ts-expect-error: mock is incomplete
|
// @ts-expect-error: mock is incomplete
|
||||||
return {destroy: vi.fn()} as Sortable;
|
return {destroy: vi.fn()} as Sortable;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ export function initRepoSettingsBranchesDrag() {
|
|||||||
onEnd: () => {
|
onEnd: () => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const itemElems = queryElemChildren(protectedBranchesList, '.item[data-id]');
|
const itemElems = queryElemChildren(protectedBranchesList, '.item[data-id]');
|
||||||
const itemIds = Array.from(itemElems, (el) => parseInt(el.getAttribute('data-id')));
|
const itemIds = Array.from(itemElems, (el) => parseInt(el.getAttribute('data-id')!));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await POST(protectedBranchesList.getAttribute('data-update-priority-url'), {
|
await POST(protectedBranchesList.getAttribute('data-update-priority-url')!, {
|
||||||
data: {
|
data: {
|
||||||
ids: itemIds,
|
ids: itemIds,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,16 +10,16 @@ const {appSubUrl, csrfToken} = window.config;
|
|||||||
function initRepoSettingsCollaboration() {
|
function initRepoSettingsCollaboration() {
|
||||||
// Change collaborator access mode
|
// Change collaborator access mode
|
||||||
for (const dropdownEl of queryElems(document, '.page-content.repository .ui.dropdown.access-mode')) {
|
for (const dropdownEl of queryElems(document, '.page-content.repository .ui.dropdown.access-mode')) {
|
||||||
const textEl = dropdownEl.querySelector(':scope > .text');
|
const textEl = dropdownEl.querySelector(':scope > .text')!;
|
||||||
const $dropdown = fomanticQuery(dropdownEl);
|
const $dropdown = fomanticQuery(dropdownEl);
|
||||||
$dropdown.dropdown({
|
$dropdown.dropdown({
|
||||||
async action(text: string, value: string) {
|
async action(text: string, value: string) {
|
||||||
dropdownEl.classList.add('is-loading', 'loading-icon-2px');
|
dropdownEl.classList.add('is-loading', 'loading-icon-2px');
|
||||||
const lastValue = dropdownEl.getAttribute('data-last-value');
|
const lastValue = dropdownEl.getAttribute('data-last-value')!;
|
||||||
$dropdown.dropdown('hide');
|
$dropdown.dropdown('hide');
|
||||||
try {
|
try {
|
||||||
const uid = dropdownEl.getAttribute('data-uid');
|
const uid = dropdownEl.getAttribute('data-uid')!;
|
||||||
await POST(dropdownEl.getAttribute('data-url'), {data: new URLSearchParams({uid, 'mode': value})});
|
await POST(dropdownEl.getAttribute('data-url')!, {data: new URLSearchParams({uid, 'mode': value})});
|
||||||
textEl.textContent = text;
|
textEl.textContent = text;
|
||||||
dropdownEl.setAttribute('data-last-value', value);
|
dropdownEl.setAttribute('data-last-value', value);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -73,8 +73,8 @@ function initRepoSettingsSearchTeamBox() {
|
|||||||
|
|
||||||
function initRepoSettingsGitHook() {
|
function initRepoSettingsGitHook() {
|
||||||
if (!document.querySelector('.page-content.repository.settings.edit.githook')) return;
|
if (!document.querySelector('.page-content.repository.settings.edit.githook')) return;
|
||||||
const filename = document.querySelector('.hook-filename').textContent;
|
const filename = document.querySelector('.hook-filename')!.textContent;
|
||||||
createMonaco(document.querySelector<HTMLTextAreaElement>('#content'), filename, {language: 'shell'});
|
createMonaco(document.querySelector<HTMLTextAreaElement>('#content')!, filename, {language: 'shell'});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initRepoSettingsBranches() {
|
function initRepoSettingsBranches() {
|
||||||
@@ -82,14 +82,14 @@ function initRepoSettingsBranches() {
|
|||||||
|
|
||||||
for (const el of document.querySelectorAll<HTMLInputElement>('.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<HTMLInputElement>('.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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -100,13 +100,13 @@ function initRepoSettingsBranches() {
|
|||||||
|
|
||||||
// show the `Matched` mark for the status checks that match the pattern
|
// show the `Matched` mark for the status checks that match the pattern
|
||||||
const markMatchedStatusChecks = () => {
|
const markMatchedStatusChecks = () => {
|
||||||
const patterns = (document.querySelector<HTMLTextAreaElement>('#status_check_contexts').value || '').split(/[\r\n]+/);
|
const patterns = (document.querySelector<HTMLTextAreaElement>('#status_check_contexts')!.value || '').split(/[\r\n]+/);
|
||||||
const validPatterns = patterns.map((item) => item.trim()).filter(Boolean as unknown as <T>(x: T | boolean) => x is T);
|
const validPatterns = patterns.map((item) => item.trim()).filter(Boolean as unknown as <T>(x: T | boolean) => x is T);
|
||||||
const marks = document.querySelectorAll('.status-check-matched-mark');
|
const marks = document.querySelectorAll('.status-check-matched-mark');
|
||||||
|
|
||||||
for (const el of marks) {
|
for (const el of marks) {
|
||||||
let matched = false;
|
let matched = false;
|
||||||
const statusCheck = el.getAttribute('data-status-check');
|
const statusCheck = el.getAttribute('data-status-check')!;
|
||||||
for (const pattern of validPatterns) {
|
for (const pattern of validPatterns) {
|
||||||
if (globMatch(statusCheck, pattern, '/')) {
|
if (globMatch(statusCheck, pattern, '/')) {
|
||||||
matched = true;
|
matched = true;
|
||||||
@@ -117,7 +117,7 @@ function initRepoSettingsBranches() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
markMatchedStatusChecks();
|
markMatchedStatusChecks();
|
||||||
document.querySelector('#status_check_contexts').addEventListener('input', onInputDebounce(markMatchedStatusChecks));
|
document.querySelector('#status_check_contexts')!.addEventListener('input', onInputDebounce(markMatchedStatusChecks));
|
||||||
}
|
}
|
||||||
|
|
||||||
function initRepoSettingsOptions() {
|
function initRepoSettingsOptions() {
|
||||||
@@ -130,17 +130,17 @@ function initRepoSettingsOptions() {
|
|||||||
queryElems(document, selector, (el) => el.classList.toggle('disabled', !enabled));
|
queryElems(document, selector, (el) => el.classList.toggle('disabled', !enabled));
|
||||||
};
|
};
|
||||||
queryElems<HTMLInputElement>(pageContent, '.enable-system', (el) => el.addEventListener('change', () => {
|
queryElems<HTMLInputElement>(pageContent, '.enable-system', (el) => el.addEventListener('change', () => {
|
||||||
toggleTargetContextPanel(el.getAttribute('data-target'), el.checked);
|
toggleTargetContextPanel(el.getAttribute('data-target')!, el.checked);
|
||||||
toggleTargetContextPanel(el.getAttribute('data-context'), !el.checked);
|
toggleTargetContextPanel(el.getAttribute('data-context')!, !el.checked);
|
||||||
}));
|
}));
|
||||||
queryElems<HTMLInputElement>(pageContent, '.enable-system-radio', (el) => el.addEventListener('change', () => {
|
queryElems<HTMLInputElement>(pageContent, '.enable-system-radio', (el) => el.addEventListener('change', () => {
|
||||||
toggleTargetContextPanel(el.getAttribute('data-target'), el.value === 'true');
|
toggleTargetContextPanel(el.getAttribute('data-target')!, el.value === 'true');
|
||||||
toggleTargetContextPanel(el.getAttribute('data-context'), el.value === 'false');
|
toggleTargetContextPanel(el.getAttribute('data-context')!, el.value === 'false');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
queryElems<HTMLInputElement>(pageContent, '.js-tracker-issue-style', (el) => el.addEventListener('change', () => {
|
queryElems<HTMLInputElement>(pageContent, '.js-tracker-issue-style', (el) => el.addEventListener('change', () => {
|
||||||
const checkedVal = el.value;
|
const checkedVal = el.value;
|
||||||
pageContent.querySelector('#tracker-issue-style-regex-box').classList.toggle('disabled', checkedVal !== 'regexp');
|
pageContent.querySelector('#tracker-issue-style-regex-box')!.classList.toggle('disabled', checkedVal !== 'regexp');
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ export function initUnicodeEscapeButton() {
|
|||||||
|
|
||||||
const unicodeContentSelector = btn.getAttribute('data-unicode-content-selector');
|
const unicodeContentSelector = btn.getAttribute('data-unicode-content-selector');
|
||||||
const container = unicodeContentSelector ?
|
const container = unicodeContentSelector ?
|
||||||
document.querySelector(unicodeContentSelector) :
|
document.querySelector(unicodeContentSelector)! :
|
||||||
btn.closest('.file-content, .non-diff-file-content');
|
btn.closest('.file-content, .non-diff-file-content')!;
|
||||||
const fileView = container.querySelector('.file-code, .file-view') ?? container;
|
const fileView = container.querySelector('.file-code, .file-view') ?? container;
|
||||||
if (btn.matches('.escape-button')) {
|
if (btn.matches('.escape-button')) {
|
||||||
fileView.classList.add('unicode-escaped');
|
fileView.classList.add('unicode-escaped');
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ function isUserSignedIn() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function toggleSidebar(btn: HTMLElement) {
|
async function toggleSidebar(btn: HTMLElement) {
|
||||||
const elToggleShow = document.querySelector('.repo-view-file-tree-toggle[data-toggle-action="show"]');
|
const elToggleShow = document.querySelector('.repo-view-file-tree-toggle[data-toggle-action="show"]')!;
|
||||||
const elFileTreeContainer = document.querySelector('.repo-view-file-tree-container');
|
const elFileTreeContainer = document.querySelector('.repo-view-file-tree-container')!;
|
||||||
const shouldShow = btn.getAttribute('data-toggle-action') === 'show';
|
const shouldShow = btn.getAttribute('data-toggle-action') === 'show';
|
||||||
toggleElem(elFileTreeContainer, shouldShow);
|
toggleElem(elFileTreeContainer, shouldShow);
|
||||||
toggleElem(elToggleShow, !shouldShow);
|
toggleElem(elToggleShow, !shouldShow);
|
||||||
@@ -32,7 +32,7 @@ export async function initRepoViewFileTree() {
|
|||||||
|
|
||||||
registerGlobalEventFunc('click', 'onRepoViewFileTreeToggle', toggleSidebar);
|
registerGlobalEventFunc('click', 'onRepoViewFileTreeToggle', toggleSidebar);
|
||||||
|
|
||||||
const fileTree = sidebar.querySelector('#view-file-tree');
|
const fileTree = sidebar.querySelector('#view-file-tree')!;
|
||||||
createApp(ViewFileTree, {
|
createApp(ViewFileTree, {
|
||||||
repoLink: fileTree.getAttribute('data-repo-link'),
|
repoLink: fileTree.getAttribute('data-repo-link'),
|
||||||
treePath: fileTree.getAttribute('data-tree-path'),
|
treePath: fileTree.getAttribute('data-tree-path'),
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ async function initRepoWikiFormEditor() {
|
|||||||
const editArea = document.querySelector<HTMLTextAreaElement>('.repository.wiki .combo-markdown-editor textarea');
|
const editArea = document.querySelector<HTMLTextAreaElement>('.repository.wiki .combo-markdown-editor textarea');
|
||||||
if (!editArea) return;
|
if (!editArea) return;
|
||||||
|
|
||||||
const form = document.querySelector('.repository.wiki.new .ui.form');
|
const form = document.querySelector('.repository.wiki.new .ui.form')!;
|
||||||
const editorContainer = form.querySelector<HTMLElement>('.combo-markdown-editor');
|
const editorContainer = form.querySelector<HTMLElement>('.combo-markdown-editor')!;
|
||||||
let editor: ComboMarkdownEditor;
|
let editor: ComboMarkdownEditor;
|
||||||
|
|
||||||
let renderRequesting = false;
|
let renderRequesting = false;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export function initSshKeyFormParser() {
|
|||||||
// Parse SSH Key
|
// Parse SSH Key
|
||||||
document.querySelector<HTMLTextAreaElement>('#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] !== '') {
|
||||||
title.value = arrays[2];
|
title.value = arrays[2];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
export function initTableSort() {
|
export function initTableSort() {
|
||||||
for (const header of document.querySelectorAll('th[data-sortt-asc]') || []) {
|
for (const header of document.querySelectorAll('th[data-sortt-asc]') || []) {
|
||||||
const sorttAsc = header.getAttribute('data-sortt-asc');
|
const sorttAsc = header.getAttribute('data-sortt-asc')!;
|
||||||
const sorttDesc = header.getAttribute('data-sortt-desc');
|
const sorttDesc = header.getAttribute('data-sortt-desc')!;
|
||||||
const sorttDefault = header.getAttribute('data-sortt-default');
|
const sorttDefault = header.getAttribute('data-sortt-default')!;
|
||||||
header.addEventListener('click', () => {
|
header.addEventListener('click', () => {
|
||||||
tableSort(sorttAsc, sorttDesc, sorttDefault);
|
tableSort(sorttAsc, sorttDesc, sorttDefault);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export async function initUserAuthWebAuthn() {
|
|||||||
|
|
||||||
// webauthn is only supported on secure contexts
|
// webauthn is only supported on secure contexts
|
||||||
if (!window.isSecureContext) {
|
if (!window.isSecureContext) {
|
||||||
hideElem(elSignInPasskeyBtn);
|
if (elSignInPasskeyBtn) hideElem(elSignInPasskeyBtn);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ async function loginPasskey() {
|
|||||||
const clientDataJSON = new Uint8Array(credResp.clientDataJSON);
|
const clientDataJSON = new Uint8Array(credResp.clientDataJSON);
|
||||||
const rawId = new Uint8Array(credential.rawId);
|
const rawId = new Uint8Array(credential.rawId);
|
||||||
const sig = new Uint8Array(credResp.signature);
|
const sig = new Uint8Array(credResp.signature);
|
||||||
const userHandle = new Uint8Array(credResp.userHandle);
|
const userHandle = new Uint8Array(credResp.userHandle ?? []);
|
||||||
|
|
||||||
const res = await POST(`${appSubUrl}/user/webauthn/passkey/login`, {
|
const res = await POST(`${appSubUrl}/user/webauthn/passkey/login`, {
|
||||||
data: {
|
data: {
|
||||||
@@ -183,7 +183,7 @@ async function webauthnRegistered(newCredential: any) { // TODO: Credential type
|
|||||||
}
|
}
|
||||||
|
|
||||||
function webAuthnError(errorType: string, message:string = '') {
|
function webAuthnError(errorType: string, message:string = '') {
|
||||||
const elErrorMsg = document.querySelector(`#webauthn-error-msg`);
|
const elErrorMsg = document.querySelector(`#webauthn-error-msg`)!;
|
||||||
|
|
||||||
if (errorType === 'general') {
|
if (errorType === 'general') {
|
||||||
elErrorMsg.textContent = message || 'unknown error';
|
elErrorMsg.textContent = message || 'unknown error';
|
||||||
@@ -228,7 +228,7 @@ export function initUserAuthWebAuthnRegister() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function webAuthnRegisterRequest() {
|
async function webAuthnRegisterRequest() {
|
||||||
const elNickname = document.querySelector<HTMLInputElement>('#nickname');
|
const elNickname = document.querySelector<HTMLInputElement>('#nickname')!;
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('name', elNickname.value);
|
formData.append('name', elNickname.value);
|
||||||
@@ -246,7 +246,7 @@ async function webAuthnRegisterRequest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const options = await res.json();
|
const options = await res.json();
|
||||||
elNickname.closest('div.field').classList.remove('error');
|
elNickname.closest('div.field')!.classList.remove('error');
|
||||||
|
|
||||||
options.publicKey.challenge = decodeURLEncodedBase64(options.publicKey.challenge);
|
options.publicKey.challenge = decodeURLEncodedBase64(options.publicKey.challenge);
|
||||||
options.publicKey.user.id = decodeURLEncodedBase64(options.publicKey.user.id);
|
options.publicKey.user.id = decodeURLEncodedBase64(options.publicKey.user.id);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export function initUserCheckAppUrl() {
|
|||||||
export function initUserAuthOauth2() {
|
export function initUserAuthOauth2() {
|
||||||
const outer = document.querySelector('#oauth2-login-navigator');
|
const outer = document.querySelector('#oauth2-login-navigator');
|
||||||
if (!outer) return;
|
if (!outer) return;
|
||||||
const inner = document.querySelector('#oauth2-login-navigator-inner');
|
const inner = document.querySelector('#oauth2-login-navigator-inner')!;
|
||||||
|
|
||||||
checkAppUrl();
|
checkAppUrl();
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ export function initUserSettings() {
|
|||||||
const usernameInput = document.querySelector<HTMLInputElement>('#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')!;
|
||||||
const promptRedirect = document.querySelector('#name-change-redirect-prompt');
|
const promptRedirect = document.querySelector('#name-change-redirect-prompt')!;
|
||||||
if (this.value.toLowerCase() !== this.getAttribute('data-name').toLowerCase()) {
|
if (this.value.toLowerCase() !== this.getAttribute('data-name')!.toLowerCase()) {
|
||||||
showElem(prompt);
|
showElem(prompt);
|
||||||
showElem(promptRedirect);
|
showElem(promptRedirect);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ export function initHtmx() {
|
|||||||
// https://htmx.org/events/#htmx:sendError
|
// https://htmx.org/events/#htmx:sendError
|
||||||
document.body.addEventListener('htmx:sendError', (event: Partial<HtmxEvent>) => {
|
document.body.addEventListener('htmx:sendError', (event: Partial<HtmxEvent>) => {
|
||||||
// TODO: add translations
|
// TODO: add translations
|
||||||
showErrorToast(`Network error when calling ${event.detail.requestConfig.path}`);
|
showErrorToast(`Network error when calling ${event.detail!.requestConfig.path}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// https://htmx.org/events/#htmx:responseError
|
// https://htmx.org/events/#htmx:responseError
|
||||||
document.body.addEventListener('htmx:responseError', (event: Partial<HtmxEvent>) => {
|
document.body.addEventListener('htmx:responseError', (event: Partial<HtmxEvent>) => {
|
||||||
// TODO: add translations
|
// TODO: add translations
|
||||||
showErrorToast(`Error ${event.detail.xhr.status} when calling ${event.detail.requestConfig.path}`);
|
showErrorToast(`Error ${event.detail!.xhr.status} when calling ${event.detail!.requestConfig.path}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const hasPrefix = (str: string): boolean => str.startsWith('user-content-');
|
|||||||
// scroll to anchor while respecting the `user-content` prefix that exists on the target
|
// scroll to anchor while respecting the `user-content` prefix that exists on the target
|
||||||
function scrollToAnchor(encodedId?: string): void {
|
function scrollToAnchor(encodedId?: string): void {
|
||||||
// FIXME: need to rewrite this function with new a better markup anchor generation logic, too many tricks here
|
// FIXME: need to rewrite this function with new a better markup anchor generation logic, too many tricks here
|
||||||
let elemId: string;
|
let elemId: string | undefined;
|
||||||
try {
|
try {
|
||||||
elemId = decodeURIComponent(encodedId ?? '');
|
elemId = decodeURIComponent(encodedId ?? '');
|
||||||
} catch {} // ignore the errors, since the "encodedId" is from user's input
|
} catch {} // ignore the errors, since the "encodedId" is from user's input
|
||||||
@@ -44,7 +44,7 @@ export function initMarkupAnchors(): void {
|
|||||||
// remove `user-content-` prefix from links so they don't show in url bar when clicked
|
// remove `user-content-` prefix from links so they don't show in url bar when clicked
|
||||||
for (const a of markupEl.querySelectorAll<HTMLAnchorElement>('a[href^="#"]')) {
|
for (const a of markupEl.querySelectorAll<HTMLAnchorElement>('a[href^="#"]')) {
|
||||||
const href = a.getAttribute('href');
|
const href = a.getAttribute('href');
|
||||||
if (!href.startsWith('#user-content-')) continue;
|
if (!href?.startsWith('#user-content-')) continue;
|
||||||
a.setAttribute('href', `#${removePrefix(href.substring(1))}`);
|
a.setAttribute('href', `#${removePrefix(href.substring(1))}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,6 @@ export function initMarkupCodeCopy(elMarkup: HTMLElement): void {
|
|||||||
btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, ''));
|
btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, ''));
|
||||||
// we only want to use `.code-block-container` if it exists, no matter `.code-block` exists or not.
|
// we only want to use `.code-block-container` if it exists, no matter `.code-block` exists or not.
|
||||||
const btnContainer = el.closest('.code-block-container') ?? el.closest('.code-block');
|
const btnContainer = el.closest('.code-block-container') ?? el.closest('.code-block');
|
||||||
btnContainer.append(btn);
|
btnContainer!.append(btn);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export async function initMarkupCodeMermaid(elMarkup: HTMLElement): Promise<void
|
|||||||
});
|
});
|
||||||
|
|
||||||
const pre = el.closest('pre');
|
const pre = el.closest('pre');
|
||||||
if (pre.hasAttribute('data-render-done')) return;
|
if (!pre || pre.hasAttribute('data-render-done')) return;
|
||||||
|
|
||||||
const source = el.textContent;
|
const source = el.textContent;
|
||||||
if (mermaidMaxSourceCharacters >= 0 && source.length > mermaidMaxSourceCharacters) {
|
if (mermaidMaxSourceCharacters >= 0 && source.length > mermaidMaxSourceCharacters) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export function showMarkupRefIssuePopup(e: MouseEvent | FocusEvent) {
|
|||||||
if (getAttachedTippyInstance(refIssue)) return;
|
if (getAttachedTippyInstance(refIssue)) return;
|
||||||
if (refIssue.classList.contains('ref-external-issue')) return;
|
if (refIssue.classList.contains('ref-external-issue')) return;
|
||||||
|
|
||||||
const issuePathInfo = parseIssueHref(refIssue.getAttribute('href'));
|
const issuePathInfo = parseIssueHref(refIssue.getAttribute('href')!);
|
||||||
if (!issuePathInfo.ownerName) return;
|
if (!issuePathInfo.ownerName) return;
|
||||||
|
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {generateElemId, queryElemChildren} from '../utils/dom.ts';
|
|||||||
import {isDarkTheme} from '../utils.ts';
|
import {isDarkTheme} from '../utils.ts';
|
||||||
|
|
||||||
export async function loadRenderIframeContent(iframe: HTMLIFrameElement) {
|
export async function loadRenderIframeContent(iframe: HTMLIFrameElement) {
|
||||||
const iframeSrcUrl = iframe.getAttribute('data-src');
|
const iframeSrcUrl = iframe.getAttribute('data-src')!;
|
||||||
if (!iframe.id) iframe.id = generateElemId('gitea-iframe-');
|
if (!iframe.id) iframe.id = generateElemId('gitea-iframe-');
|
||||||
|
|
||||||
window.addEventListener('message', (e) => {
|
window.addEventListener('message', (e) => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const preventListener = (e: Event) => e.preventDefault();
|
|||||||
export function initMarkupTasklist(elMarkup: HTMLElement): void {
|
export function initMarkupTasklist(elMarkup: HTMLElement): void {
|
||||||
if (!elMarkup.matches('[data-can-edit=true]')) return;
|
if (!elMarkup.matches('[data-can-edit=true]')) return;
|
||||||
|
|
||||||
const container = elMarkup.parentNode;
|
const container = elMarkup.parentNode!;
|
||||||
const checkboxes = elMarkup.querySelectorAll<HTMLInputElement>(`.task-list-item input[type=checkbox]`);
|
const checkboxes = elMarkup.querySelectorAll<HTMLInputElement>(`.task-list-item input[type=checkbox]`);
|
||||||
|
|
||||||
for (const checkbox of checkboxes) {
|
for (const checkbox of checkboxes) {
|
||||||
@@ -24,9 +24,9 @@ export function initMarkupTasklist(elMarkup: HTMLElement): void {
|
|||||||
checkbox.setAttribute('data-editable', 'true');
|
checkbox.setAttribute('data-editable', 'true');
|
||||||
checkbox.addEventListener('input', async () => {
|
checkbox.addEventListener('input', async () => {
|
||||||
const checkboxCharacter = checkbox.checked ? 'x' : ' ';
|
const checkboxCharacter = checkbox.checked ? 'x' : ' ';
|
||||||
const position = parseInt(checkbox.getAttribute('data-source-position')) + 1;
|
const position = parseInt(checkbox.getAttribute('data-source-position')!) + 1;
|
||||||
|
|
||||||
const rawContent = container.querySelector('.raw-content');
|
const rawContent = container.querySelector('.raw-content')!;
|
||||||
const oldContent = rawContent.textContent;
|
const oldContent = rawContent.textContent;
|
||||||
|
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
@@ -53,10 +53,10 @@ export function initMarkupTasklist(elMarkup: HTMLElement): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const editContentZone = container.querySelector<HTMLDivElement>('.edit-content-zone');
|
const editContentZone = container.querySelector<HTMLDivElement>('.edit-content-zone')!;
|
||||||
const updateUrl = editContentZone.getAttribute('data-update-url');
|
const updateUrl = editContentZone.getAttribute('data-update-url')!;
|
||||||
const context = editContentZone.getAttribute('data-context');
|
const context = editContentZone.getAttribute('data-context')!;
|
||||||
const contentVersion = editContentZone.getAttribute('data-content-version');
|
const contentVersion = editContentZone.getAttribute('data-content-version')!;
|
||||||
|
|
||||||
const requestBody = new FormData();
|
const requestBody = new FormData();
|
||||||
requestBody.append('ignore_attachments', 'true');
|
requestBody.append('ignore_attachments', 'true');
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export type DiffTreeEntry = {
|
|||||||
DiffStatus: DiffStatus,
|
DiffStatus: DiffStatus,
|
||||||
EntryMode: string,
|
EntryMode: string,
|
||||||
IsViewed: boolean,
|
IsViewed: boolean,
|
||||||
Children: DiffTreeEntry[],
|
Children: DiffTreeEntry[] | null,
|
||||||
FileIcon: string,
|
FileIcon: string,
|
||||||
ParentEntry?: DiffTreeEntry,
|
ParentEntry?: DiffTreeEntry,
|
||||||
};
|
};
|
||||||
@@ -25,7 +25,7 @@ type DiffFileTree = {
|
|||||||
folderIcon: string;
|
folderIcon: string;
|
||||||
folderOpenIcon: string;
|
folderOpenIcon: string;
|
||||||
diffFileTree: DiffFileTreeData;
|
diffFileTree: DiffFileTreeData;
|
||||||
fullNameMap?: Record<string, DiffTreeEntry>
|
fullNameMap: Record<string, DiffTreeEntry>
|
||||||
fileTreeIsVisible: boolean;
|
fileTreeIsVisible: boolean;
|
||||||
selectedItem: string;
|
selectedItem: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ const safeMethods = new Set(['GET', 'HEAD', 'OPTIONS', 'TRACE']);
|
|||||||
// which will automatically set an appropriate headers. For json content, only object
|
// which will automatically set an appropriate headers. For json content, only object
|
||||||
// and array types are currently supported.
|
// and array types are currently supported.
|
||||||
export function request(url: string, {method = 'GET', data, headers = {}, ...other}: RequestOpts = {}): Promise<Response> {
|
export function request(url: string, {method = 'GET', data, headers = {}, ...other}: RequestOpts = {}): Promise<Response> {
|
||||||
let body: string | FormData | URLSearchParams;
|
let body: string | FormData | URLSearchParams | undefined;
|
||||||
let contentType: string;
|
let contentType: string | undefined;
|
||||||
if (data instanceof FormData || data instanceof URLSearchParams) {
|
if (data instanceof FormData || data instanceof URLSearchParams) {
|
||||||
body = data;
|
body = data;
|
||||||
} else if (isObject(data) || Array.isArray(data)) {
|
} else if (isObject(data) || Array.isArray(data)) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export function initFomanticTab() {
|
|||||||
const tabName = elBtn.getAttribute('data-tab');
|
const tabName = elBtn.getAttribute('data-tab');
|
||||||
if (!tabName) continue;
|
if (!tabName) continue;
|
||||||
elBtn.addEventListener('click', () => {
|
elBtn.addEventListener('click', () => {
|
||||||
const elTab = document.querySelector(`.ui.tab[data-tab="${tabName}"]`);
|
const elTab = document.querySelector(`.ui.tab[data-tab="${tabName}"]`)!;
|
||||||
queryElemSiblings(elTab, `.ui.tab`, (el) => el.classList.remove('active'));
|
queryElemSiblings(elTab, `.ui.tab`, (el) => el.classList.remove('active'));
|
||||||
queryElemSiblings(elBtn, `[data-tab]`, (el) => el.classList.remove('active'));
|
queryElemSiblings(elBtn, `[data-tab]`, (el) => el.classList.remove('active'));
|
||||||
elBtn.classList.add('active');
|
elBtn.classList.add('active');
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function registerGlobalInitFunc<T extends HTMLElement>(name: string, hand
|
|||||||
}
|
}
|
||||||
|
|
||||||
function callGlobalInitFunc(el: HTMLElement) {
|
function callGlobalInitFunc(el: HTMLElement) {
|
||||||
const initFunc = el.getAttribute('data-global-init');
|
const initFunc = el.getAttribute('data-global-init')!;
|
||||||
const func = globalInitFuncs[initFunc];
|
const func = globalInitFuncs[initFunc];
|
||||||
if (!func) throw new Error(`Global init function "${initFunc}" not found`);
|
if (!func) throw new Error(`Global init function "${initFunc}" not found`);
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ function attachGlobalEvents() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initGlobalSelectorObserver(perfTracer?: InitPerformanceTracer): void {
|
export function initGlobalSelectorObserver(perfTracer: InitPerformanceTracer | null): void {
|
||||||
if (globalSelectorObserverInited) throw new Error('initGlobalSelectorObserver() already called');
|
if (globalSelectorObserverInited) throw new Error('initGlobalSelectorObserver() already called');
|
||||||
globalSelectorObserverInited = true;
|
globalSelectorObserverInited = true;
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ export async function createSortable(el: Element, opts: {handle?: string} & Sort
|
|||||||
animation: 150,
|
animation: 150,
|
||||||
ghostClass: 'card-ghost',
|
ghostClass: 'card-ghost',
|
||||||
onChoose: (e: SortableEvent) => {
|
onChoose: (e: SortableEvent) => {
|
||||||
const handle = opts.handle ? e.item.querySelector(opts.handle) : e.item;
|
const handle = opts.handle ? e.item.querySelector(opts.handle)! : e.item;
|
||||||
handle.classList.add('tw-cursor-grabbing');
|
handle.classList.add('tw-cursor-grabbing');
|
||||||
opts.onChoose?.(e);
|
opts.onChoose?.(e);
|
||||||
},
|
},
|
||||||
onUnchoose: (e: SortableEvent) => {
|
onUnchoose: (e: SortableEvent) => {
|
||||||
const handle = opts.handle ? e.item.querySelector(opts.handle) : e.item;
|
const handle = opts.handle ? e.item.querySelector(opts.handle)! : e.item;
|
||||||
handle.classList.remove('tw-cursor-grabbing');
|
handle.classList.remove('tw-cursor-grabbing');
|
||||||
opts.onUnchoose?.(e);
|
opts.onUnchoose?.(e);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export function createTippy(target: Element, opts: TippyOpts = {}): Instance {
|
|||||||
*
|
*
|
||||||
* Note: "tooltip" doesn't equal to "tippy". "tooltip" means a auto-popup content, it just uses tippy as the implementation.
|
* Note: "tooltip" doesn't equal to "tippy". "tooltip" means a auto-popup content, it just uses tippy as the implementation.
|
||||||
*/
|
*/
|
||||||
function attachTooltip(target: Element, content: Content = null): Instance {
|
function attachTooltip(target: Element, content: Content | null = null): Instance | null {
|
||||||
switchTitleToTooltip(target);
|
switchTitleToTooltip(target);
|
||||||
|
|
||||||
content = content ?? target.getAttribute('data-tooltip-content');
|
content = content ?? target.getAttribute('data-tooltip-content');
|
||||||
@@ -125,7 +125,7 @@ function switchTitleToTooltip(target: Element): void {
|
|||||||
* 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(this: HTMLElement, e: Event): void {
|
function lazyTooltipOnMouseHover(this: HTMLElement, e: Event): void {
|
||||||
e.target.removeEventListener('mouseover', lazyTooltipOnMouseHover, true);
|
(e.target as HTMLElement).removeEventListener('mouseover', lazyTooltipOnMouseHover, true);
|
||||||
attachTooltip(this);
|
attachTooltip(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ export function initGlobalTooltips(): void {
|
|||||||
export function showTemporaryTooltip(target: Element, content: Content): void {
|
export function showTemporaryTooltip(target: Element, content: Content): void {
|
||||||
// if the target is inside a dropdown or tippy popup, the menu will be hidden soon
|
// if the target is inside a dropdown or tippy popup, the menu will be hidden soon
|
||||||
// so display the tooltip on the "aria-controls" element or dropdown instead
|
// so display the tooltip on the "aria-controls" element or dropdown instead
|
||||||
let refClientRect: DOMRect;
|
let refClientRect: DOMRect | undefined;
|
||||||
const popupTippyId = target.closest(`[data-tippy-root]`)?.id;
|
const popupTippyId = target.closest(`[data-tippy-root]`)?.id;
|
||||||
if (popupTippyId) {
|
if (popupTippyId) {
|
||||||
// for example, the "Copy Permalink" button in the "File View" page for the selected lines
|
// for example, the "Copy Permalink" button in the "File View" page for the selected lines
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ function showToast(message: string, level: Intent, {gravity, position, duration,
|
|||||||
if (preventDuplicates) {
|
if (preventDuplicates) {
|
||||||
const toastEl = parent.querySelector(`:scope > .toastify.on[data-toast-unique-key="${CSS.escape(duplicateKey)}"]`);
|
const toastEl = parent.querySelector(`:scope > .toastify.on[data-toast-unique-key="${CSS.escape(duplicateKey)}"]`);
|
||||||
if (toastEl) {
|
if (toastEl) {
|
||||||
const toastDupNumEl = toastEl.querySelector('.toast-duplicate-number');
|
const toastDupNumEl = toastEl.querySelector('.toast-duplicate-number')!;
|
||||||
showElem(toastDupNumEl);
|
showElem(toastDupNumEl);
|
||||||
toastDupNumEl.textContent = String(Number(toastDupNumEl.textContent) + 1);
|
toastDupNumEl.textContent = String(Number(toastDupNumEl.textContent) + 1);
|
||||||
animateOnce(toastDupNumEl, 'pulse-1p5-200');
|
animateOnce(toastDupNumEl, 'pulse-1p5-200');
|
||||||
@@ -77,9 +77,10 @@ function showToast(message: string, level: Intent, {gravity, position, duration,
|
|||||||
});
|
});
|
||||||
|
|
||||||
toast.showToast();
|
toast.showToast();
|
||||||
toast.toastElement.querySelector('.toast-close').addEventListener('click', () => toast.hideToast());
|
const el = toast.toastElement as ToastifyElement;
|
||||||
toast.toastElement.setAttribute('data-toast-unique-key', duplicateKey);
|
el.querySelector('.toast-close')!.addEventListener('click', () => toast.hideToast());
|
||||||
(toast.toastElement as ToastifyElement)._giteaToastifyInstance = toast;
|
el.setAttribute('data-toast-unique-key', duplicateKey);
|
||||||
|
el._giteaToastifyInstance = toast;
|
||||||
return toast;
|
return toast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import {showInfoToast, showWarningToast, showErrorToast} from '../modules/toast.ts';
|
import {showInfoToast, showWarningToast, showErrorToast, type Toast} from '../modules/toast.ts';
|
||||||
|
|
||||||
|
type LevelMap = Record<string, (message: string) => Toast | null>;
|
||||||
|
|
||||||
function initDevtestToast() {
|
function initDevtestToast() {
|
||||||
const levelMap: Record<string, any> = {info: showInfoToast, warning: showWarningToast, error: showErrorToast};
|
const levelMap: LevelMap = {info: showInfoToast, warning: showWarningToast, error: showErrorToast};
|
||||||
for (const el of document.querySelectorAll('.toast-test-button')) {
|
for (const el of document.querySelectorAll('.toast-test-button')) {
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
const level = el.getAttribute('data-toast-level');
|
const level = el.getAttribute('data-toast-level')!;
|
||||||
const message = el.getAttribute('data-toast-message');
|
const message = el.getAttribute('data-toast-message')!;
|
||||||
levelMap[level](message);
|
levelMap[level](message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function mainExternalRenderIframe() {
|
|||||||
// safe links: "./any", "../any", "/any", "//host/any", "http://host/any", "https://host/any"
|
// safe links: "./any", "../any", "/any", "//host/any", "http://host/any", "https://host/any"
|
||||||
if (href.startsWith('.') || href.startsWith('/') || href.startsWith('http://') || href.startsWith('https://')) {
|
if (href.startsWith('.') || href.startsWith('/') || href.startsWith('http://') || href.startsWith('https://')) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
openIframeLink(href, el.getAttribute('target'));
|
openIframeLink(href, el.getAttribute('target')!);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user