mirror of
https://github.com/go-gitea/gitea
synced 2025-02-02 04:54:34 +00:00
Enable Typescript noImplicitAny
(#33322)
Enable `noImplicitAny` and fix all issues. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
6fe4d1c038
commit
c7f4ca2653
@ -1,12 +1,13 @@
|
|||||||
import {expect} from '@playwright/test';
|
import {expect} from '@playwright/test';
|
||||||
import {env} from 'node:process';
|
import {env} from 'node:process';
|
||||||
|
import type {Browser, Page, WorkerInfo} from '@playwright/test';
|
||||||
|
|
||||||
const ARTIFACTS_PATH = `tests/e2e/test-artifacts`;
|
const ARTIFACTS_PATH = `tests/e2e/test-artifacts`;
|
||||||
const LOGIN_PASSWORD = 'password';
|
const LOGIN_PASSWORD = 'password';
|
||||||
|
|
||||||
// log in user and store session info. This should generally be
|
// log in user and store session info. This should generally be
|
||||||
// run in test.beforeAll(), then the session can be loaded in tests.
|
// run in test.beforeAll(), then the session can be loaded in tests.
|
||||||
export async function login_user(browser, workerInfo, user) {
|
export async function login_user(browser: Browser, workerInfo: WorkerInfo, user: string) {
|
||||||
// Set up a new context
|
// Set up a new context
|
||||||
const context = await browser.newContext();
|
const context = await browser.newContext();
|
||||||
const page = await context.newPage();
|
const page = await context.newPage();
|
||||||
@ -17,8 +18,8 @@ export async function login_user(browser, workerInfo, user) {
|
|||||||
expect(response?.status()).toBe(200); // Status OK
|
expect(response?.status()).toBe(200); // Status OK
|
||||||
|
|
||||||
// Fill out form
|
// Fill out form
|
||||||
await page.type('input[name=user_name]', user);
|
await page.locator('input[name=user_name]').fill(user);
|
||||||
await page.type('input[name=password]', LOGIN_PASSWORD);
|
await page.locator('input[name=password]').fill(LOGIN_PASSWORD);
|
||||||
await page.click('form button.ui.primary.button:visible');
|
await page.click('form button.ui.primary.button:visible');
|
||||||
|
|
||||||
await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle
|
await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle
|
||||||
@ -31,7 +32,7 @@ export async function login_user(browser, workerInfo, user) {
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function load_logged_in_context(browser, workerInfo, user) {
|
export async function load_logged_in_context(browser: Browser, workerInfo: WorkerInfo, user: string) {
|
||||||
let context;
|
let context;
|
||||||
try {
|
try {
|
||||||
context = await browser.newContext({storageState: `${ARTIFACTS_PATH}/state-${user}-${workerInfo.workerIndex}.json`});
|
context = await browser.newContext({storageState: `${ARTIFACTS_PATH}/state-${user}-${workerInfo.workerIndex}.json`});
|
||||||
@ -43,7 +44,7 @@ export async function load_logged_in_context(browser, workerInfo, user) {
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function save_visual(page) {
|
export async function save_visual(page: Page) {
|
||||||
// Optionally include visual testing
|
// Optionally include visual testing
|
||||||
if (env.VISUAL_TEST) {
|
if (env.VISUAL_TEST) {
|
||||||
await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle
|
await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
"stripInternal": true,
|
"stripInternal": true,
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"strictFunctionTypes": true,
|
"strictFunctionTypes": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
|
@ -130,12 +130,12 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
changeTab(t) {
|
changeTab(tab: string) {
|
||||||
this.tab = t;
|
this.tab = tab;
|
||||||
this.updateHistory();
|
this.updateHistory();
|
||||||
},
|
},
|
||||||
|
|
||||||
changeReposFilter(filter) {
|
changeReposFilter(filter: string) {
|
||||||
this.reposFilter = filter;
|
this.reposFilter = filter;
|
||||||
this.repos = [];
|
this.repos = [];
|
||||||
this.page = 1;
|
this.page = 1;
|
||||||
@ -218,7 +218,7 @@ export default defineComponent({
|
|||||||
this.searchRepos();
|
this.searchRepos();
|
||||||
},
|
},
|
||||||
|
|
||||||
changePage(page) {
|
changePage(page: number) {
|
||||||
this.page = page;
|
this.page = page;
|
||||||
if (this.page > this.finalPage) {
|
if (this.page > this.finalPage) {
|
||||||
this.page = this.finalPage;
|
this.page = this.finalPage;
|
||||||
@ -256,7 +256,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (searchedURL === this.searchURL) {
|
if (searchedURL === this.searchURL) {
|
||||||
this.repos = json.data.map((webSearchRepo) => {
|
this.repos = json.data.map((webSearchRepo: any) => {
|
||||||
return {
|
return {
|
||||||
...webSearchRepo.repository,
|
...webSearchRepo.repository,
|
||||||
latest_commit_status_state: webSearchRepo.latest_commit_status?.State, // if latest_commit_status is null, it means there is no commit status
|
latest_commit_status_state: webSearchRepo.latest_commit_status?.State, // if latest_commit_status is null, it means there is no commit status
|
||||||
@ -264,7 +264,7 @@ export default defineComponent({
|
|||||||
locale_latest_commit_status_state: webSearchRepo.locale_latest_commit_status,
|
locale_latest_commit_status_state: webSearchRepo.locale_latest_commit_status,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const count = response.headers.get('X-Total-Count');
|
const count = Number(response.headers.get('X-Total-Count'));
|
||||||
if (searchedQuery === '' && searchedMode === '' && this.archivedFilter === 'both') {
|
if (searchedQuery === '' && searchedMode === '' && this.archivedFilter === 'both') {
|
||||||
this.reposTotalCount = count;
|
this.reposTotalCount = count;
|
||||||
}
|
}
|
||||||
@ -275,7 +275,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
repoIcon(repo) {
|
repoIcon(repo: any) {
|
||||||
if (repo.fork) {
|
if (repo.fork) {
|
||||||
return 'octicon-repo-forked';
|
return 'octicon-repo-forked';
|
||||||
} else if (repo.mirror) {
|
} else if (repo.mirror) {
|
||||||
@ -298,7 +298,7 @@ export default defineComponent({
|
|||||||
return commitStatus[status].color;
|
return commitStatus[status].color;
|
||||||
},
|
},
|
||||||
|
|
||||||
reposFilterKeyControl(e) {
|
reposFilterKeyControl(e: KeyboardEvent) {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
document.querySelector<HTMLAnchorElement>('.repo-owner-name-list li.active a')?.click();
|
document.querySelector<HTMLAnchorElement>('.repo-owner-name-list li.active a')?.click();
|
||||||
|
@ -4,6 +4,22 @@ import {SvgIcon} from '../svg.ts';
|
|||||||
import {GET} from '../modules/fetch.ts';
|
import {GET} from '../modules/fetch.ts';
|
||||||
import {generateAriaId} from '../modules/fomantic/base.ts';
|
import {generateAriaId} from '../modules/fomantic/base.ts';
|
||||||
|
|
||||||
|
type Commit = {
|
||||||
|
id: string,
|
||||||
|
hovered: boolean,
|
||||||
|
selected: boolean,
|
||||||
|
summary: string,
|
||||||
|
committer_or_author_name: string,
|
||||||
|
time: string,
|
||||||
|
short_sha: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommitListResult = {
|
||||||
|
commits: Array<Commit>,
|
||||||
|
last_review_commit_sha: string,
|
||||||
|
locale: Record<string, string>,
|
||||||
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {SvgIcon},
|
components: {SvgIcon},
|
||||||
data: () => {
|
data: () => {
|
||||||
@ -16,9 +32,9 @@ export default defineComponent({
|
|||||||
locale: {
|
locale: {
|
||||||
filter_changes_by_commit: el.getAttribute('data-filter_changes_by_commit'),
|
filter_changes_by_commit: el.getAttribute('data-filter_changes_by_commit'),
|
||||||
} as Record<string, string>,
|
} as Record<string, string>,
|
||||||
commits: [],
|
commits: [] as Array<Commit>,
|
||||||
hoverActivated: false,
|
hoverActivated: false,
|
||||||
lastReviewCommitSha: null,
|
lastReviewCommitSha: '',
|
||||||
uniqueIdMenu: generateAriaId(),
|
uniqueIdMenu: generateAriaId(),
|
||||||
uniqueIdShowAll: generateAriaId(),
|
uniqueIdShowAll: generateAriaId(),
|
||||||
};
|
};
|
||||||
@ -71,7 +87,7 @@ export default defineComponent({
|
|||||||
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
||||||
const item = document.activeElement; // try to highlight the selected commits
|
const item = document.activeElement; // try to highlight the selected commits
|
||||||
const commitIdx = item?.matches('.item') ? item.getAttribute('data-commit-idx') : null;
|
const commitIdx = item?.matches('.item') ? item.getAttribute('data-commit-idx') : null;
|
||||||
if (commitIdx) this.highlight(this.commits[commitIdx]);
|
if (commitIdx) this.highlight(this.commits[Number(commitIdx)]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onKeyUp(event: KeyboardEvent) {
|
onKeyUp(event: KeyboardEvent) {
|
||||||
@ -87,7 +103,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
highlight(commit) {
|
highlight(commit: Commit) {
|
||||||
if (!this.hoverActivated) return;
|
if (!this.hoverActivated) return;
|
||||||
const indexSelected = this.commits.findIndex((x) => x.selected);
|
const indexSelected = this.commits.findIndex((x) => x.selected);
|
||||||
const indexCurrentElem = this.commits.findIndex((x) => x.id === commit.id);
|
const indexCurrentElem = this.commits.findIndex((x) => x.id === commit.id);
|
||||||
@ -125,10 +141,11 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/** Load the commits to show in this dropdown */
|
/** Load the commits to show in this dropdown */
|
||||||
async fetchCommits() {
|
async fetchCommits() {
|
||||||
const resp = await GET(`${this.issueLink}/commits/list`);
|
const resp = await GET(`${this.issueLink}/commits/list`);
|
||||||
const results = await resp.json();
|
const results = await resp.json() as CommitListResult;
|
||||||
this.commits.push(...results.commits.map((x) => {
|
this.commits.push(...results.commits.map((x) => {
|
||||||
x.hovered = false;
|
x.hovered = false;
|
||||||
return x;
|
return x;
|
||||||
@ -166,7 +183,7 @@ export default defineComponent({
|
|||||||
* the diff from beginning of PR up to the second clicked commit is
|
* the diff from beginning of PR up to the second clicked commit is
|
||||||
* opened
|
* opened
|
||||||
*/
|
*/
|
||||||
commitClickedShift(commit) {
|
commitClickedShift(commit: Commit) {
|
||||||
this.hoverActivated = !this.hoverActivated;
|
this.hoverActivated = !this.hoverActivated;
|
||||||
commit.selected = true;
|
commit.selected = true;
|
||||||
// Second click -> determine our range and open links accordingly
|
// Second click -> determine our range and open links accordingly
|
||||||
|
@ -18,14 +18,14 @@ function toggleFileList() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function diffTypeToString(pType: number) {
|
function diffTypeToString(pType: number) {
|
||||||
const diffTypes = {
|
const diffTypes: Record<string, string> = {
|
||||||
1: 'add',
|
'1': 'add',
|
||||||
2: 'modify',
|
'2': 'modify',
|
||||||
3: 'del',
|
'3': 'del',
|
||||||
4: 'rename',
|
'4': 'rename',
|
||||||
5: 'copy',
|
'5': 'copy',
|
||||||
};
|
};
|
||||||
return diffTypes[pType];
|
return diffTypes[String(pType)];
|
||||||
}
|
}
|
||||||
|
|
||||||
function diffStatsWidth(adds: number, dels: number) {
|
function diffStatsWidth(adds: number, dels: number) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import DiffFileTreeItem from './DiffFileTreeItem.vue';
|
import DiffFileTreeItem, {type Item} from './DiffFileTreeItem.vue';
|
||||||
import {loadMoreFiles} from '../features/repo-diff.ts';
|
import {loadMoreFiles} from '../features/repo-diff.ts';
|
||||||
import {toggleElem} from '../utils/dom.ts';
|
import {toggleElem} from '../utils/dom.ts';
|
||||||
import {diffTreeStore} from '../modules/stores.ts';
|
import {diffTreeStore} from '../modules/stores.ts';
|
||||||
@ -11,7 +11,7 @@ const LOCAL_STORAGE_KEY = 'diff_file_tree_visible';
|
|||||||
const store = diffTreeStore();
|
const store = diffTreeStore();
|
||||||
|
|
||||||
const fileTree = computed(() => {
|
const fileTree = computed(() => {
|
||||||
const result = [];
|
const result: Array<Item> = [];
|
||||||
for (const file of store.files) {
|
for (const file of store.files) {
|
||||||
// Split file into directories
|
// Split file into directories
|
||||||
const splits = file.Name.split('/');
|
const splits = file.Name.split('/');
|
||||||
@ -24,15 +24,10 @@ const fileTree = computed(() => {
|
|||||||
if (index === splits.length) {
|
if (index === splits.length) {
|
||||||
isFile = true;
|
isFile = true;
|
||||||
}
|
}
|
||||||
let newParent = {
|
let newParent: Item = {
|
||||||
name: split,
|
name: split,
|
||||||
children: [],
|
children: [],
|
||||||
isFile,
|
isFile,
|
||||||
} as {
|
|
||||||
name: string,
|
|
||||||
children: any[],
|
|
||||||
isFile: boolean,
|
|
||||||
file?: any,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isFile === true) {
|
if (isFile === true) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {SvgIcon} from '../svg.ts';
|
import {SvgIcon, type SvgName} from '../svg.ts';
|
||||||
import {diffTreeStore} from '../modules/stores.ts';
|
import {diffTreeStore} from '../modules/stores.ts';
|
||||||
import {ref} from 'vue';
|
import {ref} from 'vue';
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ type File = {
|
|||||||
IsSubmodule: boolean;
|
IsSubmodule: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Item = {
|
export type Item = {
|
||||||
name: string;
|
name: string;
|
||||||
isFile: boolean;
|
isFile: boolean;
|
||||||
file?: File;
|
file?: File;
|
||||||
@ -26,14 +26,14 @@ const store = diffTreeStore();
|
|||||||
const collapsed = ref(false);
|
const collapsed = ref(false);
|
||||||
|
|
||||||
function getIconForDiffType(pType: number) {
|
function getIconForDiffType(pType: number) {
|
||||||
const diffTypes = {
|
const diffTypes: Record<string, {name: SvgName, classes: Array<string>}> = {
|
||||||
1: {name: 'octicon-diff-added', classes: ['text', 'green']},
|
'1': {name: 'octicon-diff-added', classes: ['text', 'green']},
|
||||||
2: {name: 'octicon-diff-modified', classes: ['text', 'yellow']},
|
'2': {name: 'octicon-diff-modified', classes: ['text', 'yellow']},
|
||||||
3: {name: 'octicon-diff-removed', classes: ['text', 'red']},
|
'3': {name: 'octicon-diff-removed', classes: ['text', 'red']},
|
||||||
4: {name: 'octicon-diff-renamed', classes: ['text', 'teal']},
|
'4': {name: 'octicon-diff-renamed', classes: ['text', 'teal']},
|
||||||
5: {name: 'octicon-diff-renamed', classes: ['text', 'green']}, // there is no octicon for copied, so renamed should be ok
|
'5': {name: 'octicon-diff-renamed', classes: ['text', 'green']}, // there is no octicon for copied, so renamed should be ok
|
||||||
};
|
};
|
||||||
return diffTypes[pType];
|
return diffTypes[String(pType)];
|
||||||
}
|
}
|
||||||
|
|
||||||
function fileIcon(file: File) {
|
function fileIcon(file: File) {
|
||||||
|
@ -36,17 +36,17 @@ const forceMerge = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
watch(mergeStyle, (val) => {
|
watch(mergeStyle, (val) => {
|
||||||
mergeStyleDetail.value = mergeForm.value.mergeStyles.find((e) => e.name === val);
|
mergeStyleDetail.value = mergeForm.value.mergeStyles.find((e: any) => e.name === val);
|
||||||
for (const elem of document.querySelectorAll('[data-pull-merge-style]')) {
|
for (const elem of document.querySelectorAll('[data-pull-merge-style]')) {
|
||||||
toggleElem(elem, elem.getAttribute('data-pull-merge-style') === val);
|
toggleElem(elem, elem.getAttribute('data-pull-merge-style') === val);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
mergeStyleAllowedCount.value = mergeForm.value.mergeStyles.reduce((v, msd) => v + (msd.allowed ? 1 : 0), 0);
|
mergeStyleAllowedCount.value = mergeForm.value.mergeStyles.reduce((v: any, msd: any) => v + (msd.allowed ? 1 : 0), 0);
|
||||||
|
|
||||||
let mergeStyle = mergeForm.value.mergeStyles.find((e) => e.allowed && e.name === mergeForm.value.defaultMergeStyle)?.name;
|
let mergeStyle = mergeForm.value.mergeStyles.find((e: any) => e.allowed && e.name === mergeForm.value.defaultMergeStyle)?.name;
|
||||||
if (!mergeStyle) mergeStyle = mergeForm.value.mergeStyles.find((e) => e.allowed)?.name;
|
if (!mergeStyle) mergeStyle = mergeForm.value.mergeStyles.find((e: any) => e.allowed)?.name;
|
||||||
switchMergeStyle(mergeStyle, !mergeForm.value.canMergeNow);
|
switchMergeStyle(mergeStyle, !mergeForm.value.canMergeNow);
|
||||||
|
|
||||||
document.addEventListener('mouseup', hideMergeStyleMenu);
|
document.addEventListener('mouseup', hideMergeStyleMenu);
|
||||||
|
@ -6,6 +6,7 @@ import {createElementFromAttrs, toggleElem} from '../utils/dom.ts';
|
|||||||
import {formatDatetime} from '../utils/time.ts';
|
import {formatDatetime} from '../utils/time.ts';
|
||||||
import {renderAnsi} from '../render/ansi.ts';
|
import {renderAnsi} from '../render/ansi.ts';
|
||||||
import {POST, DELETE} from '../modules/fetch.ts';
|
import {POST, DELETE} from '../modules/fetch.ts';
|
||||||
|
import type {IntervalId} from '../types.ts';
|
||||||
|
|
||||||
// see "models/actions/status.go", if it needs to be used somewhere else, move it to a shared file like "types/actions.ts"
|
// see "models/actions/status.go", if it needs to be used somewhere else, move it to a shared file like "types/actions.ts"
|
||||||
type RunStatus = 'unknown' | 'waiting' | 'running' | 'success' | 'failure' | 'cancelled' | 'skipped' | 'blocked';
|
type RunStatus = 'unknown' | 'waiting' | 'running' | 'success' | 'failure' | 'cancelled' | 'skipped' | 'blocked';
|
||||||
@ -24,6 +25,20 @@ type LogLineCommand = {
|
|||||||
prefix: string,
|
prefix: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Job = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
status: RunStatus;
|
||||||
|
canRerun: boolean;
|
||||||
|
duration: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Step = {
|
||||||
|
summary: string,
|
||||||
|
duration: string,
|
||||||
|
status: RunStatus,
|
||||||
|
}
|
||||||
|
|
||||||
function parseLineCommand(line: LogLine): LogLineCommand | null {
|
function parseLineCommand(line: LogLine): LogLineCommand | null {
|
||||||
for (const prefix of LogLinePrefixesGroup) {
|
for (const prefix of LogLinePrefixesGroup) {
|
||||||
if (line.message.startsWith(prefix)) {
|
if (line.message.startsWith(prefix)) {
|
||||||
@ -77,7 +92,7 @@ export default defineComponent({
|
|||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
locale: {
|
locale: {
|
||||||
type: Object as PropType<Record<string, string>>,
|
type: Object as PropType<Record<string, any>>,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -86,10 +101,10 @@ export default defineComponent({
|
|||||||
const {autoScroll, expandRunning} = getLocaleStorageOptions();
|
const {autoScroll, expandRunning} = getLocaleStorageOptions();
|
||||||
return {
|
return {
|
||||||
// internal state
|
// internal state
|
||||||
loadingAbortController: null,
|
loadingAbortController: null as AbortController | null,
|
||||||
intervalID: null,
|
intervalID: null as IntervalId | null,
|
||||||
currentJobStepsStates: [],
|
currentJobStepsStates: [] as Array<Record<string, any>>,
|
||||||
artifacts: [],
|
artifacts: [] as Array<Record<string, any>>,
|
||||||
onHoverRerunIndex: -1,
|
onHoverRerunIndex: -1,
|
||||||
menuVisible: false,
|
menuVisible: false,
|
||||||
isFullScreen: false,
|
isFullScreen: false,
|
||||||
@ -122,7 +137,7 @@ export default defineComponent({
|
|||||||
// canRerun: false,
|
// canRerun: false,
|
||||||
// duration: '',
|
// duration: '',
|
||||||
// },
|
// },
|
||||||
],
|
] as Array<Job>,
|
||||||
commit: {
|
commit: {
|
||||||
localeCommit: '',
|
localeCommit: '',
|
||||||
localePushedBy: '',
|
localePushedBy: '',
|
||||||
@ -148,7 +163,7 @@ export default defineComponent({
|
|||||||
// duration: '',
|
// duration: '',
|
||||||
// status: '',
|
// status: '',
|
||||||
// }
|
// }
|
||||||
],
|
] as Array<Step>,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -194,7 +209,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
// get the job step logs container ('.job-step-logs')
|
// get the job step logs container ('.job-step-logs')
|
||||||
getJobStepLogsContainer(stepIndex: number): HTMLElement {
|
getJobStepLogsContainer(stepIndex: number): HTMLElement {
|
||||||
return this.$refs.logs[stepIndex];
|
return (this.$refs.logs as any)[stepIndex];
|
||||||
},
|
},
|
||||||
|
|
||||||
// get the active logs container element, either the `job-step-logs` or the `job-log-list` in the `job-log-group`
|
// get the active logs container element, either the `job-step-logs` or the `job-log-list` in the `job-log-group`
|
||||||
@ -205,7 +220,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
// begin a log group
|
// begin a log group
|
||||||
beginLogGroup(stepIndex: number, startTime: number, line: LogLine, cmd: LogLineCommand) {
|
beginLogGroup(stepIndex: number, startTime: number, line: LogLine, cmd: LogLineCommand) {
|
||||||
const el = this.$refs.logs[stepIndex];
|
const el = (this.$refs.logs as any)[stepIndex];
|
||||||
const elJobLogGroupSummary = createElementFromAttrs('summary', {class: 'job-log-group-summary'},
|
const elJobLogGroupSummary = createElementFromAttrs('summary', {class: 'job-log-group-summary'},
|
||||||
this.createLogLine(stepIndex, startTime, {
|
this.createLogLine(stepIndex, startTime, {
|
||||||
index: line.index,
|
index: line.index,
|
||||||
@ -223,7 +238,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
// end a log group
|
// end a log group
|
||||||
endLogGroup(stepIndex: number, startTime: number, line: LogLine, cmd: LogLineCommand) {
|
endLogGroup(stepIndex: number, startTime: number, line: LogLine, cmd: LogLineCommand) {
|
||||||
const el = this.$refs.logs[stepIndex];
|
const el = (this.$refs.logs as any)[stepIndex];
|
||||||
el._stepLogsActiveContainer = null;
|
el._stepLogsActiveContainer = null;
|
||||||
el.append(this.createLogLine(stepIndex, startTime, {
|
el.append(this.createLogLine(stepIndex, startTime, {
|
||||||
index: line.index,
|
index: line.index,
|
||||||
@ -393,7 +408,7 @@ export default defineComponent({
|
|||||||
if (this.menuVisible) this.menuVisible = false;
|
if (this.menuVisible) this.menuVisible = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleTimeDisplay(type: string) {
|
toggleTimeDisplay(type: 'seconds' | 'stamp') {
|
||||||
this.timeVisible[`log-time-${type}`] = !this.timeVisible[`log-time-${type}`];
|
this.timeVisible[`log-time-${type}`] = !this.timeVisible[`log-time-${type}`];
|
||||||
for (const el of (this.$refs.steps as HTMLElement).querySelectorAll(`.log-time-${type}`)) {
|
for (const el of (this.$refs.steps as HTMLElement).querySelectorAll(`.log-time-${type}`)) {
|
||||||
toggleElem(el, this.timeVisible[`log-time-${type}`]);
|
toggleElem(el, this.timeVisible[`log-time-${type}`]);
|
||||||
@ -422,9 +437,10 @@ export default defineComponent({
|
|||||||
const selectedLogStep = window.location.hash;
|
const selectedLogStep = window.location.hash;
|
||||||
if (!selectedLogStep) return;
|
if (!selectedLogStep) return;
|
||||||
const [_, step, _line] = selectedLogStep.split('-');
|
const [_, step, _line] = selectedLogStep.split('-');
|
||||||
if (!this.currentJobStepsStates[step]) return;
|
const stepNum = Number(step);
|
||||||
if (!this.currentJobStepsStates[step].expanded && this.currentJobStepsStates[step].cursor === null) {
|
if (!this.currentJobStepsStates[stepNum]) return;
|
||||||
this.currentJobStepsStates[step].expanded = true;
|
if (!this.currentJobStepsStates[stepNum].expanded && this.currentJobStepsStates[stepNum].cursor === null) {
|
||||||
|
this.currentJobStepsStates[stepNum].expanded = true;
|
||||||
// need to await for load job if the step log is loaded for the first time
|
// need to await for load job if the step log is loaded for the first time
|
||||||
// so logline can be selected by querySelector
|
// so logline can be selected by querySelector
|
||||||
await this.loadJob();
|
await this.loadJob();
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
// @ts-expect-error - module exports no types
|
||||||
import {VueBarGraph} from 'vue-bar-graph';
|
import {VueBarGraph} from 'vue-bar-graph';
|
||||||
import {computed, onMounted, ref} from 'vue';
|
import {computed, onMounted, ref} from 'vue';
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ export default defineComponent({
|
|||||||
// @ts-expect-error - el is unknown type
|
// @ts-expect-error - el is unknown type
|
||||||
return (el && el.length) ? el[0] : null;
|
return (el && el.length) ? el[0] : null;
|
||||||
},
|
},
|
||||||
keydown(e) {
|
keydown(e: KeyboardEvent) {
|
||||||
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ export default defineComponent({
|
|||||||
this.menuVisible = false;
|
this.menuVisible = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleTabSwitch(selectedTab) {
|
handleTabSwitch(selectedTab: SelectedTab) {
|
||||||
this.selectedTab = selectedTab;
|
this.selectedTab = selectedTab;
|
||||||
this.focusSearchField();
|
this.focusSearchField();
|
||||||
this.loadTabItems();
|
this.loadTabItems();
|
||||||
|
@ -80,10 +80,10 @@ export default defineComponent({
|
|||||||
sortedContributors: {} as Record<string, any>,
|
sortedContributors: {} as Record<string, any>,
|
||||||
type: 'commits',
|
type: 'commits',
|
||||||
contributorsStats: {} as Record<string, any>,
|
contributorsStats: {} as Record<string, any>,
|
||||||
xAxisStart: null,
|
xAxisStart: null as number | null,
|
||||||
xAxisEnd: null,
|
xAxisEnd: null as number | null,
|
||||||
xAxisMin: null,
|
xAxisMin: null as number | null,
|
||||||
xAxisMax: null,
|
xAxisMax: null as number | null,
|
||||||
}),
|
}),
|
||||||
mounted() {
|
mounted() {
|
||||||
this.fetchGraphData();
|
this.fetchGraphData();
|
||||||
@ -99,7 +99,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
sortContributors() {
|
sortContributors() {
|
||||||
const contributors = this.filterContributorWeeksByDateRange();
|
const contributors: Record<string, any> = this.filterContributorWeeksByDateRange();
|
||||||
const criteria = `total_${this.type}`;
|
const criteria = `total_${this.type}`;
|
||||||
this.sortedContributors = Object.values(contributors)
|
this.sortedContributors = Object.values(contributors)
|
||||||
.filter((contributor) => contributor[criteria] !== 0)
|
.filter((contributor) => contributor[criteria] !== 0)
|
||||||
@ -158,7 +158,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
filterContributorWeeksByDateRange() {
|
filterContributorWeeksByDateRange() {
|
||||||
const filteredData = {};
|
const filteredData: Record<string, any> = {};
|
||||||
const data = this.contributorsStats;
|
const data = this.contributorsStats;
|
||||||
for (const key of Object.keys(data)) {
|
for (const key of Object.keys(data)) {
|
||||||
const user = data[key];
|
const user = data[key];
|
||||||
@ -196,7 +196,7 @@ export default defineComponent({
|
|||||||
// Normally, chartjs handles this automatically, but it will resize the graph when you
|
// Normally, chartjs handles this automatically, but it will resize the graph when you
|
||||||
// zoom, pan etc. I think resizing the graph makes it harder to compare things visually.
|
// zoom, pan etc. I think resizing the graph makes it harder to compare things visually.
|
||||||
const maxValue = Math.max(
|
const maxValue = Math.max(
|
||||||
...this.totalStats.weeks.map((o) => o[this.type]),
|
...this.totalStats.weeks.map((o: Record<string, any>) => o[this.type]),
|
||||||
);
|
);
|
||||||
const [coefficient, exp] = maxValue.toExponential().split('e').map(Number);
|
const [coefficient, exp] = maxValue.toExponential().split('e').map(Number);
|
||||||
if (coefficient % 1 === 0) return maxValue;
|
if (coefficient % 1 === 0) return maxValue;
|
||||||
@ -208,7 +208,7 @@ export default defineComponent({
|
|||||||
// for contributors' graph. If I let chartjs do this for me, it will choose different
|
// for contributors' graph. If I let chartjs do this for me, it will choose different
|
||||||
// maxY value for each contributors' graph which again makes it harder to compare.
|
// maxY value for each contributors' graph which again makes it harder to compare.
|
||||||
const maxValue = Math.max(
|
const maxValue = Math.max(
|
||||||
...this.sortedContributors.map((c) => c.max_contribution_type),
|
...this.sortedContributors.map((c: Record<string, any>) => c.max_contribution_type),
|
||||||
);
|
);
|
||||||
const [coefficient, exp] = maxValue.toExponential().split('e').map(Number);
|
const [coefficient, exp] = maxValue.toExponential().split('e').map(Number);
|
||||||
if (coefficient % 1 === 0) return maxValue;
|
if (coefficient % 1 === 0) return maxValue;
|
||||||
@ -232,8 +232,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
updateOtherCharts({chart}: {chart: Chart}, reset: boolean = false) {
|
updateOtherCharts({chart}: {chart: Chart}, reset: boolean = false) {
|
||||||
const minVal = chart.options.scales.x.min;
|
const minVal = Number(chart.options.scales.x.min);
|
||||||
const maxVal = 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;
|
||||||
|
@ -35,7 +35,7 @@ onUnmounted(() => {
|
|||||||
document.querySelector('#scoped-access-submit').removeEventListener('click', onClickSubmit);
|
document.querySelector('#scoped-access-submit').removeEventListener('click', onClickSubmit);
|
||||||
});
|
});
|
||||||
|
|
||||||
function onClickSubmit(e) {
|
function onClickSubmit(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const warningEl = document.querySelector('#scoped-access-warning');
|
const warningEl = document.querySelector('#scoped-access-warning');
|
||||||
|
@ -90,7 +90,7 @@ export function initAdminCommon(): void {
|
|||||||
onOAuth2UseCustomURLChange(applyDefaultValues);
|
onOAuth2UseCustomURLChange(applyDefaultValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onOAuth2UseCustomURLChange(applyDefaultValues) {
|
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]')) {
|
||||||
|
@ -5,9 +5,13 @@ const {pageData} = window.config;
|
|||||||
|
|
||||||
async function initInputCitationValue(citationCopyApa: HTMLButtonElement, citationCopyBibtex: HTMLButtonElement) {
|
async function initInputCitationValue(citationCopyApa: HTMLButtonElement, citationCopyBibtex: HTMLButtonElement) {
|
||||||
const [{Cite, plugins}] = await Promise.all([
|
const [{Cite, plugins}] = await Promise.all([
|
||||||
|
// @ts-expect-error: module exports no types
|
||||||
import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'),
|
import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'),
|
||||||
|
// @ts-expect-error: module exports no types
|
||||||
import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'),
|
import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'),
|
||||||
|
// @ts-expect-error: module exports no types
|
||||||
import(/* webpackChunkName: "citation-js-bibtex" */'@citation-js/plugin-bibtex'),
|
import(/* webpackChunkName: "citation-js-bibtex" */'@citation-js/plugin-bibtex'),
|
||||||
|
// @ts-expect-error: module exports no types
|
||||||
import(/* webpackChunkName: "citation-js-csl" */'@citation-js/plugin-csl'),
|
import(/* webpackChunkName: "citation-js-csl" */'@citation-js/plugin-csl'),
|
||||||
]);
|
]);
|
||||||
const {citationFileContent} = pageData;
|
const {citationFileContent} = pageData;
|
||||||
|
@ -74,10 +74,10 @@ export function initGlobalDeleteButton(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onShowPanelClick(e) {
|
function onShowPanelClick(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
|
||||||
const el = e.currentTarget;
|
const el = e.currentTarget as HTMLElement;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const sel = el.getAttribute('data-panel');
|
const sel = el.getAttribute('data-panel');
|
||||||
if (el.classList.contains('toggle')) {
|
if (el.classList.contains('toggle')) {
|
||||||
@ -87,9 +87,9 @@ function onShowPanelClick(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onHidePanelClick(e) {
|
function onHidePanelClick(e: MouseEvent) {
|
||||||
// a `.hide-panel` element can hide a panel, by `data-panel="selector"` or `data-panel-closest="selector"`
|
// a `.hide-panel` element can hide a panel, by `data-panel="selector"` or `data-panel-closest="selector"`
|
||||||
const el = e.currentTarget;
|
const el = e.currentTarget as HTMLElement;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let sel = el.getAttribute('data-panel');
|
let sel = el.getAttribute('data-panel');
|
||||||
if (sel) {
|
if (sel) {
|
||||||
@ -98,13 +98,13 @@ function onHidePanelClick(e) {
|
|||||||
}
|
}
|
||||||
sel = el.getAttribute('data-panel-closest');
|
sel = el.getAttribute('data-panel-closest');
|
||||||
if (sel) {
|
if (sel) {
|
||||||
hideElem(el.parentNode.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
|
||||||
}
|
}
|
||||||
|
|
||||||
function onShowModalClick(e) {
|
function onShowModalClick(e: MouseEvent) {
|
||||||
// A ".show-modal" button will show a modal dialog defined by its "data-modal" attribute.
|
// A ".show-modal" button will show a modal dialog defined by its "data-modal" attribute.
|
||||||
// Each "data-modal-{target}" attribute will be filled to target element's value or text-content.
|
// Each "data-modal-{target}" attribute will be filled to target element's value or text-content.
|
||||||
// * First, try to query '#target'
|
// * First, try to query '#target'
|
||||||
@ -112,7 +112,7 @@ function onShowModalClick(e) {
|
|||||||
// * Then, try to query '.target'
|
// * Then, try to query '.target'
|
||||||
// * Then, try to query 'target' as HTML tag
|
// * Then, try to query 'target' as HTML tag
|
||||||
// If there is a ".{attr}" part like "data-modal-form.action", then the form's "action" attribute will be set.
|
// If there is a ".{attr}" part like "data-modal-form.action", then the form's "action" attribute will be set.
|
||||||
const el = e.currentTarget;
|
const el = e.currentTarget as HTMLElement;
|
||||||
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);
|
||||||
@ -137,9 +137,9 @@ function onShowModalClick(e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (attrTargetAttr) {
|
if (attrTargetAttr) {
|
||||||
attrTarget[camelize(attrTargetAttr)] = attrib.value;
|
(attrTarget as any)[camelize(attrTargetAttr)] = attrib.value;
|
||||||
} else if (attrTarget.matches('input, textarea')) {
|
} else if (attrTarget.matches('input, textarea')) {
|
||||||
attrTarget.value = attrib.value; // FIXME: add more supports like checkbox
|
(attrTarget as HTMLInputElement | HTMLTextAreaElement).value = attrib.value; // FIXME: add more supports like checkbox
|
||||||
} else {
|
} else {
|
||||||
attrTarget.textContent = attrib.value; // FIXME: it should be more strict here, only handle div/span/p
|
attrTarget.textContent = attrib.value; // FIXME: it should be more strict here, only handle div/span/p
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,10 @@ async function formFetchAction(formEl: HTMLFormElement, e: SubmitEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let reqUrl = formActionUrl;
|
let reqUrl = formActionUrl;
|
||||||
const reqOpt = {method: formMethod.toUpperCase(), body: null};
|
const reqOpt = {
|
||||||
|
method: formMethod.toUpperCase(),
|
||||||
|
body: null as FormData | null,
|
||||||
|
};
|
||||||
if (formMethod.toLowerCase() === 'get') {
|
if (formMethod.toLowerCase() === 'get') {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
for (const [key, value] of formData) {
|
for (const [key, value] of formData) {
|
||||||
|
@ -17,13 +17,13 @@ export function initGlobalEnterQuickSubmit() {
|
|||||||
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.matches('textarea')) {
|
||||||
if (handleGlobalEnterQuickSubmit(e.target)) {
|
if (handleGlobalEnterQuickSubmit(e.target as HTMLElement)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
} else if (e.target.matches('input') && !e.target.closest('form')) {
|
} else if (e.target.matches('input') && !e.target.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)) {
|
if (handleGlobalEnterQuickSubmit(e.target as HTMLElement)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,10 +29,10 @@ let elementIdCounter = 0;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* validate if the given textarea is non-empty.
|
* validate if the given textarea is non-empty.
|
||||||
* @param {HTMLElement} textarea - The textarea element to be validated.
|
* @param {HTMLTextAreaElement} textarea - The textarea element to be validated.
|
||||||
* @returns {boolean} returns true if validation succeeded.
|
* @returns {boolean} returns true if validation succeeded.
|
||||||
*/
|
*/
|
||||||
export function validateTextareaNonEmpty(textarea) {
|
export function validateTextareaNonEmpty(textarea: HTMLTextAreaElement) {
|
||||||
// When using EasyMDE, the original edit area HTML element is hidden, breaking HTML5 input validation.
|
// When using EasyMDE, the original edit area HTML element is hidden, breaking HTML5 input validation.
|
||||||
// The workaround (https://github.com/sparksuite/simplemde-markdown-editor/issues/324) doesn't work with contenteditable, so we just show an alert.
|
// The workaround (https://github.com/sparksuite/simplemde-markdown-editor/issues/324) doesn't work with contenteditable, so we just show an alert.
|
||||||
if (!textarea.value) {
|
if (!textarea.value) {
|
||||||
@ -49,16 +49,25 @@ export function validateTextareaNonEmpty(textarea) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Heights = {
|
||||||
|
minHeight?: string,
|
||||||
|
height?: string,
|
||||||
|
maxHeight?: string,
|
||||||
|
};
|
||||||
|
|
||||||
type ComboMarkdownEditorOptions = {
|
type ComboMarkdownEditorOptions = {
|
||||||
editorHeights?: {minHeight?: string, height?: string, maxHeight?: string},
|
editorHeights?: Heights,
|
||||||
easyMDEOptions?: EasyMDE.Options,
|
easyMDEOptions?: EasyMDE.Options,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ComboMarkdownEditorTextarea = HTMLTextAreaElement & {_giteaComboMarkdownEditor: any};
|
||||||
|
type ComboMarkdownEditorContainer = HTMLElement & {_giteaComboMarkdownEditor?: any};
|
||||||
|
|
||||||
export class ComboMarkdownEditor {
|
export class ComboMarkdownEditor {
|
||||||
static EventEditorContentChanged = EventEditorContentChanged;
|
static EventEditorContentChanged = EventEditorContentChanged;
|
||||||
static EventUploadStateChanged = EventUploadStateChanged;
|
static EventUploadStateChanged = EventUploadStateChanged;
|
||||||
|
|
||||||
public container : HTMLElement;
|
public container: HTMLElement;
|
||||||
|
|
||||||
options: ComboMarkdownEditorOptions;
|
options: ComboMarkdownEditorOptions;
|
||||||
|
|
||||||
@ -70,7 +79,7 @@ export class ComboMarkdownEditor {
|
|||||||
easyMDEToolbarActions: any;
|
easyMDEToolbarActions: any;
|
||||||
easyMDEToolbarDefault: any;
|
easyMDEToolbarDefault: any;
|
||||||
|
|
||||||
textarea: HTMLTextAreaElement & {_giteaComboMarkdownEditor: any};
|
textarea: ComboMarkdownEditorTextarea;
|
||||||
textareaMarkdownToolbar: HTMLElement;
|
textareaMarkdownToolbar: HTMLElement;
|
||||||
textareaAutosize: any;
|
textareaAutosize: any;
|
||||||
|
|
||||||
@ -81,7 +90,7 @@ export class ComboMarkdownEditor {
|
|||||||
previewUrl: string;
|
previewUrl: string;
|
||||||
previewContext: string;
|
previewContext: string;
|
||||||
|
|
||||||
constructor(container, options:ComboMarkdownEditorOptions = {}) {
|
constructor(container: ComboMarkdownEditorContainer, options:ComboMarkdownEditorOptions = {}) {
|
||||||
if (container._giteaComboMarkdownEditor) throw new Error('ComboMarkdownEditor already initialized');
|
if (container._giteaComboMarkdownEditor) throw new Error('ComboMarkdownEditor already initialized');
|
||||||
container._giteaComboMarkdownEditor = this;
|
container._giteaComboMarkdownEditor = this;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
@ -98,7 +107,7 @@ export class ComboMarkdownEditor {
|
|||||||
await this.switchToUserPreference();
|
await this.switchToUserPreference();
|
||||||
}
|
}
|
||||||
|
|
||||||
applyEditorHeights(el, heights) {
|
applyEditorHeights(el: HTMLElement, heights: Heights) {
|
||||||
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;
|
||||||
@ -283,7 +292,7 @@ export class ComboMarkdownEditor {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
parseEasyMDEToolbar(easyMde: typeof EasyMDE, actions) {
|
parseEasyMDEToolbar(easyMde: typeof EasyMDE, actions: any) {
|
||||||
this.easyMDEToolbarActions = this.easyMDEToolbarActions || easyMDEToolbarActions(easyMde, this);
|
this.easyMDEToolbarActions = this.easyMDEToolbarActions || easyMDEToolbarActions(easyMde, this);
|
||||||
const processed = [];
|
const processed = [];
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
@ -332,21 +341,21 @@ export class ComboMarkdownEditor {
|
|||||||
this.easyMDE = new EasyMDE(easyMDEOpt);
|
this.easyMDE = new EasyMDE(easyMDEOpt);
|
||||||
this.easyMDE.codemirror.on('change', () => triggerEditorContentChanged(this.container));
|
this.easyMDE.codemirror.on('change', () => triggerEditorContentChanged(this.container));
|
||||||
this.easyMDE.codemirror.setOption('extraKeys', {
|
this.easyMDE.codemirror.setOption('extraKeys', {
|
||||||
'Cmd-Enter': (cm) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
|
'Cmd-Enter': (cm: any) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
|
||||||
'Ctrl-Enter': (cm) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
|
'Ctrl-Enter': (cm: any) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
|
||||||
Enter: (cm) => {
|
Enter: (cm: any) => {
|
||||||
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
|
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
|
||||||
if (!tributeContainer || tributeContainer.style.display === 'none') {
|
if (!tributeContainer || tributeContainer.style.display === 'none') {
|
||||||
cm.execCommand('newlineAndIndent');
|
cm.execCommand('newlineAndIndent');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Up: (cm) => {
|
Up: (cm: any) => {
|
||||||
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
|
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
|
||||||
if (!tributeContainer || tributeContainer.style.display === 'none') {
|
if (!tributeContainer || tributeContainer.style.display === 'none') {
|
||||||
return cm.execCommand('goLineUp');
|
return cm.execCommand('goLineUp');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Down: (cm) => {
|
Down: (cm: any) => {
|
||||||
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
|
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
|
||||||
if (!tributeContainer || tributeContainer.style.display === 'none') {
|
if (!tributeContainer || tributeContainer.style.display === 'none') {
|
||||||
return cm.execCommand('goLineDown');
|
return cm.execCommand('goLineDown');
|
||||||
@ -354,14 +363,14 @@ 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(), {mentions: true, emoji: true});
|
await attachTribute(this.easyMDE.codemirror.getInputField());
|
||||||
if (this.dropzone) {
|
if (this.dropzone) {
|
||||||
initEasyMDEPaste(this.easyMDE, this.dropzone);
|
initEasyMDEPaste(this.easyMDE, this.dropzone);
|
||||||
}
|
}
|
||||||
hideElem(this.textareaMarkdownToolbar);
|
hideElem(this.textareaMarkdownToolbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
value(v = undefined) {
|
value(v: any = undefined) {
|
||||||
if (v === undefined) {
|
if (v === undefined) {
|
||||||
if (this.easyMDE) {
|
if (this.easyMDE) {
|
||||||
return this.easyMDE.value();
|
return this.easyMDE.value();
|
||||||
@ -402,7 +411,7 @@ export class ComboMarkdownEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getComboMarkdownEditor(el) {
|
export function getComboMarkdownEditor(el: any) {
|
||||||
if (!el) return null;
|
if (!el) return null;
|
||||||
if (el.length) el = el[0];
|
if (el.length) el = el[0];
|
||||||
return el._giteaComboMarkdownEditor;
|
return el._giteaComboMarkdownEditor;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
export const EventEditorContentChanged = 'ce-editor-content-changed';
|
export const EventEditorContentChanged = 'ce-editor-content-changed';
|
||||||
|
|
||||||
export function triggerEditorContentChanged(target) {
|
export function triggerEditorContentChanged(target: HTMLElement) {
|
||||||
target.dispatchEvent(new CustomEvent(EventEditorContentChanged, {bubbles: true}));
|
target.dispatchEvent(new CustomEvent(EventEditorContentChanged, {bubbles: true}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function textareaInsertText(textarea, value) {
|
export function textareaInsertText(textarea: HTMLTextAreaElement, value: string) {
|
||||||
const startPos = textarea.selectionStart;
|
const startPos = textarea.selectionStart;
|
||||||
const endPos = textarea.selectionEnd;
|
const endPos = textarea.selectionEnd;
|
||||||
textarea.value = textarea.value.substring(0, startPos) + value + textarea.value.substring(endPos);
|
textarea.value = textarea.value.substring(0, startPos) + value + textarea.value.substring(endPos);
|
||||||
@ -20,7 +20,7 @@ type TextareaValueSelection = {
|
|||||||
selEnd: number;
|
selEnd: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleIndentSelection(textarea: HTMLTextAreaElement, e) {
|
function handleIndentSelection(textarea: HTMLTextAreaElement, e: KeyboardEvent) {
|
||||||
const selStart = textarea.selectionStart;
|
const selStart = textarea.selectionStart;
|
||||||
const selEnd = textarea.selectionEnd;
|
const selEnd = textarea.selectionEnd;
|
||||||
if (selEnd === selStart) return; // do not process when no selection
|
if (selEnd === selStart) return; // do not process when no selection
|
||||||
@ -188,7 +188,7 @@ function isTextExpanderShown(textarea: HTMLElement): boolean {
|
|||||||
return Boolean(textarea.closest('text-expander')?.querySelector('.suggestions'));
|
return Boolean(textarea.closest('text-expander')?.querySelector('.suggestions'));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initTextareaMarkdown(textarea) {
|
export function initTextareaMarkdown(textarea: HTMLTextAreaElement) {
|
||||||
textarea.addEventListener('keydown', (e) => {
|
textarea.addEventListener('keydown', (e) => {
|
||||||
if (isTextExpanderShown(textarea)) return;
|
if (isTextExpanderShown(textarea)) return;
|
||||||
if (e.key === 'Tab' && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
if (e.key === 'Tab' && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
||||||
|
@ -8,43 +8,46 @@ import {
|
|||||||
generateMarkdownLinkForAttachment,
|
generateMarkdownLinkForAttachment,
|
||||||
} from '../dropzone.ts';
|
} from '../dropzone.ts';
|
||||||
import type CodeMirror from 'codemirror';
|
import type CodeMirror from 'codemirror';
|
||||||
|
import type EasyMDE from 'easymde';
|
||||||
|
import type {DropzoneFile} from 'dropzone';
|
||||||
|
|
||||||
let uploadIdCounter = 0;
|
let uploadIdCounter = 0;
|
||||||
|
|
||||||
export const EventUploadStateChanged = 'ce-upload-state-changed';
|
export const EventUploadStateChanged = 'ce-upload-state-changed';
|
||||||
|
|
||||||
export function triggerUploadStateChanged(target) {
|
export function triggerUploadStateChanged(target: HTMLElement) {
|
||||||
target.dispatchEvent(new CustomEvent(EventUploadStateChanged, {bubbles: true}));
|
target.dispatchEvent(new CustomEvent(EventUploadStateChanged, {bubbles: true}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadFile(dropzoneEl, file) {
|
function uploadFile(dropzoneEl: HTMLElement, file: File) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const curUploadId = uploadIdCounter++;
|
const curUploadId = uploadIdCounter++;
|
||||||
file._giteaUploadId = curUploadId;
|
(file as any)._giteaUploadId = curUploadId;
|
||||||
const dropzoneInst = dropzoneEl.dropzone;
|
const dropzoneInst = dropzoneEl.dropzone;
|
||||||
const onUploadDone = ({file}) => {
|
const onUploadDone = ({file}: {file: any}) => {
|
||||||
if (file._giteaUploadId === curUploadId) {
|
if (file._giteaUploadId === curUploadId) {
|
||||||
dropzoneInst.off(DropzoneCustomEventUploadDone, onUploadDone);
|
dropzoneInst.off(DropzoneCustomEventUploadDone, onUploadDone);
|
||||||
resolve(file);
|
resolve(file);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
dropzoneInst.on(DropzoneCustomEventUploadDone, onUploadDone);
|
dropzoneInst.on(DropzoneCustomEventUploadDone, onUploadDone);
|
||||||
dropzoneInst.handleFiles([file]);
|
// FIXME: this is not entirely correct because `file` does not satisfy DropzoneFile (we have abused the Dropzone for long time)
|
||||||
|
dropzoneInst.addFile(file as DropzoneFile);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextareaEditor {
|
class TextareaEditor {
|
||||||
editor : HTMLTextAreaElement;
|
editor: HTMLTextAreaElement;
|
||||||
|
|
||||||
constructor(editor) {
|
constructor(editor: HTMLTextAreaElement) {
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
insertPlaceholder(value) {
|
insertPlaceholder(value: string) {
|
||||||
textareaInsertText(this.editor, value);
|
textareaInsertText(this.editor, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
replacePlaceholder(oldVal, newVal) {
|
replacePlaceholder(oldVal: string, newVal: string) {
|
||||||
const editor = this.editor;
|
const editor = this.editor;
|
||||||
const startPos = editor.selectionStart;
|
const startPos = editor.selectionStart;
|
||||||
const endPos = editor.selectionEnd;
|
const endPos = editor.selectionEnd;
|
||||||
@ -65,11 +68,11 @@ class TextareaEditor {
|
|||||||
class CodeMirrorEditor {
|
class CodeMirrorEditor {
|
||||||
editor: CodeMirror.EditorFromTextArea;
|
editor: CodeMirror.EditorFromTextArea;
|
||||||
|
|
||||||
constructor(editor) {
|
constructor(editor: CodeMirror.EditorFromTextArea) {
|
||||||
this.editor = editor;
|
this.editor = editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
insertPlaceholder(value) {
|
insertPlaceholder(value: string) {
|
||||||
const editor = this.editor;
|
const editor = this.editor;
|
||||||
const startPoint = editor.getCursor('start');
|
const startPoint = editor.getCursor('start');
|
||||||
const endPoint = editor.getCursor('end');
|
const endPoint = editor.getCursor('end');
|
||||||
@ -80,7 +83,7 @@ class CodeMirrorEditor {
|
|||||||
triggerEditorContentChanged(editor.getTextArea());
|
triggerEditorContentChanged(editor.getTextArea());
|
||||||
}
|
}
|
||||||
|
|
||||||
replacePlaceholder(oldVal, newVal) {
|
replacePlaceholder(oldVal: string, newVal: string) {
|
||||||
const editor = this.editor;
|
const editor = this.editor;
|
||||||
const endPoint = editor.getCursor('end');
|
const endPoint = editor.getCursor('end');
|
||||||
if (editor.getSelection() === oldVal) {
|
if (editor.getSelection() === oldVal) {
|
||||||
@ -96,7 +99,7 @@ class CodeMirrorEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUploadFiles(editor, dropzoneEl, files, e) {
|
async function handleUploadFiles(editor: CodeMirrorEditor | TextareaEditor, dropzoneEl: HTMLElement, files: Array<File> | FileList, e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const name = file.name.slice(0, file.name.lastIndexOf('.'));
|
const name = file.name.slice(0, file.name.lastIndexOf('.'));
|
||||||
@ -109,13 +112,13 @@ async function handleUploadFiles(editor, dropzoneEl, files, e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeAttachmentLinksFromMarkdown(text, fileUuid) {
|
export function removeAttachmentLinksFromMarkdown(text: string, fileUuid: string) {
|
||||||
text = text.replace(new RegExp(`!?\\[([^\\]]+)\\]\\(/?attachments/${fileUuid}\\)`, 'g'), '');
|
text = text.replace(new RegExp(`!?\\[([^\\]]+)\\]\\(/?attachments/${fileUuid}\\)`, 'g'), '');
|
||||||
text = text.replace(new RegExp(`<img[^>]+src="/?attachments/${fileUuid}"[^>]*>`, 'g'), '');
|
text = text.replace(new RegExp(`<img[^>]+src="/?attachments/${fileUuid}"[^>]*>`, 'g'), '');
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClipboardText(textarea, e, {text, isShiftDown}) {
|
function handleClipboardText(textarea: HTMLTextAreaElement, e: ClipboardEvent, text: string, isShiftDown: boolean) {
|
||||||
// pasting with "shift" means "paste as original content" in most applications
|
// pasting with "shift" means "paste as original content" in most applications
|
||||||
if (isShiftDown) return; // let the browser handle it
|
if (isShiftDown) return; // let the browser handle it
|
||||||
|
|
||||||
@ -131,7 +134,7 @@ function handleClipboardText(textarea, e, {text, isShiftDown}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// extract text and images from "paste" event
|
// extract text and images from "paste" event
|
||||||
function getPastedContent(e) {
|
function getPastedContent(e: ClipboardEvent) {
|
||||||
const images = [];
|
const images = [];
|
||||||
for (const item of e.clipboardData?.items ?? []) {
|
for (const item of e.clipboardData?.items ?? []) {
|
||||||
if (item.type?.startsWith('image/')) {
|
if (item.type?.startsWith('image/')) {
|
||||||
@ -142,8 +145,8 @@ function getPastedContent(e) {
|
|||||||
return {text, images};
|
return {text, images};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initEasyMDEPaste(easyMDE, dropzoneEl) {
|
export function initEasyMDEPaste(easyMDE: EasyMDE, dropzoneEl: HTMLElement) {
|
||||||
const editor = new CodeMirrorEditor(easyMDE.codemirror);
|
const editor = new CodeMirrorEditor(easyMDE.codemirror as any);
|
||||||
easyMDE.codemirror.on('paste', (_, e) => {
|
easyMDE.codemirror.on('paste', (_, e) => {
|
||||||
const {images} = getPastedContent(e);
|
const {images} = getPastedContent(e);
|
||||||
if (!images.length) return;
|
if (!images.length) return;
|
||||||
@ -160,28 +163,28 @@ export function initEasyMDEPaste(easyMDE, dropzoneEl) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initTextareaEvents(textarea, dropzoneEl) {
|
export function initTextareaEvents(textarea: HTMLTextAreaElement, dropzoneEl: HTMLElement) {
|
||||||
let isShiftDown = false;
|
let isShiftDown = false;
|
||||||
textarea.addEventListener('keydown', (e) => {
|
textarea.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||||
if (e.shiftKey) isShiftDown = true;
|
if (e.shiftKey) isShiftDown = true;
|
||||||
});
|
});
|
||||||
textarea.addEventListener('keyup', (e) => {
|
textarea.addEventListener('keyup', (e: KeyboardEvent) => {
|
||||||
if (!e.shiftKey) isShiftDown = false;
|
if (!e.shiftKey) isShiftDown = false;
|
||||||
});
|
});
|
||||||
textarea.addEventListener('paste', (e) => {
|
textarea.addEventListener('paste', (e: ClipboardEvent) => {
|
||||||
const {images, text} = getPastedContent(e);
|
const {images, text} = getPastedContent(e);
|
||||||
if (images.length && dropzoneEl) {
|
if (images.length && dropzoneEl) {
|
||||||
handleUploadFiles(new TextareaEditor(textarea), dropzoneEl, images, e);
|
handleUploadFiles(new TextareaEditor(textarea), dropzoneEl, images, e);
|
||||||
} else if (text) {
|
} else if (text) {
|
||||||
handleClipboardText(textarea, e, {text, isShiftDown});
|
handleClipboardText(textarea, e, text, isShiftDown);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
textarea.addEventListener('drop', (e) => {
|
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);
|
||||||
});
|
});
|
||||||
dropzoneEl?.dropzone.on(DropzoneCustomEventRemovedFile, ({fileUuid}) => {
|
dropzoneEl?.dropzone.on(DropzoneCustomEventRemovedFile, ({fileUuid}: {fileUuid: string}) => {
|
||||||
const newText = removeAttachmentLinksFromMarkdown(textarea.value, fileUuid);
|
const newText = removeAttachmentLinksFromMarkdown(textarea.value, fileUuid);
|
||||||
if (textarea.value !== newText) textarea.value = newText;
|
if (textarea.value !== newText) textarea.value = newText;
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {querySingleVisibleElem} from '../../utils/dom.ts';
|
import {querySingleVisibleElem} from '../../utils/dom.ts';
|
||||||
|
|
||||||
export function handleGlobalEnterQuickSubmit(target) {
|
export function handleGlobalEnterQuickSubmit(target: HTMLElement) {
|
||||||
let form = target.closest('form');
|
let form = target.closest('form');
|
||||||
if (form) {
|
if (form) {
|
||||||
if (!form.checkValidity()) {
|
if (!form.checkValidity()) {
|
||||||
|
@ -14,7 +14,7 @@ export function initCompSearchUserBox() {
|
|||||||
minCharacters: 2,
|
minCharacters: 2,
|
||||||
apiSettings: {
|
apiSettings: {
|
||||||
url: `${appSubUrl}/user/search_candidates?q={query}`,
|
url: `${appSubUrl}/user/search_candidates?q={query}`,
|
||||||
onResponse(response) {
|
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();
|
||||||
|
@ -5,6 +5,7 @@ import {parseIssueHref, parseRepoOwnerPathInfo} from '../../utils.ts';
|
|||||||
import {createElementFromAttrs, createElementFromHTML} from '../../utils/dom.ts';
|
import {createElementFromAttrs, createElementFromHTML} from '../../utils/dom.ts';
|
||||||
import {getIssueColor, getIssueIcon} from '../issue.ts';
|
import {getIssueColor, getIssueIcon} from '../issue.ts';
|
||||||
import {debounce} from 'perfect-debounce';
|
import {debounce} from 'perfect-debounce';
|
||||||
|
import type TextExpanderElement from '@github/text-expander-element';
|
||||||
|
|
||||||
const debouncedSuggestIssues = debounce((key: string, text: string) => new Promise<{matched:boolean; fragment?: HTMLElement}>(async (resolve) => {
|
const debouncedSuggestIssues = debounce((key: string, text: string) => new Promise<{matched:boolean; fragment?: HTMLElement}>(async (resolve) => {
|
||||||
const issuePathInfo = parseIssueHref(window.location.href);
|
const issuePathInfo = parseIssueHref(window.location.href);
|
||||||
@ -32,8 +33,8 @@ const debouncedSuggestIssues = debounce((key: string, text: string) => new Promi
|
|||||||
resolve({matched: true, fragment: ul});
|
resolve({matched: true, fragment: ul});
|
||||||
}), 100);
|
}), 100);
|
||||||
|
|
||||||
export function initTextExpander(expander) {
|
export function initTextExpander(expander: TextExpanderElement) {
|
||||||
expander?.addEventListener('text-expander-change', ({detail: {key, provide, text}}) => {
|
expander?.addEventListener('text-expander-change', ({detail: {key, provide, text}}: Record<string, any>) => {
|
||||||
if (key === ':') {
|
if (key === ':') {
|
||||||
const matches = matchEmoji(text);
|
const matches = matchEmoji(text);
|
||||||
if (!matches.length) return provide({matched: false});
|
if (!matches.length) return provide({matched: false});
|
||||||
@ -84,7 +85,7 @@ export function initTextExpander(expander) {
|
|||||||
provide(debouncedSuggestIssues(key, text));
|
provide(debouncedSuggestIssues(key, text));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
expander?.addEventListener('text-expander-value', ({detail}) => {
|
expander?.addEventListener('text-expander-value', ({detail}: Record<string, any>) => {
|
||||||
if (detail?.item) {
|
if (detail?.item) {
|
||||||
// add a space after @mentions and #issue as it's likely the user wants one
|
// add a space after @mentions and #issue as it's likely the user wants one
|
||||||
const suffix = ['@', '#'].includes(detail.key) ? ' ' : '';
|
const suffix = ['@', '#'].includes(detail.key) ? ' ' : '';
|
||||||
|
@ -4,11 +4,11 @@ import {parseIssueHref} from '../utils.ts';
|
|||||||
import {createTippy} from '../modules/tippy.ts';
|
import {createTippy} from '../modules/tippy.ts';
|
||||||
|
|
||||||
export function initContextPopups() {
|
export function initContextPopups() {
|
||||||
const refIssues = document.querySelectorAll('.ref-issue');
|
const refIssues = document.querySelectorAll<HTMLElement>('.ref-issue');
|
||||||
attachRefIssueContextPopup(refIssues);
|
attachRefIssueContextPopup(refIssues);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function attachRefIssueContextPopup(refIssues) {
|
export function attachRefIssueContextPopup(refIssues: NodeListOf<HTMLElement>) {
|
||||||
for (const refIssue of refIssues) {
|
for (const refIssue of refIssues) {
|
||||||
if (refIssue.classList.contains('ref-external-issue')) continue;
|
if (refIssue.classList.contains('ref-external-issue')) continue;
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ export function initCopyContent() {
|
|||||||
showTemporaryTooltip(btn, i18n.copy_success);
|
showTemporaryTooltip(btn, i18n.copy_success);
|
||||||
} else {
|
} else {
|
||||||
if (isRasterImage) {
|
if (isRasterImage) {
|
||||||
const success = await clippie(await convertImage(content, 'image/png'));
|
const success = await clippie(await convertImage(content as Blob, 'image/png'));
|
||||||
showTemporaryTooltip(btn, success ? i18n.copy_success : i18n.copy_error);
|
showTemporaryTooltip(btn, success ? i18n.copy_success : i18n.copy_error);
|
||||||
} else {
|
} else {
|
||||||
showTemporaryTooltip(btn, i18n.copy_error);
|
showTemporaryTooltip(btn, i18n.copy_error);
|
||||||
|
@ -6,16 +6,18 @@ import {GET, POST} from '../modules/fetch.ts';
|
|||||||
import {showErrorToast} from '../modules/toast.ts';
|
import {showErrorToast} from '../modules/toast.ts';
|
||||||
import {createElementFromHTML, createElementFromAttrs} from '../utils/dom.ts';
|
import {createElementFromHTML, createElementFromAttrs} from '../utils/dom.ts';
|
||||||
import {isImageFile, isVideoFile} from '../utils.ts';
|
import {isImageFile, isVideoFile} from '../utils.ts';
|
||||||
import type {DropzoneFile} from 'dropzone/index.js';
|
import type {DropzoneFile, DropzoneOptions} from 'dropzone/index.js';
|
||||||
|
|
||||||
const {csrfToken, i18n} = window.config;
|
const {csrfToken, i18n} = window.config;
|
||||||
|
|
||||||
|
type CustomDropzoneFile = DropzoneFile & {uuid: string};
|
||||||
|
|
||||||
// dropzone has its owner event dispatcher (emitter)
|
// dropzone has its owner event dispatcher (emitter)
|
||||||
export const DropzoneCustomEventReloadFiles = 'dropzone-custom-reload-files';
|
export const DropzoneCustomEventReloadFiles = 'dropzone-custom-reload-files';
|
||||||
export const DropzoneCustomEventRemovedFile = 'dropzone-custom-removed-file';
|
export const DropzoneCustomEventRemovedFile = 'dropzone-custom-removed-file';
|
||||||
export const DropzoneCustomEventUploadDone = 'dropzone-custom-upload-done';
|
export const DropzoneCustomEventUploadDone = 'dropzone-custom-upload-done';
|
||||||
|
|
||||||
async function createDropzone(el, opts) {
|
async function createDropzone(el: HTMLElement, opts: DropzoneOptions) {
|
||||||
const [{default: Dropzone}] = await Promise.all([
|
const [{default: Dropzone}] = await Promise.all([
|
||||||
import(/* webpackChunkName: "dropzone" */'dropzone'),
|
import(/* webpackChunkName: "dropzone" */'dropzone'),
|
||||||
import(/* webpackChunkName: "dropzone" */'dropzone/dist/dropzone.css'),
|
import(/* webpackChunkName: "dropzone" */'dropzone/dist/dropzone.css'),
|
||||||
@ -23,7 +25,7 @@ async function createDropzone(el, opts) {
|
|||||||
return new Dropzone(el, opts);
|
return new Dropzone(el, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateMarkdownLinkForAttachment(file, {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)) {
|
||||||
fileMarkdown = `!${fileMarkdown}`;
|
fileMarkdown = `!${fileMarkdown}`;
|
||||||
@ -43,7 +45,7 @@ export function generateMarkdownLinkForAttachment(file, {width, dppx}: {width?:
|
|||||||
return fileMarkdown;
|
return fileMarkdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCopyLink(file) {
|
function addCopyLink(file: Partial<CustomDropzoneFile>) {
|
||||||
// Create a "Copy Link" element, to conveniently copy the image or file link as Markdown to the clipboard
|
// Create a "Copy Link" element, to conveniently copy the image or file link as Markdown to the clipboard
|
||||||
// The "<a>" element has a hardcoded cursor: pointer because the default is overridden by .dropzone
|
// The "<a>" element has a hardcoded cursor: pointer because the default is overridden by .dropzone
|
||||||
const copyLinkEl = createElementFromHTML(`
|
const copyLinkEl = createElementFromHTML(`
|
||||||
@ -58,6 +60,8 @@ function addCopyLink(file) {
|
|||||||
file.previewTemplate.append(copyLinkEl);
|
file.previewTemplate.append(copyLinkEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FileUuidDict = Record<string, {submitted: boolean}>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {HTMLElement} dropzoneEl
|
* @param {HTMLElement} dropzoneEl
|
||||||
*/
|
*/
|
||||||
@ -67,7 +71,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) {
|
|||||||
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 = {}; // 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},
|
||||||
@ -89,7 +93,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) {
|
|||||||
// "http://localhost:3000/owner/repo/issues/[object%20Event]"
|
// "http://localhost:3000/owner/repo/issues/[object%20Event]"
|
||||||
// the reason is that the preview "callback(dataURL)" is assign to "img.onerror" then "thumbnail" uses the error object as the dataURL and generates '<img src="[object Event]">'
|
// the reason is that the preview "callback(dataURL)" is assign to "img.onerror" then "thumbnail" uses the error object as the dataURL and generates '<img src="[object Event]">'
|
||||||
const dzInst = await createDropzone(dropzoneEl, opts);
|
const dzInst = await createDropzone(dropzoneEl, opts);
|
||||||
dzInst.on('success', (file: DropzoneFile & {uuid: string}, resp: any) => {
|
dzInst.on('success', (file: CustomDropzoneFile, resp: any) => {
|
||||||
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});
|
||||||
@ -98,7 +102,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) {
|
|||||||
dzInst.emit(DropzoneCustomEventUploadDone, {file});
|
dzInst.emit(DropzoneCustomEventUploadDone, {file});
|
||||||
});
|
});
|
||||||
|
|
||||||
dzInst.on('removedfile', async (file: DropzoneFile & {uuid: string}) => {
|
dzInst.on('removedfile', async (file: CustomDropzoneFile) => {
|
||||||
if (disableRemovedfileEvent) return;
|
if (disableRemovedfileEvent) return;
|
||||||
|
|
||||||
dzInst.emit(DropzoneCustomEventRemovedFile, {fileUuid: file.uuid});
|
dzInst.emit(DropzoneCustomEventRemovedFile, {fileUuid: file.uuid});
|
||||||
|
@ -15,13 +15,13 @@ export const emojiKeys = Object.keys(tempMap).sort((a, b) => {
|
|||||||
return a.localeCompare(b);
|
return a.localeCompare(b);
|
||||||
});
|
});
|
||||||
|
|
||||||
const emojiMap = {};
|
const emojiMap: Record<string, string> = {};
|
||||||
for (const key of emojiKeys) {
|
for (const key of emojiKeys) {
|
||||||
emojiMap[key] = tempMap[key];
|
emojiMap[key] = tempMap[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
// retrieve HTML for given emoji name
|
// retrieve HTML for given emoji name
|
||||||
export function emojiHTML(name) {
|
export function emojiHTML(name: string) {
|
||||||
let inner;
|
let inner;
|
||||||
if (Object.hasOwn(customEmojis, name)) {
|
if (Object.hasOwn(customEmojis, name)) {
|
||||||
inner = `<img alt=":${name}:" src="${assetUrlPrefix}/img/emoji/${name}.png">`;
|
inner = `<img alt=":${name}:" src="${assetUrlPrefix}/img/emoji/${name}.png">`;
|
||||||
@ -33,6 +33,6 @@ export function emojiHTML(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// retrieve string for given emoji name
|
// retrieve string for given emoji name
|
||||||
export function emojiString(name) {
|
export function emojiString(name: string) {
|
||||||
return emojiMap[name] || `:${name}:`;
|
return emojiMap[name] || `:${name}:`;
|
||||||
}
|
}
|
||||||
|
@ -5,15 +5,15 @@ import {svg} from '../svg.ts';
|
|||||||
// The fold arrow is the icon displayed on the upper left of the file box, especially intended for components having the 'fold-file' class.
|
// The fold arrow is the icon displayed on the upper left of the file box, especially intended for components having the 'fold-file' class.
|
||||||
// The file content box is the box that should be hidden or shown, especially intended for components having the 'file-content' class.
|
// The file content box is the box that should be hidden or shown, especially intended for components having the 'file-content' class.
|
||||||
//
|
//
|
||||||
export function setFileFolding(fileContentBox, foldArrow, newFold) {
|
export function setFileFolding(fileContentBox: HTMLElement, foldArrow: HTMLElement, newFold: boolean) {
|
||||||
foldArrow.innerHTML = svg(`octicon-chevron-${newFold ? 'right' : 'down'}`, 18);
|
foldArrow.innerHTML = svg(`octicon-chevron-${newFold ? 'right' : 'down'}`, 18);
|
||||||
fileContentBox.setAttribute('data-folded', newFold);
|
fileContentBox.setAttribute('data-folded', String(newFold));
|
||||||
if (newFold && fileContentBox.getBoundingClientRect().top < 0) {
|
if (newFold && fileContentBox.getBoundingClientRect().top < 0) {
|
||||||
fileContentBox.scrollIntoView();
|
fileContentBox.scrollIntoView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like `setFileFolding`, except that it automatically inverts the current file folding state.
|
// Like `setFileFolding`, except that it automatically inverts the current file folding state.
|
||||||
export function invertFileFolding(fileContentBox, foldArrow) {
|
export function invertFileFolding(fileContentBox:HTMLElement, foldArrow: HTMLElement) {
|
||||||
setFileFolding(fileContentBox, foldArrow, fileContentBox.getAttribute('data-folded') !== 'true');
|
setFileFolding(fileContentBox, foldArrow, fileContentBox.getAttribute('data-folded') !== 'true');
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ export function initHeatmap() {
|
|||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const heatmap = {};
|
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();
|
||||||
|
@ -3,7 +3,7 @@ import {hideElem, loadElem, queryElemChildren, queryElems} from '../utils/dom.ts
|
|||||||
import {parseDom} from '../utils.ts';
|
import {parseDom} from '../utils.ts';
|
||||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||||
|
|
||||||
function getDefaultSvgBoundsIfUndefined(text, src) {
|
function getDefaultSvgBoundsIfUndefined(text: string, src: string) {
|
||||||
const defaultSize = 300;
|
const defaultSize = 300;
|
||||||
const maxSize = 99999;
|
const maxSize = 99999;
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ function getDefaultSvgBoundsIfUndefined(text, src) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createContext(imageAfter, imageBefore) {
|
function createContext(imageAfter: HTMLImageElement, imageBefore: HTMLImageElement) {
|
||||||
const sizeAfter = {
|
const sizeAfter = {
|
||||||
width: imageAfter?.width || 0,
|
width: imageAfter?.width || 0,
|
||||||
height: imageAfter?.height || 0,
|
height: imageAfter?.height || 0,
|
||||||
@ -123,7 +123,7 @@ class ImageDiff {
|
|||||||
queryElemChildren(containerEl, '.image-diff-tabs', (el) => el.classList.remove('is-loading'));
|
queryElemChildren(containerEl, '.image-diff-tabs', (el) => el.classList.remove('is-loading'));
|
||||||
}
|
}
|
||||||
|
|
||||||
initSideBySide(sizes) {
|
initSideBySide(sizes: Record<string, any>) {
|
||||||
let factor = 1;
|
let factor = 1;
|
||||||
if (sizes.maxSize.width > (this.diffContainerWidth - 24) / 2) {
|
if (sizes.maxSize.width > (this.diffContainerWidth - 24) / 2) {
|
||||||
factor = (this.diffContainerWidth - 24) / 2 / sizes.maxSize.width;
|
factor = (this.diffContainerWidth - 24) / 2 / sizes.maxSize.width;
|
||||||
@ -176,7 +176,7 @@ class ImageDiff {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initSwipe(sizes) {
|
initSwipe(sizes: Record<string, any>) {
|
||||||
let factor = 1;
|
let factor = 1;
|
||||||
if (sizes.maxSize.width > this.diffContainerWidth - 12) {
|
if (sizes.maxSize.width > this.diffContainerWidth - 12) {
|
||||||
factor = (this.diffContainerWidth - 12) / sizes.maxSize.width;
|
factor = (this.diffContainerWidth - 12) / sizes.maxSize.width;
|
||||||
@ -215,14 +215,14 @@ class ImageDiff {
|
|||||||
|
|
||||||
this.containerEl.querySelector('.swipe-bar').addEventListener('mousedown', (e) => {
|
this.containerEl.querySelector('.swipe-bar').addEventListener('mousedown', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.initSwipeEventListeners(e.currentTarget);
|
this.initSwipeEventListeners(e.currentTarget as HTMLElement);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initSwipeEventListeners(swipeBar) {
|
initSwipeEventListeners(swipeBar: HTMLElement) {
|
||||||
const swipeFrame = swipeBar.parentNode;
|
const swipeFrame = swipeBar.parentNode as HTMLElement;
|
||||||
const width = swipeFrame.clientWidth;
|
const width = swipeFrame.clientWidth;
|
||||||
const onSwipeMouseMove = (e) => {
|
const onSwipeMouseMove = (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
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));
|
||||||
@ -237,7 +237,7 @@ class ImageDiff {
|
|||||||
document.addEventListener('mouseup', removeEventListeners);
|
document.addEventListener('mouseup', removeEventListeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
initOverlay(sizes) {
|
initOverlay(sizes: Record<string, any>) {
|
||||||
let factor = 1;
|
let factor = 1;
|
||||||
if (sizes.maxSize.width > this.diffContainerWidth - 12) {
|
if (sizes.maxSize.width > this.diffContainerWidth - 12) {
|
||||||
factor = (this.diffContainerWidth - 12) / sizes.maxSize.width;
|
factor = (this.diffContainerWidth - 12) / sizes.maxSize.width;
|
||||||
|
@ -12,11 +12,12 @@ export function initInstall() {
|
|||||||
initPreInstall();
|
initPreInstall();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initPreInstall() {
|
function initPreInstall() {
|
||||||
const defaultDbUser = 'gitea';
|
const defaultDbUser = 'gitea';
|
||||||
const defaultDbName = 'gitea';
|
const defaultDbName = 'gitea';
|
||||||
|
|
||||||
const defaultDbHosts = {
|
const defaultDbHosts: Record<string, string> = {
|
||||||
mysql: '127.0.0.1:3306',
|
mysql: '127.0.0.1:3306',
|
||||||
postgres: '127.0.0.1:5432',
|
postgres: '127.0.0.1:5432',
|
||||||
mssql: '127.0.0.1:1433',
|
mssql: '127.0.0.1:1433',
|
||||||
|
@ -21,7 +21,7 @@ function initOrgTeamSearchRepoBox() {
|
|||||||
minCharacters: 2,
|
minCharacters: 2,
|
||||||
apiSettings: {
|
apiSettings: {
|
||||||
url: `${appSubUrl}/repo/search?q={query}&uid=${$searchRepoBox.data('uid')}`,
|
url: `${appSubUrl}/repo/search?q={query}&uid=${$searchRepoBox.data('uid')}`,
|
||||||
onResponse(response) {
|
onResponse(response: any) {
|
||||||
const items = [];
|
const items = [];
|
||||||
for (const item of response.data) {
|
for (const item of response.data) {
|
||||||
items.push({
|
items.push({
|
||||||
|
@ -59,13 +59,13 @@ export function initViewedCheckboxListenerFor() {
|
|||||||
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
|
||||||
const fileInPageData = diffTreeStore().files.find((x) => x.Name === fileName);
|
const fileInPageData = diffTreeStore().files.find((x: Record<string, any>) => x.Name === fileName);
|
||||||
if (fileInPageData) {
|
if (fileInPageData) {
|
||||||
fileInPageData.IsViewed = this.checked;
|
fileInPageData.IsViewed = this.checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unfortunately, actual forms cause too many problems, hence another approach is needed
|
// Unfortunately, actual forms cause too many problems, hence another approach is needed
|
||||||
const files = {};
|
const files: Record<string, boolean> = {};
|
||||||
files[fileName] = this.checked;
|
files[fileName] = this.checked;
|
||||||
const data: Record<string, any> = {files};
|
const data: Record<string, any> = {files};
|
||||||
const headCommitSHA = form.getAttribute('data-headcommit');
|
const headCommitSHA = form.getAttribute('data-headcommit');
|
||||||
@ -82,13 +82,13 @@ export function initViewedCheckboxListenerFor() {
|
|||||||
export function initExpandAndCollapseFilesButton() {
|
export function initExpandAndCollapseFilesButton() {
|
||||||
// expand btn
|
// expand btn
|
||||||
document.querySelector(expandFilesBtnSelector)?.addEventListener('click', () => {
|
document.querySelector(expandFilesBtnSelector)?.addEventListener('click', () => {
|
||||||
for (const box of document.querySelectorAll('.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('.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);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {queryElems} from '../utils/dom.ts';
|
import {queryElems, type DOMEvent} 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) {
|
async function onDownloadArchive(e: DOMEvent<MouseEvent>) {
|
||||||
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('a.archive-link[href]');
|
const el = e.target.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 {
|
||||||
@ -107,7 +107,7 @@ export function initRepoCloneButtons() {
|
|||||||
queryElems(document, '.clone-buttons-combo', initCloneSchemeUrlSelection);
|
queryElems(document, '.clone-buttons-combo', initCloneSchemeUrlSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateIssuesMeta(url, action, issue_ids, id) {
|
export async function updateIssuesMeta(url: string, action: string, issue_ids: string, id: string) {
|
||||||
try {
|
try {
|
||||||
const response = await POST(url, {data: new URLSearchParams({action, issue_ids, id})});
|
const response = await POST(url, {data: new URLSearchParams({action, issue_ids, id})});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
@ -168,7 +168,7 @@ function onShowMoreFiles() {
|
|||||||
initDiffHeaderPopup();
|
initDiffHeaderPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadMoreFiles(url) {
|
export async function loadMoreFiles(url: string) {
|
||||||
const target = document.querySelector('a#diff-show-more-files');
|
const target = document.querySelector('a#diff-show-more-files');
|
||||||
if (target?.classList.contains('disabled') || pageData.diffFileInfo.isLoadingNewData) {
|
if (target?.classList.contains('disabled') || pageData.diffFileInfo.isLoadingNewData) {
|
||||||
return;
|
return;
|
||||||
|
@ -168,7 +168,7 @@ export function initRepoEditor() {
|
|||||||
silent: true,
|
silent: true,
|
||||||
dirtyClass: dirtyFileClass,
|
dirtyClass: dirtyFileClass,
|
||||||
fieldSelector: ':input:not(.commit-form-wrapper :input)',
|
fieldSelector: ':input:not(.commit-form-wrapper :input)',
|
||||||
change($form) {
|
change($form: any) {
|
||||||
const dirty = $form[0]?.classList.contains(dirtyFileClass);
|
const dirty = $form[0]?.classList.contains(dirtyFileClass);
|
||||||
commitButton.disabled = !dirty;
|
commitButton.disabled = !dirty;
|
||||||
},
|
},
|
||||||
|
@ -4,13 +4,15 @@ import {pathEscapeSegments} from '../utils/url.ts';
|
|||||||
import {GET} from '../modules/fetch.ts';
|
import {GET} from '../modules/fetch.ts';
|
||||||
|
|
||||||
const threshold = 50;
|
const threshold = 50;
|
||||||
let files = [];
|
let files: Array<string> = [];
|
||||||
let repoFindFileInput, repoFindFileTableBody, repoFindFileNoResult;
|
let repoFindFileInput: HTMLInputElement;
|
||||||
|
let repoFindFileTableBody: HTMLElement;
|
||||||
|
let repoFindFileNoResult: HTMLElement;
|
||||||
|
|
||||||
// return the case-insensitive sub-match result as an array: [unmatched, matched, unmatched, matched, ...]
|
// return the case-insensitive sub-match result as an array: [unmatched, matched, unmatched, matched, ...]
|
||||||
// res[even] is unmatched, res[odd] is matched, see unit tests for examples
|
// res[even] is unmatched, res[odd] is matched, see unit tests for examples
|
||||||
// argument subLower must be a lower-cased string.
|
// argument subLower must be a lower-cased string.
|
||||||
export function strSubMatch(full, subLower) {
|
export function strSubMatch(full: string, subLower: string) {
|
||||||
const res = [''];
|
const res = [''];
|
||||||
let i = 0, j = 0;
|
let i = 0, j = 0;
|
||||||
const fullLower = full.toLowerCase();
|
const fullLower = full.toLowerCase();
|
||||||
@ -38,7 +40,7 @@ export function strSubMatch(full, subLower) {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calcMatchedWeight(matchResult) {
|
export function calcMatchedWeight(matchResult: Array<any>) {
|
||||||
let weight = 0;
|
let weight = 0;
|
||||||
for (let i = 0; i < matchResult.length; i++) {
|
for (let i = 0; i < matchResult.length; i++) {
|
||||||
if (i % 2 === 1) { // matches are on odd indices, see strSubMatch
|
if (i % 2 === 1) { // matches are on odd indices, see strSubMatch
|
||||||
@ -49,7 +51,7 @@ export function calcMatchedWeight(matchResult) {
|
|||||||
return weight;
|
return weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterRepoFilesWeighted(files, filter) {
|
export function filterRepoFilesWeighted(files: Array<string>, filter: string) {
|
||||||
let filterResult = [];
|
let filterResult = [];
|
||||||
if (filter) {
|
if (filter) {
|
||||||
const filterLower = filter.toLowerCase();
|
const filterLower = filter.toLowerCase();
|
||||||
@ -71,7 +73,7 @@ export function filterRepoFilesWeighted(files, filter) {
|
|||||||
return filterResult;
|
return filterResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterRepoFiles(filter) {
|
function filterRepoFiles(filter: string) {
|
||||||
const treeLink = repoFindFileInput.getAttribute('data-url-tree-link');
|
const treeLink = repoFindFileInput.getAttribute('data-url-tree-link');
|
||||||
repoFindFileTableBody.innerHTML = '';
|
repoFindFileTableBody.innerHTML = '';
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ export function initRepoTopicBar() {
|
|||||||
onResponse(this: any, res: any) {
|
onResponse(this: any, res: any) {
|
||||||
const formattedResponse = {
|
const formattedResponse = {
|
||||||
success: false,
|
success: false,
|
||||||
results: [],
|
results: [] as Array<Record<string, any>>,
|
||||||
};
|
};
|
||||||
const query = stripTags(this.urlData.query.trim());
|
const query = stripTags(this.urlData.query.trim());
|
||||||
let found_query = false;
|
let found_query = false;
|
||||||
@ -134,12 +134,12 @@ export function initRepoTopicBar() {
|
|||||||
return formattedResponse;
|
return formattedResponse;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
onLabelCreate(value) {
|
onLabelCreate(value: string) {
|
||||||
value = value.toLowerCase().trim();
|
value = value.toLowerCase().trim();
|
||||||
this.attr('data-value', value).contents().first().replaceWith(value);
|
this.attr('data-value', value).contents().first().replaceWith(value);
|
||||||
return fomanticQuery(this);
|
return fomanticQuery(this);
|
||||||
},
|
},
|
||||||
onAdd(addedValue, _addedText, $addedChoice) {
|
onAdd(addedValue: string, _addedText: any, $addedChoice: any) {
|
||||||
addedValue = addedValue.toLowerCase().trim();
|
addedValue = addedValue.toLowerCase().trim();
|
||||||
$addedChoice[0].setAttribute('data-value', addedValue);
|
$addedChoice[0].setAttribute('data-value', addedValue);
|
||||||
$addedChoice[0].setAttribute('data-text', addedValue);
|
$addedChoice[0].setAttribute('data-text', addedValue);
|
||||||
|
@ -33,7 +33,7 @@ function showContentHistoryDetail(issueBaseUrl: string, commentId: string, histo
|
|||||||
$fomanticDropdownOptions.dropdown({
|
$fomanticDropdownOptions.dropdown({
|
||||||
showOnFocus: false,
|
showOnFocus: false,
|
||||||
allowReselection: true,
|
allowReselection: true,
|
||||||
async onChange(_value, _text, $item) {
|
async onChange(_value: string, _text: string, $item: any) {
|
||||||
const optionItem = $item.data('option-item');
|
const optionItem = $item.data('option-item');
|
||||||
if (optionItem === 'delete') {
|
if (optionItem === 'delete') {
|
||||||
if (window.confirm(i18nTextDeleteFromHistoryConfirm)) {
|
if (window.confirm(i18nTextDeleteFromHistoryConfirm)) {
|
||||||
@ -115,7 +115,7 @@ function showContentHistoryMenu(issueBaseUrl: string, elCommentItem: Element, co
|
|||||||
onHide() {
|
onHide() {
|
||||||
$fomanticDropdown.dropdown('change values', null);
|
$fomanticDropdown.dropdown('change values', null);
|
||||||
},
|
},
|
||||||
onChange(value, itemHtml, $item) {
|
onChange(value: string, itemHtml: string, $item: any) {
|
||||||
if (value && !$item.find('[data-history-is-deleted=1]').length) {
|
if (value && !$item.find('[data-history-is-deleted=1]').length) {
|
||||||
showContentHistoryDetail(issueBaseUrl, commentId, value, itemHtml);
|
showContentHistoryDetail(issueBaseUrl, commentId, value, itemHtml);
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,14 @@ 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} from '../utils/dom.ts';
|
import {hideElem, querySingleVisibleElem, showElem, type DOMEvent} from '../utils/dom.ts';
|
||||||
import {attachRefIssueContextPopup} from './contextpopup.ts';
|
import {attachRefIssueContextPopup} from './contextpopup.ts';
|
||||||
import {initCommentContent, initMarkupContent} from '../markup/content.ts';
|
import {initCommentContent, initMarkupContent} from '../markup/content.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) {
|
async function tryOnEditContent(e: DOMEvent<MouseEvent>) {
|
||||||
const clickTarget = e.target.closest('.edit-content');
|
const clickTarget = e.target.closest('.edit-content');
|
||||||
if (!clickTarget) return;
|
if (!clickTarget) return;
|
||||||
|
|
||||||
@ -21,14 +21,14 @@ async function tryOnEditContent(e) {
|
|||||||
|
|
||||||
let comboMarkdownEditor : ComboMarkdownEditor;
|
let comboMarkdownEditor : ComboMarkdownEditor;
|
||||||
|
|
||||||
const cancelAndReset = (e) => {
|
const cancelAndReset = (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
showElem(renderContent);
|
showElem(renderContent);
|
||||||
hideElem(editContentZone);
|
hideElem(editContentZone);
|
||||||
comboMarkdownEditor.dropzoneReloadFiles();
|
comboMarkdownEditor.dropzoneReloadFiles();
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveAndRefresh = async (e) => {
|
const saveAndRefresh = async (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// we are already in a form, do not bubble up to the document otherwise there will be other "form submit handlers"
|
// we are already in a form, do not bubble up to the document otherwise there will be other "form submit handlers"
|
||||||
// at the moment, the form submit event conflicts with initRepoDiffConversationForm (global '.conversation-holder form' event handler)
|
// at the moment, the form submit event conflicts with initRepoDiffConversationForm (global '.conversation-holder form' event handler)
|
||||||
@ -60,7 +60,7 @@ async function tryOnEditContent(e) {
|
|||||||
} else {
|
} else {
|
||||||
renderContent.innerHTML = data.content;
|
renderContent.innerHTML = data.content;
|
||||||
rawContent.textContent = comboMarkdownEditor.value();
|
rawContent.textContent = comboMarkdownEditor.value();
|
||||||
const refIssues = renderContent.querySelectorAll('p .ref-issue');
|
const refIssues = renderContent.querySelectorAll<HTMLElement>('p .ref-issue');
|
||||||
attachRefIssueContextPopup(refIssues);
|
attachRefIssueContextPopup(refIssues);
|
||||||
}
|
}
|
||||||
const content = segment;
|
const content = segment;
|
||||||
@ -125,7 +125,7 @@ function extractSelectedMarkdown(container: HTMLElement) {
|
|||||||
return convertHtmlToMarkdown(el);
|
return convertHtmlToMarkdown(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tryOnQuoteReply(e) {
|
async function tryOnQuoteReply(e: Event) {
|
||||||
const clickTarget = (e.target as HTMLElement).closest('.quote-reply');
|
const clickTarget = (e.target as HTMLElement).closest('.quote-reply');
|
||||||
if (!clickTarget) return;
|
if (!clickTarget) return;
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ async function tryOnQuoteReply(e) {
|
|||||||
|
|
||||||
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('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
|
||||||
|
@ -7,6 +7,7 @@ import {createSortable} from '../modules/sortable.ts';
|
|||||||
import {DELETE, POST} from '../modules/fetch.ts';
|
import {DELETE, POST} from '../modules/fetch.ts';
|
||||||
import {parseDom} from '../utils.ts';
|
import {parseDom} from '../utils.ts';
|
||||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||||
|
import type {SortableEvent} from 'sortablejs';
|
||||||
|
|
||||||
function initRepoIssueListCheckboxes() {
|
function initRepoIssueListCheckboxes() {
|
||||||
const issueSelectAll = document.querySelector<HTMLInputElement>('.issue-checkbox-all');
|
const issueSelectAll = document.querySelector<HTMLInputElement>('.issue-checkbox-all');
|
||||||
@ -104,7 +105,7 @@ function initDropdownUserRemoteSearch(el: Element) {
|
|||||||
$searchDropdown.dropdown('setting', {
|
$searchDropdown.dropdown('setting', {
|
||||||
fullTextSearch: true,
|
fullTextSearch: true,
|
||||||
selectOnKeydown: false,
|
selectOnKeydown: false,
|
||||||
action: (_text, value) => {
|
action: (_text: string, value: string) => {
|
||||||
window.location.href = actionJumpUrl.replace('{username}', encodeURIComponent(value));
|
window.location.href = actionJumpUrl.replace('{username}', encodeURIComponent(value));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -133,7 +134,7 @@ function initDropdownUserRemoteSearch(el: Element) {
|
|||||||
$searchDropdown.dropdown('setting', 'apiSettings', {
|
$searchDropdown.dropdown('setting', 'apiSettings', {
|
||||||
cache: false,
|
cache: false,
|
||||||
url: `${searchUrl}&q={query}`,
|
url: `${searchUrl}&q={query}`,
|
||||||
onResponse(resp) {
|
onResponse(resp: any) {
|
||||||
// the content is provided by backend IssuePosters handler
|
// the content is provided by backend IssuePosters handler
|
||||||
processedResults.length = 0;
|
processedResults.length = 0;
|
||||||
for (const item of resp.results) {
|
for (const item of resp.results) {
|
||||||
@ -153,7 +154,7 @@ function initDropdownUserRemoteSearch(el: Element) {
|
|||||||
const dropdownSetup = {...$searchDropdown.dropdown('internal', 'setup')};
|
const dropdownSetup = {...$searchDropdown.dropdown('internal', 'setup')};
|
||||||
const dropdownTemplates = $searchDropdown.dropdown('setting', 'templates');
|
const dropdownTemplates = $searchDropdown.dropdown('setting', 'templates');
|
||||||
$searchDropdown.dropdown('internal', 'setup', dropdownSetup);
|
$searchDropdown.dropdown('internal', 'setup', dropdownSetup);
|
||||||
dropdownSetup.menu = function (values) {
|
dropdownSetup.menu = function (values: any) {
|
||||||
// remove old dynamic items
|
// remove old dynamic items
|
||||||
for (const el of elMenu.querySelectorAll(':scope > .dynamic-item')) {
|
for (const el of elMenu.querySelectorAll(':scope > .dynamic-item')) {
|
||||||
el.remove();
|
el.remove();
|
||||||
@ -193,7 +194,7 @@ function initPinRemoveButton() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pinMoveEnd(e) {
|
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}});
|
||||||
|
@ -46,7 +46,7 @@ class IssueSidebarComboList {
|
|||||||
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) {
|
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) {
|
||||||
@ -60,7 +60,7 @@ class IssueSidebarComboList {
|
|||||||
toggleElem(elEmptyTip, !hasItems);
|
toggleElem(elEmptyTip, !hasItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateToBackend(changedValues) {
|
async updateToBackend(changedValues: Array<string>) {
|
||||||
if (this.updateAlgo === 'diff') {
|
if (this.updateAlgo === 'diff') {
|
||||||
for (const value of this.initialValues) {
|
for (const value of this.initialValues) {
|
||||||
if (!changedValues.includes(value)) {
|
if (!changedValues.includes(value)) {
|
||||||
@ -93,7 +93,7 @@ class IssueSidebarComboList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onItemClick(e) {
|
async onItemClick(e: Event) {
|
||||||
const elItem = (e.target as HTMLElement).closest('.item');
|
const elItem = (e.target as HTMLElement).closest('.item');
|
||||||
if (!elItem) return;
|
if (!elItem) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -32,8 +32,8 @@ export function initRepoIssueSidebarList() {
|
|||||||
fullTextSearch: true,
|
fullTextSearch: true,
|
||||||
apiSettings: {
|
apiSettings: {
|
||||||
url: issueSearchUrl,
|
url: issueSearchUrl,
|
||||||
onResponse(response) {
|
onResponse(response: any) {
|
||||||
const filteredResponse = {success: true, results: []};
|
const filteredResponse = {success: true, results: [] as Array<Record<string, any>>};
|
||||||
const currIssueId = $('#new-dependency-drop-list').data('issue-id');
|
const currIssueId = $('#new-dependency-drop-list').data('issue-id');
|
||||||
// Parse the response from the api to work with our dropdown
|
// Parse the response from the api to work with our dropdown
|
||||||
$.each(response, (_i, issue) => {
|
$.each(response, (_i, issue) => {
|
||||||
@ -247,7 +247,7 @@ export function initRepoPullRequestUpdate() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('.update-button > .dropdown').dropdown({
|
$('.update-button > .dropdown').dropdown({
|
||||||
onChange(_text, _value, $choice) {
|
onChange(_text: string, _value: string, $choice: any) {
|
||||||
const choiceEl = $choice[0];
|
const choiceEl = $choice[0];
|
||||||
const url = choiceEl.getAttribute('data-do');
|
const url = choiceEl.getAttribute('data-do');
|
||||||
if (url) {
|
if (url) {
|
||||||
@ -298,8 +298,8 @@ export function initRepoIssueReferenceRepositorySearch() {
|
|||||||
.dropdown({
|
.dropdown({
|
||||||
apiSettings: {
|
apiSettings: {
|
||||||
url: `${appSubUrl}/repo/search?q={query}&limit=20`,
|
url: `${appSubUrl}/repo/search?q={query}&limit=20`,
|
||||||
onResponse(response) {
|
onResponse(response: any) {
|
||||||
const filteredResponse = {success: true, results: []};
|
const filteredResponse = {success: true, results: [] as Array<Record<string, any>>};
|
||||||
$.each(response.data, (_r, repo) => {
|
$.each(response.data, (_r, repo) => {
|
||||||
filteredResponse.results.push({
|
filteredResponse.results.push({
|
||||||
name: htmlEscape(repo.repository.full_name),
|
name: htmlEscape(repo.repository.full_name),
|
||||||
@ -310,7 +310,7 @@ export function initRepoIssueReferenceRepositorySearch() {
|
|||||||
},
|
},
|
||||||
cache: false,
|
cache: false,
|
||||||
},
|
},
|
||||||
onChange(_value, _text, $choice) {
|
onChange(_value: string, _text: string, $choice: any) {
|
||||||
const $form = $choice.closest('form');
|
const $form = $choice.closest('form');
|
||||||
if (!$form.length) return;
|
if (!$form.length) return;
|
||||||
|
|
||||||
@ -360,7 +360,7 @@ export function initRepoIssueComments() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleReply(el) {
|
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');
|
||||||
|
|
||||||
@ -379,7 +379,7 @@ export function initRepoPullRequestReview() {
|
|||||||
const groupID = commentDiv.closest('div[id^="code-comments-"]')?.getAttribute('id');
|
const groupID = commentDiv.closest('div[id^="code-comments-"]')?.getAttribute('id');
|
||||||
if (groupID && groupID.startsWith('code-comments-')) {
|
if (groupID && groupID.startsWith('code-comments-')) {
|
||||||
const id = groupID.slice(14);
|
const id = groupID.slice(14);
|
||||||
const ancestorDiffBox = commentDiv.closest('.diff-file-box');
|
const ancestorDiffBox = commentDiv.closest<HTMLElement>('.diff-file-box');
|
||||||
|
|
||||||
hideElem(`#show-outdated-${id}`);
|
hideElem(`#show-outdated-${id}`);
|
||||||
showElem(`#code-comments-${id}, #code-preview-${id}, #hide-outdated-${id}`);
|
showElem(`#code-comments-${id}, #code-preview-${id}, #hide-outdated-${id}`);
|
||||||
@ -589,7 +589,7 @@ export function initRepoIssueBranchSelect() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initSingleCommentEditor($commentForm) {
|
async function initSingleCommentEditor($commentForm: any) {
|
||||||
// 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
|
||||||
@ -611,7 +611,7 @@ async function initSingleCommentEditor($commentForm) {
|
|||||||
syncUiState();
|
syncUiState();
|
||||||
}
|
}
|
||||||
|
|
||||||
function initIssueTemplateCommentEditors($commentForm) {
|
function initIssueTemplateCommentEditors($commentForm: any) {
|
||||||
// pages:
|
// pages:
|
||||||
// * new issue with issue template
|
// * new issue with issue template
|
||||||
const $comboFields = $commentForm.find('.combo-editor-dropzone');
|
const $comboFields = $commentForm.find('.combo-editor-dropzone');
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import {hideElem, showElem} from '../utils/dom.ts';
|
import {hideElem, showElem, type DOMEvent} from '../utils/dom.ts';
|
||||||
import {GET, POST} from '../modules/fetch.ts';
|
import {GET, POST} from '../modules/fetch.ts';
|
||||||
|
|
||||||
export function initRepoMigrationStatusChecker() {
|
export function initRepoMigrationStatusChecker() {
|
||||||
const repoMigrating = document.querySelector('#repo_migrating');
|
const repoMigrating = document.querySelector('#repo_migrating');
|
||||||
if (!repoMigrating) return;
|
if (!repoMigrating) return;
|
||||||
|
|
||||||
document.querySelector('#repo_migrating_retry')?.addEventListener('click', doMigrationRetry);
|
document.querySelector<HTMLButtonElement>('#repo_migrating_retry')?.addEventListener('click', doMigrationRetry);
|
||||||
|
|
||||||
const repoLink = repoMigrating.getAttribute('data-migrating-repo-link');
|
const repoLink = repoMigrating.getAttribute('data-migrating-repo-link');
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ export function initRepoMigrationStatusChecker() {
|
|||||||
syncTaskStatus(); // no await
|
syncTaskStatus(); // no await
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doMigrationRetry(e) {
|
async function doMigrationRetry(e: DOMEvent<MouseEvent>) {
|
||||||
await POST(e.target.getAttribute('data-migrating-task-retry-url'));
|
await POST(e.target.getAttribute('data-migrating-task-retry-url'));
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ function initRepoNewTemplateSearch(form: HTMLFormElement) {
|
|||||||
$dropdown.dropdown('setting', {
|
$dropdown.dropdown('setting', {
|
||||||
apiSettings: {
|
apiSettings: {
|
||||||
url: `${appSubUrl}/repo/search?q={query}&template=true&priority_owner_id=${inputRepoOwnerUid.value}`,
|
url: `${appSubUrl}/repo/search?q={query}&template=true&priority_owner_id=${inputRepoOwnerUid.value}`,
|
||||||
onResponse(response) {
|
onResponse(response: any) {
|
||||||
const results = [];
|
const results = [];
|
||||||
results.push({name: '', value: ''}); // empty item means not using template
|
results.push({name: '', value: ''}); // empty item means not using template
|
||||||
for (const tmplRepo of response.data) {
|
for (const tmplRepo of response.data) {
|
||||||
@ -66,7 +66,7 @@ export function initRepoNew() {
|
|||||||
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 = {'.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];
|
||||||
// inputPrivate might be disabled because site admin "force private"
|
// inputPrivate might be disabled because site admin "force private"
|
||||||
if (preferPrivate !== undefined && !inputPrivate.closest('.disabled, [disabled]')) {
|
if (preferPrivate !== undefined && !inputPrivate.closest('.disabled, [disabled]')) {
|
||||||
|
@ -12,7 +12,7 @@ function initRepoSettingsCollaboration() {
|
|||||||
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');
|
||||||
$(dropdownEl).dropdown({
|
$(dropdownEl).dropdown({
|
||||||
async action(text, value) {
|
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');
|
||||||
$(dropdownEl).dropdown('hide');
|
$(dropdownEl).dropdown('hide');
|
||||||
@ -53,8 +53,8 @@ function initRepoSettingsSearchTeamBox() {
|
|||||||
apiSettings: {
|
apiSettings: {
|
||||||
url: `${appSubUrl}/org/${searchTeamBox.getAttribute('data-org-name')}/teams/-/search?q={query}`,
|
url: `${appSubUrl}/org/${searchTeamBox.getAttribute('data-org-name')}/teams/-/search?q={query}`,
|
||||||
headers: {'X-Csrf-Token': csrfToken},
|
headers: {'X-Csrf-Token': csrfToken},
|
||||||
onResponse(response) {
|
onResponse(response: any) {
|
||||||
const items = [];
|
const items: Array<Record<string, any>> = [];
|
||||||
$.each(response.data, (_i, item) => {
|
$.each(response.data, (_i, item) => {
|
||||||
items.push({
|
items.push({
|
||||||
title: item.name,
|
title: item.name,
|
||||||
|
@ -70,7 +70,7 @@ async function initRepoWikiFormEditor() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function collapseWikiTocForMobile(collapse) {
|
function collapseWikiTocForMobile(collapse: boolean) {
|
||||||
if (collapse) {
|
if (collapse) {
|
||||||
document.querySelector('.wiki-content-toc details')?.removeAttribute('open');
|
document.querySelector('.wiki-content-toc details')?.removeAttribute('open');
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ export function initStopwatch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let usingPeriodicPoller = false;
|
let usingPeriodicPoller = false;
|
||||||
const startPeriodicPoller = (timeout) => {
|
const startPeriodicPoller = (timeout: number) => {
|
||||||
if (timeout <= 0 || !Number.isFinite(timeout)) return;
|
if (timeout <= 0 || !Number.isFinite(timeout)) return;
|
||||||
usingPeriodicPoller = true;
|
usingPeriodicPoller = true;
|
||||||
setTimeout(() => updateStopwatchWithCallback(startPeriodicPoller, timeout), timeout);
|
setTimeout(() => updateStopwatchWithCallback(startPeriodicPoller, timeout), timeout);
|
||||||
@ -103,7 +103,7 @@ export function initStopwatch() {
|
|||||||
startPeriodicPoller(notificationSettings.MinTimeout);
|
startPeriodicPoller(notificationSettings.MinTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateStopwatchWithCallback(callback, timeout) {
|
async function updateStopwatchWithCallback(callback: (timeout: number) => void, timeout: number) {
|
||||||
const isSet = await updateStopwatch();
|
const isSet = await updateStopwatch();
|
||||||
|
|
||||||
if (!isSet) {
|
if (!isSet) {
|
||||||
@ -125,7 +125,7 @@ async function updateStopwatch() {
|
|||||||
return updateStopwatchData(data);
|
return updateStopwatchData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStopwatchData(data) {
|
function updateStopwatchData(data: any) {
|
||||||
const watch = data[0];
|
const watch = data[0];
|
||||||
const btnEls = document.querySelectorAll('.active-stopwatch');
|
const btnEls = document.querySelectorAll('.active-stopwatch');
|
||||||
if (!watch) {
|
if (!watch) {
|
||||||
|
@ -9,7 +9,7 @@ export function initTableSort() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function tableSort(normSort, revSort, isDefault) {
|
function tableSort(normSort: string, revSort: string, isDefault: string) {
|
||||||
if (!normSort) return false;
|
if (!normSort) return false;
|
||||||
if (!revSort) revSort = '';
|
if (!revSort) revSort = '';
|
||||||
|
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import {emojiKeys, emojiHTML, emojiString} from './emoji.ts';
|
import {emojiKeys, emojiHTML, emojiString} from './emoji.ts';
|
||||||
import {htmlEscape} from 'escape-goat';
|
import {htmlEscape} from 'escape-goat';
|
||||||
|
|
||||||
function makeCollections({mentions, emoji}) {
|
type TributeItem = Record<string, any>;
|
||||||
const collections = [];
|
|
||||||
|
|
||||||
if (emoji) {
|
export async function attachTribute(element: HTMLElement) {
|
||||||
collections.push({
|
const {default: Tribute} = await import(/* webpackChunkName: "tribute" */'tributejs');
|
||||||
|
|
||||||
|
const collections = [
|
||||||
|
{ // emojis
|
||||||
trigger: ':',
|
trigger: ':',
|
||||||
requireLeadingSpace: true,
|
requireLeadingSpace: true,
|
||||||
values: (query, cb) => {
|
values: (query: string, cb: (matches: Array<string>) => void) => {
|
||||||
const matches = [];
|
const matches = [];
|
||||||
for (const name of emojiKeys) {
|
for (const name of emojiKeys) {
|
||||||
if (name.includes(query)) {
|
if (name.includes(query)) {
|
||||||
@ -18,22 +20,18 @@ function makeCollections({mentions, emoji}) {
|
|||||||
}
|
}
|
||||||
cb(matches);
|
cb(matches);
|
||||||
},
|
},
|
||||||
lookup: (item) => item,
|
lookup: (item: TributeItem) => item,
|
||||||
selectTemplate: (item) => {
|
selectTemplate: (item: TributeItem) => {
|
||||||
if (item === undefined) return null;
|
if (item === undefined) return null;
|
||||||
return emojiString(item.original);
|
return emojiString(item.original);
|
||||||
},
|
},
|
||||||
menuItemTemplate: (item) => {
|
menuItemTemplate: (item: TributeItem) => {
|
||||||
return `<div class="tribute-item">${emojiHTML(item.original)}<span>${htmlEscape(item.original)}</span></div>`;
|
return `<div class="tribute-item">${emojiHTML(item.original)}<span>${htmlEscape(item.original)}</span></div>`;
|
||||||
},
|
},
|
||||||
});
|
}, { // mentions
|
||||||
}
|
|
||||||
|
|
||||||
if (mentions) {
|
|
||||||
collections.push({
|
|
||||||
values: window.config.mentionValues ?? [],
|
values: window.config.mentionValues ?? [],
|
||||||
requireLeadingSpace: true,
|
requireLeadingSpace: true,
|
||||||
menuItemTemplate: (item) => {
|
menuItemTemplate: (item: TributeItem) => {
|
||||||
return `
|
return `
|
||||||
<div class="tribute-item">
|
<div class="tribute-item">
|
||||||
<img src="${htmlEscape(item.original.avatar)}" width="21" height="21"/>
|
<img src="${htmlEscape(item.original.avatar)}" width="21" height="21"/>
|
||||||
@ -42,15 +40,9 @@ function makeCollections({mentions, emoji}) {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
}
|
];
|
||||||
|
|
||||||
return collections;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function attachTribute(element, {mentions, emoji}) {
|
|
||||||
const {default: Tribute} = await import(/* webpackChunkName: "tribute" */'tributejs');
|
|
||||||
const collections = makeCollections({mentions, emoji});
|
|
||||||
// @ts-expect-error TS2351: This expression is not constructable (strange, why)
|
// @ts-expect-error TS2351: This expression is not constructable (strange, why)
|
||||||
const tribute = new Tribute({collection: collections, noMatchTemplate: ''});
|
const tribute = new Tribute({collection: collections, noMatchTemplate: ''});
|
||||||
tribute.attach(element);
|
tribute.attach(element);
|
||||||
|
@ -114,7 +114,7 @@ async function login2FA() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function verifyAssertion(assertedCredential) {
|
async function verifyAssertion(assertedCredential: any) { // TODO: Credential type does not work
|
||||||
// Move data into Arrays in case it is super long
|
// Move data into Arrays in case it is super long
|
||||||
const authData = new Uint8Array(assertedCredential.response.authenticatorData);
|
const authData = new Uint8Array(assertedCredential.response.authenticatorData);
|
||||||
const clientDataJSON = new Uint8Array(assertedCredential.response.clientDataJSON);
|
const clientDataJSON = new Uint8Array(assertedCredential.response.clientDataJSON);
|
||||||
@ -148,7 +148,7 @@ async function verifyAssertion(assertedCredential) {
|
|||||||
window.location.href = reply?.redirect ?? `${appSubUrl}/`;
|
window.location.href = reply?.redirect ?? `${appSubUrl}/`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function webauthnRegistered(newCredential) {
|
async function webauthnRegistered(newCredential: any) { // TODO: Credential type does not work
|
||||||
const attestationObject = new Uint8Array(newCredential.response.attestationObject);
|
const attestationObject = new Uint8Array(newCredential.response.attestationObject);
|
||||||
const clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
|
const clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
|
||||||
const rawId = new Uint8Array(newCredential.rawId);
|
const rawId = new Uint8Array(newCredential.rawId);
|
||||||
|
@ -3,6 +3,7 @@ export async function renderAsciicast() {
|
|||||||
if (!els.length) return;
|
if (!els.length) return;
|
||||||
|
|
||||||
const [player] = await Promise.all([
|
const [player] = await Promise.all([
|
||||||
|
// @ts-expect-error: module exports no types
|
||||||
import(/* webpackChunkName: "asciinema-player" */'asciinema-player'),
|
import(/* webpackChunkName: "asciinema-player" */'asciinema-player'),
|
||||||
import(/* webpackChunkName: "asciinema-player" */'asciinema-player/dist/bundle/asciinema-player.css'),
|
import(/* webpackChunkName: "asciinema-player" */'asciinema-player/dist/bundle/asciinema-player.css'),
|
||||||
]);
|
]);
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import {htmlEscape} from 'escape-goat';
|
import {htmlEscape} from 'escape-goat';
|
||||||
|
|
||||||
|
type Processor = (el: HTMLElement) => string | HTMLElement | void;
|
||||||
|
|
||||||
type Processors = {
|
type Processors = {
|
||||||
[tagName: string]: (el: HTMLElement) => string | HTMLElement | void;
|
[tagName: string]: Processor;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProcessorContext = {
|
type ProcessorContext = {
|
||||||
@ -11,7 +13,7 @@ type ProcessorContext = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function prepareProcessors(ctx:ProcessorContext): Processors {
|
function prepareProcessors(ctx:ProcessorContext): Processors {
|
||||||
const processors = {
|
const processors: Processors = {
|
||||||
H1(el: HTMLElement) {
|
H1(el: HTMLElement) {
|
||||||
const level = parseInt(el.tagName.slice(1));
|
const level = parseInt(el.tagName.slice(1));
|
||||||
el.textContent = `${'#'.repeat(level)} ${el.textContent.trim()}`;
|
el.textContent = `${'#'.repeat(level)} ${el.textContent.trim()}`;
|
||||||
|
@ -38,7 +38,7 @@ function ariaDropdownFn(this: any, ...args: Parameters<FomanticInitFunction>) {
|
|||||||
// the elements inside the dropdown menu item should not be focusable, the focus should always be on the dropdown primary element.
|
// the elements inside the dropdown menu item should not be focusable, the focus should always be on the dropdown primary element.
|
||||||
function updateMenuItem(dropdown: HTMLElement, item: HTMLElement) {
|
function updateMenuItem(dropdown: HTMLElement, item: HTMLElement) {
|
||||||
if (!item.id) item.id = generateAriaId();
|
if (!item.id) item.id = generateAriaId();
|
||||||
item.setAttribute('role', dropdown[ariaPatchKey].listItemRole);
|
item.setAttribute('role', (dropdown as any)[ariaPatchKey].listItemRole);
|
||||||
item.setAttribute('tabindex', '-1');
|
item.setAttribute('tabindex', '-1');
|
||||||
for (const el of item.querySelectorAll('a, input, button')) el.setAttribute('tabindex', '-1');
|
for (const el of item.querySelectorAll('a, input, button')) el.setAttribute('tabindex', '-1');
|
||||||
}
|
}
|
||||||
@ -61,7 +61,7 @@ function updateSelectionLabel(label: HTMLElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processMenuItems($dropdown, dropdownCall) {
|
function processMenuItems($dropdown: any, dropdownCall: any) {
|
||||||
const hideEmptyDividers = dropdownCall('setting', 'hideDividers') === 'empty';
|
const hideEmptyDividers = dropdownCall('setting', 'hideDividers') === 'empty';
|
||||||
const itemsMenu = $dropdown[0].querySelector('.scrolling.menu') || $dropdown[0].querySelector('.menu');
|
const itemsMenu = $dropdown[0].querySelector('.scrolling.menu') || $dropdown[0].querySelector('.menu');
|
||||||
if (hideEmptyDividers) hideScopedEmptyDividers(itemsMenu);
|
if (hideEmptyDividers) hideScopedEmptyDividers(itemsMenu);
|
||||||
@ -143,7 +143,7 @@ function attachStaticElements(dropdown: HTMLElement, focusable: HTMLElement, men
|
|||||||
$(menu).find('> .item').each((_, item) => updateMenuItem(dropdown, item));
|
$(menu).find('> .item').each((_, item) => updateMenuItem(dropdown, item));
|
||||||
|
|
||||||
// this role could only be changed after its content is ready, otherwise some browsers+readers (like Chrome+AppleVoice) crash
|
// this role could only be changed after its content is ready, otherwise some browsers+readers (like Chrome+AppleVoice) crash
|
||||||
menu.setAttribute('role', dropdown[ariaPatchKey].listPopupRole);
|
menu.setAttribute('role', (dropdown as any)[ariaPatchKey].listPopupRole);
|
||||||
|
|
||||||
// prepare selection label items
|
// prepare selection label items
|
||||||
for (const label of dropdown.querySelectorAll<HTMLElement>('.ui.label')) {
|
for (const label of dropdown.querySelectorAll<HTMLElement>('.ui.label')) {
|
||||||
@ -151,8 +151,8 @@ function attachStaticElements(dropdown: HTMLElement, focusable: HTMLElement, men
|
|||||||
}
|
}
|
||||||
|
|
||||||
// make the primary element (focusable) aria-friendly
|
// make the primary element (focusable) aria-friendly
|
||||||
focusable.setAttribute('role', focusable.getAttribute('role') ?? dropdown[ariaPatchKey].focusableRole);
|
focusable.setAttribute('role', focusable.getAttribute('role') ?? (dropdown as any)[ariaPatchKey].focusableRole);
|
||||||
focusable.setAttribute('aria-haspopup', dropdown[ariaPatchKey].listPopupRole);
|
focusable.setAttribute('aria-haspopup', (dropdown as any)[ariaPatchKey].listPopupRole);
|
||||||
focusable.setAttribute('aria-controls', menu.id);
|
focusable.setAttribute('aria-controls', menu.id);
|
||||||
focusable.setAttribute('aria-expanded', 'false');
|
focusable.setAttribute('aria-expanded', 'false');
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ function attachStaticElements(dropdown: HTMLElement, focusable: HTMLElement, men
|
|||||||
}
|
}
|
||||||
|
|
||||||
function attachInit(dropdown: HTMLElement) {
|
function attachInit(dropdown: HTMLElement) {
|
||||||
dropdown[ariaPatchKey] = {};
|
(dropdown as any)[ariaPatchKey] = {};
|
||||||
if (dropdown.classList.contains('custom')) return;
|
if (dropdown.classList.contains('custom')) return;
|
||||||
|
|
||||||
// Dropdown has 2 different focusing behaviors
|
// Dropdown has 2 different focusing behaviors
|
||||||
@ -204,9 +204,9 @@ function attachInit(dropdown: HTMLElement) {
|
|||||||
// Since #19861 we have prepared the "combobox" solution, but didn't get enough time to put it into practice and test before.
|
// Since #19861 we have prepared the "combobox" solution, but didn't get enough time to put it into practice and test before.
|
||||||
const isComboBox = dropdown.querySelectorAll('input').length > 0;
|
const isComboBox = dropdown.querySelectorAll('input').length > 0;
|
||||||
|
|
||||||
dropdown[ariaPatchKey].focusableRole = isComboBox ? 'combobox' : 'menu';
|
(dropdown as any)[ariaPatchKey].focusableRole = isComboBox ? 'combobox' : 'menu';
|
||||||
dropdown[ariaPatchKey].listPopupRole = isComboBox ? 'listbox' : '';
|
(dropdown as any)[ariaPatchKey].listPopupRole = isComboBox ? 'listbox' : '';
|
||||||
dropdown[ariaPatchKey].listItemRole = isComboBox ? 'option' : 'menuitem';
|
(dropdown as any)[ariaPatchKey].listItemRole = isComboBox ? 'option' : 'menuitem';
|
||||||
|
|
||||||
attachDomEvents(dropdown, focusable, menu);
|
attachDomEvents(dropdown, focusable, menu);
|
||||||
attachStaticElements(dropdown, focusable, menu);
|
attachStaticElements(dropdown, focusable, menu);
|
||||||
@ -229,7 +229,7 @@ function attachDomEvents(dropdown: HTMLElement, focusable: HTMLElement, menu: HT
|
|||||||
// if the popup is visible and has an active/selected item, use its id as aria-activedescendant
|
// if the popup is visible and has an active/selected item, use its id as aria-activedescendant
|
||||||
if (menuVisible) {
|
if (menuVisible) {
|
||||||
focusable.setAttribute('aria-activedescendant', active.id);
|
focusable.setAttribute('aria-activedescendant', active.id);
|
||||||
} else if (dropdown[ariaPatchKey].listPopupRole === 'menu') {
|
} else if ((dropdown as any)[ariaPatchKey].listPopupRole === 'menu') {
|
||||||
// for menu, when the popup is hidden, no need to keep the aria-activedescendant, and clear the active/selected item
|
// for menu, when the popup is hidden, no need to keep the aria-activedescendant, and clear the active/selected item
|
||||||
focusable.removeAttribute('aria-activedescendant');
|
focusable.removeAttribute('aria-activedescendant');
|
||||||
active.classList.remove('active', 'selected');
|
active.classList.remove('active', 'selected');
|
||||||
@ -253,7 +253,7 @@ function attachDomEvents(dropdown: HTMLElement, focusable: HTMLElement, menu: HT
|
|||||||
// when the popup is hiding, it's better to have a small "delay", because there is a Fomantic UI animation
|
// when the popup is hiding, it's better to have a small "delay", because there is a Fomantic UI animation
|
||||||
// without the delay for hiding, the UI will be somewhat laggy and sometimes may get stuck in the animation.
|
// without the delay for hiding, the UI will be somewhat laggy and sometimes may get stuck in the animation.
|
||||||
const deferredRefreshAriaActiveItem = (delay = 0) => { setTimeout(refreshAriaActiveItem, delay) };
|
const deferredRefreshAriaActiveItem = (delay = 0) => { setTimeout(refreshAriaActiveItem, delay) };
|
||||||
dropdown[ariaPatchKey].deferredRefreshAriaActiveItem = deferredRefreshAriaActiveItem;
|
(dropdown as any)[ariaPatchKey].deferredRefreshAriaActiveItem = deferredRefreshAriaActiveItem;
|
||||||
dropdown.addEventListener('keyup', (e) => { if (e.key.startsWith('Arrow')) deferredRefreshAriaActiveItem(); });
|
dropdown.addEventListener('keyup', (e) => { if (e.key.startsWith('Arrow')) deferredRefreshAriaActiveItem(); });
|
||||||
|
|
||||||
// if the dropdown has been opened by focus, do not trigger the next click event again.
|
// if the dropdown has been opened by focus, do not trigger the next click event again.
|
||||||
@ -363,7 +363,7 @@ function onResponseKeepSelectedItem(dropdown: typeof $|HTMLElement, selectedValu
|
|||||||
// then the dropdown only shows other items and will select another (wrong) one.
|
// then the dropdown only shows other items and will select another (wrong) one.
|
||||||
// It can't be easily fix by using setTimeout(patch, 0) in `onResponse` because the `onResponse` is called before another `setTimeout(..., timeLeft)`
|
// It can't be easily fix by using setTimeout(patch, 0) in `onResponse` because the `onResponse` is called before another `setTimeout(..., timeLeft)`
|
||||||
// Fortunately, the "timeLeft" is controlled by "loadingDuration" which is always zero at the moment, so we can use `setTimeout(..., 10)`
|
// Fortunately, the "timeLeft" is controlled by "loadingDuration" which is always zero at the moment, so we can use `setTimeout(..., 10)`
|
||||||
const elDropdown = (dropdown instanceof HTMLElement) ? dropdown : dropdown[0];
|
const elDropdown = (dropdown instanceof HTMLElement) ? dropdown : (dropdown as any)[0];
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
queryElems(elDropdown, `.menu .item[data-value="${CSS.escape(selectedValue)}"].filtered`, (el) => el.classList.remove('filtered'));
|
queryElems(elDropdown, `.menu .item[data-value="${CSS.escape(selectedValue)}"].filtered`, (el) => el.classList.remove('filtered'));
|
||||||
$(elDropdown).dropdown('set selected', selectedValue ?? '');
|
$(elDropdown).dropdown('set selected', selectedValue ?? '');
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {showInfoToast, showWarningToast, showErrorToast} from '../modules/toast.ts';
|
import {showInfoToast, showWarningToast, showErrorToast} from '../modules/toast.ts';
|
||||||
|
|
||||||
function initDevtestToast() {
|
function initDevtestToast() {
|
||||||
const levelMap = {info: showInfoToast, warning: showWarningToast, error: showErrorToast};
|
const levelMap: Record<string, any> = {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');
|
||||||
|
@ -208,7 +208,7 @@ export const SvgIcon = defineComponent({
|
|||||||
let {svgOuter, svgInnerHtml} = svgParseOuterInner(this.name);
|
let {svgOuter, svgInnerHtml} = svgParseOuterInner(this.name);
|
||||||
// https://vuejs.org/guide/extras/render-function.html#creating-vnodes
|
// https://vuejs.org/guide/extras/render-function.html#creating-vnodes
|
||||||
// the `^` is used for attr, set SVG attributes like 'width', `aria-hidden`, `viewBox`, etc
|
// the `^` is used for attr, set SVG attributes like 'width', `aria-hidden`, `viewBox`, etc
|
||||||
const attrs = {};
|
const attrs: Record<string, any> = {};
|
||||||
for (const attr of svgOuter.attributes) {
|
for (const attr of svgOuter.attributes) {
|
||||||
if (attr.name === 'class') continue;
|
if (attr.name === 'class') continue;
|
||||||
attrs[`^${attr.name}`] = attr.value;
|
attrs[`^${attr.name}`] = attr.value;
|
||||||
|
@ -22,6 +22,8 @@ export type Config = {
|
|||||||
i18n: Record<string, string>,
|
i18n: Record<string, string>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IntervalId = ReturnType<typeof setInterval>;
|
||||||
|
|
||||||
export type Intent = 'error' | 'warning' | 'info';
|
export type Intent = 'error' | 'warning' | 'info';
|
||||||
|
|
||||||
export type RequestData = string | FormData | URLSearchParams | Record<string, any>;
|
export type RequestData = string | FormData | URLSearchParams | Record<string, any>;
|
||||||
|
@ -166,10 +166,10 @@ export function sleep(ms: number): Promise<void> {
|
|||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isImageFile({name, type}: {name: string, type?: string}): boolean {
|
export function isImageFile({name, type}: {name?: string, type?: string}): boolean {
|
||||||
return /\.(avif|jpe?g|png|gif|webp|svg|heic)$/i.test(name || '') || type?.startsWith('image/');
|
return /\.(avif|jpe?g|png|gif|webp|svg|heic)$/i.test(name || '') || type?.startsWith('image/');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isVideoFile({name, type}: {name: string, type?: string}): boolean {
|
export function isVideoFile({name, type}: {name?: string, type?: string}): boolean {
|
||||||
return /\.(mpe?g|mp4|mkv|webm)$/i.test(name || '') || type?.startsWith('video/');
|
return /\.(mpe?g|mp4|mkv|webm)$/i.test(name || '') || type?.startsWith('video/');
|
||||||
}
|
}
|
||||||
|
@ -255,12 +255,12 @@ export function loadElem(el: LoadableElement, src: string) {
|
|||||||
// it can't use other transparent polyfill patches because PaleMoon also doesn't support "addEventListener(capture)"
|
// it can't use other transparent polyfill patches because PaleMoon also doesn't support "addEventListener(capture)"
|
||||||
const needSubmitEventPolyfill = typeof SubmitEvent === 'undefined';
|
const needSubmitEventPolyfill = typeof SubmitEvent === 'undefined';
|
||||||
|
|
||||||
export function submitEventSubmitter(e) {
|
export function submitEventSubmitter(e: any) {
|
||||||
e = e.originalEvent ?? e; // if the event is wrapped by jQuery, use "originalEvent", otherwise, use the event itself
|
e = e.originalEvent ?? e; // if the event is wrapped by jQuery, use "originalEvent", otherwise, use the event itself
|
||||||
return needSubmitEventPolyfill ? (e.target._submitter || null) : e.submitter;
|
return needSubmitEventPolyfill ? (e.target._submitter || null) : e.submitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitEventPolyfillListener(e) {
|
function submitEventPolyfillListener(e: DOMEvent<Event>) {
|
||||||
const form = e.target.closest('form');
|
const form = e.target.closest('form');
|
||||||
if (!form) return;
|
if (!form) return;
|
||||||
form._submitter = e.target.closest('button:not([type]), button[type="submit"], input[type="submit"]');
|
form._submitter = e.target.closest('button:not([type]), button[type="submit"], input[type="submit"]');
|
||||||
|
@ -4,7 +4,7 @@ const pngNoPhys = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA
|
|||||||
const pngPhys = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAEElEQVQI12OQNZcAIgYIBQAL8gGxdzzM0A==';
|
const pngPhys = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAEElEQVQI12OQNZcAIgYIBQAL8gGxdzzM0A==';
|
||||||
const pngEmpty = 'data:image/png;base64,';
|
const pngEmpty = 'data:image/png;base64,';
|
||||||
|
|
||||||
async function dataUriToBlob(datauri) {
|
async function dataUriToBlob(datauri: string) {
|
||||||
return await (await globalThis.fetch(datauri)).blob();
|
return await (await globalThis.fetch(datauri)).blob();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ export type DayDataObject = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function fillEmptyStartDaysWithZeroes(startDays: number[], data: DayDataObject): DayData[] {
|
export function fillEmptyStartDaysWithZeroes(startDays: number[], data: DayDataObject): DayData[] {
|
||||||
const result = {};
|
const result: Record<string, any> = {};
|
||||||
|
|
||||||
for (const startDay of startDays) {
|
for (const startDay of startDays) {
|
||||||
result[startDay] = data[startDay] || {'week': startDay, 'additions': 0, 'deletions': 0, 'commits': 0};
|
result[startDay] = data[startDay] || {'week': startDay, 'additions': 0, 'deletions': 0, 'commits': 0};
|
||||||
|
@ -15,7 +15,7 @@ window.customElements.define('absolute-date', class extends HTMLElement {
|
|||||||
initialized = false;
|
initialized = false;
|
||||||
|
|
||||||
update = () => {
|
update = () => {
|
||||||
const opt: Intl.DateTimeFormatOptions = {};
|
const opt: Record<string, string> = {};
|
||||||
for (const attr of ['year', 'month', 'weekday', 'day']) {
|
for (const attr of ['year', 'month', 'weekday', 'day']) {
|
||||||
if (this.getAttribute(attr)) opt[attr] = this.getAttribute(attr);
|
if (this.getAttribute(attr)) opt[attr] = this.getAttribute(attr);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user