mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-28 15:35:48 +02:00
Merge branch 'main' into feat-32257-add-comments-unchanged-lines-and-show
This commit is contained in:
Generated
+1083
-329
File diff suppressed because it is too large
Load Diff
@@ -122,6 +122,6 @@ async function linkAction(el: HTMLElement, e: Event) {
|
||||
}
|
||||
|
||||
export function initGlobalFetchAction() {
|
||||
addDelegatedEventListener(document, 'click', '.form-fetch-action', formFetchAction);
|
||||
addDelegatedEventListener(document, 'submit', '.form-fetch-action', formFetchAction);
|
||||
addDelegatedEventListener(document, 'click', '.link-action', linkAction);
|
||||
}
|
||||
|
||||
@@ -196,7 +196,11 @@ async function initIssuePinSort() {
|
||||
|
||||
createSortable(pinDiv, {
|
||||
group: 'shared',
|
||||
onEnd: pinMoveEnd, // eslint-disable-line @typescript-eslint/no-misused-promises
|
||||
onEnd: (e) => {
|
||||
(async () => {
|
||||
await pinMoveEnd(e);
|
||||
})();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import {beforeEach, describe, expect, test, vi} from 'vitest';
|
||||
import {initRepoBranchesSettings} from './repo-settings-branches.ts';
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {createSortable} from '../modules/sortable.ts';
|
||||
|
||||
vi.mock('../modules/fetch.ts', () => ({
|
||||
POST: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../modules/sortable.ts', () => ({
|
||||
createSortable: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('Repository Branch Settings', () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<div id="protected-branches-list" data-update-priority-url="some/repo/branches/priority">
|
||||
<div class="flex-item tw-items-center item" data-id="1" >
|
||||
<div class="drag-handle"></div>
|
||||
</div>
|
||||
<div class="flex-item tw-items-center item" data-id="2" >
|
||||
<div class="drag-handle"></div>
|
||||
</div>
|
||||
<div class="flex-item tw-items-center item" data-id="3" >
|
||||
<div class="drag-handle"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should initialize sortable for protected branches list', () => {
|
||||
initRepoBranchesSettings();
|
||||
|
||||
expect(createSortable).toHaveBeenCalledWith(
|
||||
document.querySelector('#protected-branches-list'),
|
||||
expect.objectContaining({
|
||||
handle: '.drag-handle',
|
||||
animation: 150,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should not initialize if protected branches list is not present', () => {
|
||||
document.body.innerHTML = '';
|
||||
|
||||
initRepoBranchesSettings();
|
||||
|
||||
expect(createSortable).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should post new order after sorting', async () => {
|
||||
vi.mocked(POST).mockResolvedValue({ok: true} as Response);
|
||||
|
||||
// Mock createSortable to capture and execute the onEnd callback
|
||||
vi.mocked(createSortable).mockImplementation((_el, options) => {
|
||||
options.onEnd();
|
||||
return {destroy: vi.fn()};
|
||||
});
|
||||
|
||||
initRepoBranchesSettings();
|
||||
|
||||
expect(POST).toHaveBeenCalledWith(
|
||||
'some/repo/branches/priority',
|
||||
expect.objectContaining({
|
||||
data: {ids: [1, 2, 3]},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import {createSortable} from '../modules/sortable.ts';
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {showErrorToast} from '../modules/toast.ts';
|
||||
import {queryElemChildren} from '../utils/dom.ts';
|
||||
|
||||
export function initRepoBranchesSettings() {
|
||||
const protectedBranchesList = document.querySelector('#protected-branches-list');
|
||||
if (!protectedBranchesList) return;
|
||||
|
||||
createSortable(protectedBranchesList, {
|
||||
handle: '.drag-handle',
|
||||
animation: 150,
|
||||
|
||||
onEnd: () => {
|
||||
(async () => {
|
||||
const itemElems = queryElemChildren(protectedBranchesList, '.item[data-id]');
|
||||
const itemIds = Array.from(itemElems, (el) => parseInt(el.getAttribute('data-id')));
|
||||
|
||||
try {
|
||||
await POST(protectedBranchesList.getAttribute('data-update-priority-url'), {
|
||||
data: {
|
||||
ids: itemIds,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
const errorMessage = String(err);
|
||||
showErrorToast(`Failed to update branch protection rule priority:, error: ${errorMessage}`);
|
||||
}
|
||||
})();
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import {minimatch} from 'minimatch';
|
||||
import {createMonaco} from './codeeditor.ts';
|
||||
import {onInputDebounce, queryElems, toggleElem} from '../utils/dom.ts';
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {initRepoBranchesSettings} from './repo-settings-branches.ts';
|
||||
|
||||
const {appSubUrl, csrfToken} = window.config;
|
||||
|
||||
@@ -154,4 +155,5 @@ export function initRepoSettings() {
|
||||
initRepoSettingsCollaboration();
|
||||
initRepoSettingsSearchTeamBox();
|
||||
initRepoSettingsGitHook();
|
||||
initRepoBranchesSettings();
|
||||
}
|
||||
|
||||
@@ -40,14 +40,15 @@ async function loginPasskey() {
|
||||
try {
|
||||
const credential = await navigator.credentials.get({
|
||||
publicKey: options.publicKey,
|
||||
});
|
||||
}) as PublicKeyCredential;
|
||||
const credResp = credential.response as AuthenticatorAssertionResponse;
|
||||
|
||||
// Move data into Arrays in case it is super long
|
||||
const authData = new Uint8Array(credential.response.authenticatorData);
|
||||
const clientDataJSON = new Uint8Array(credential.response.clientDataJSON);
|
||||
const authData = new Uint8Array(credResp.authenticatorData);
|
||||
const clientDataJSON = new Uint8Array(credResp.clientDataJSON);
|
||||
const rawId = new Uint8Array(credential.rawId);
|
||||
const sig = new Uint8Array(credential.response.signature);
|
||||
const userHandle = new Uint8Array(credential.response.userHandle);
|
||||
const sig = new Uint8Array(credResp.signature);
|
||||
const userHandle = new Uint8Array(credResp.userHandle);
|
||||
|
||||
const res = await POST(`${appSubUrl}/user/webauthn/passkey/login`, {
|
||||
data: {
|
||||
@@ -175,7 +176,7 @@ async function webauthnRegistered(newCredential) {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function webAuthnError(errorType, message) {
|
||||
function webAuthnError(errorType: string, message:string = '') {
|
||||
const elErrorMsg = document.querySelector(`#webauthn-error-msg`);
|
||||
|
||||
if (errorType === 'general') {
|
||||
@@ -207,10 +208,9 @@ function detectWebAuthnSupport() {
|
||||
}
|
||||
|
||||
export function initUserAuthWebAuthnRegister() {
|
||||
const elRegister = document.querySelector('#register-webauthn');
|
||||
if (!elRegister) {
|
||||
return;
|
||||
}
|
||||
const elRegister = document.querySelector<HTMLInputElement>('#register-webauthn');
|
||||
if (!elRegister) return;
|
||||
|
||||
if (!detectWebAuthnSupport()) {
|
||||
elRegister.disabled = true;
|
||||
return;
|
||||
@@ -222,7 +222,7 @@ export function initUserAuthWebAuthnRegister() {
|
||||
}
|
||||
|
||||
async function webAuthnRegisterRequest() {
|
||||
const elNickname = document.querySelector('#nickname');
|
||||
const elNickname = document.querySelector<HTMLInputElement>('#nickname');
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('name', elNickname.value);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {isObject} from '../utils.ts';
|
||||
import type {RequestData, RequestOpts} from '../types.ts';
|
||||
import type {RequestOpts} from '../types.ts';
|
||||
|
||||
const {csrfToken} = window.config;
|
||||
|
||||
@@ -10,7 +10,7 @@ const safeMethods = new Set(['GET', 'HEAD', 'OPTIONS', 'TRACE']);
|
||||
// which will automatically set an appropriate headers. For json content, only object
|
||||
// and array types are currently supported.
|
||||
export function request(url: string, {method = 'GET', data, headers = {}, ...other}: RequestOpts = {}): Promise<Response> {
|
||||
let body: RequestData;
|
||||
let body: string | FormData | URLSearchParams;
|
||||
let contentType: string;
|
||||
if (data instanceof FormData || data instanceof URLSearchParams) {
|
||||
body = data;
|
||||
|
||||
@@ -34,6 +34,7 @@ import octiconGitCommit from '../../public/assets/img/svg/octicon-git-commit.svg
|
||||
import octiconGitMerge from '../../public/assets/img/svg/octicon-git-merge.svg';
|
||||
import octiconGitPullRequest from '../../public/assets/img/svg/octicon-git-pull-request.svg';
|
||||
import octiconGitPullRequestDraft from '../../public/assets/img/svg/octicon-git-pull-request-draft.svg';
|
||||
import octiconGrabber from '../../public/assets/img/svg/octicon-grabber.svg';
|
||||
import octiconHeading from '../../public/assets/img/svg/octicon-heading.svg';
|
||||
import octiconHorizontalRule from '../../public/assets/img/svg/octicon-horizontal-rule.svg';
|
||||
import octiconImage from '../../public/assets/img/svg/octicon-image.svg';
|
||||
@@ -107,6 +108,7 @@ const svgs = {
|
||||
'octicon-git-merge': octiconGitMerge,
|
||||
'octicon-git-pull-request': octiconGitPullRequest,
|
||||
'octicon-git-pull-request-draft': octiconGitPullRequestDraft,
|
||||
'octicon-grabber': octiconGrabber,
|
||||
'octicon-heading': octiconHeading,
|
||||
'octicon-horizontal-rule': octiconHorizontalRule,
|
||||
'octicon-image': octiconImage,
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ export type Config = {
|
||||
|
||||
export type Intent = 'error' | 'warning' | 'info';
|
||||
|
||||
export type RequestData = string | FormData | URLSearchParams;
|
||||
export type RequestData = string | FormData | URLSearchParams | Record<string, any>;
|
||||
|
||||
export type RequestOpts = {
|
||||
data?: RequestData,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {debounce} from 'throttle-debounce';
|
||||
import type {Promisable} from 'type-fest';
|
||||
import type $ from 'jquery';
|
||||
import {isInFrontendUnitTest} from './testhelper.ts';
|
||||
|
||||
type ArrayLikeIterable<T> = ArrayLike<T> & Iterable<T>; // for NodeListOf and Array
|
||||
type ElementArg = Element | string | ArrayLikeIterable<Element> | ReturnType<typeof $>;
|
||||
@@ -76,8 +77,8 @@ export function queryElemSiblings<T extends Element>(el: Element, selector = '*'
|
||||
|
||||
// it works like jQuery.children: only the direct children are selected
|
||||
export function queryElemChildren<T extends Element>(parent: Element | ParentNode, selector = '*', fn?: ElementsCallback<T>): ArrayLikeIterable<T> {
|
||||
if (window.vitest) {
|
||||
// bypass the vitest bug: it doesn't support ":scope >"
|
||||
if (isInFrontendUnitTest()) {
|
||||
// https://github.com/capricorn86/happy-dom/issues/1620 : ":scope" doesn't work
|
||||
const selected = Array.from<T>(parent.children as any).filter((child) => child.matches(selector));
|
||||
return applyElemsCallback<T>(selected, fn);
|
||||
}
|
||||
@@ -357,6 +358,6 @@ export function addDelegatedEventListener<T extends HTMLElement, E extends Event
|
||||
parent.addEventListener(type, (e: Event) => {
|
||||
const elem = (e.target as HTMLElement).closest(selector);
|
||||
if (!elem) return;
|
||||
listener(elem as T, e);
|
||||
listener(elem as T, e as E);
|
||||
}, options);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// there could be different "testing" concepts, for example: backend's "setting.IsInTesting"
|
||||
// even if backend is in testing mode, frontend could be complied in production mode
|
||||
// so this function only checks if the frontend is in unit testing mode (usually from *.test.ts files)
|
||||
export function isInFrontendUnitTest() {
|
||||
return process.env.TEST === 'true';
|
||||
}
|
||||
Reference in New Issue
Block a user