merge main

This commit is contained in:
Kerwin Bryant
2025-01-10 01:57:19 +00:00
211 changed files with 3789 additions and 3048 deletions
+5 -4
View File
@@ -336,8 +336,13 @@ a.label,
border-color: var(--color-secondary);
}
.ui.dropdown .menu > .header {
text-transform: none; /* reset fomantic's "uppercase" */
}
.ui.dropdown .menu > .header:not(.ui) {
color: var(--color-text);
font-size: 0.95em; /* reset fomantic's small font-size */
}
.ui.dropdown .menu > .item {
@@ -691,10 +696,6 @@ input:-webkit-autofill:active,
box-shadow: 0 6px 18px var(--color-shadow) !important;
}
.ui.dropdown .menu > .header {
font-size: 0.8em;
}
.ui .text.left {
text-align: left !important;
}
+1
View File
@@ -155,6 +155,7 @@ textarea:focus,
}
.ui.form.left-right-form .inline.field > .help {
display: block;
margin-left: calc(250px + 15px);
}
+3
View File
@@ -11,6 +11,9 @@
.ui.fluid.container {
width: 100%;
}
.ui.container.medium-width {
width: 800px;
}
.ui[class*="center aligned"].container {
text-align: center;
+2 -1
View File
@@ -48,7 +48,8 @@
align-items: stretch;
}
/* hide all items */
#navbar .item {
#navbar .navbar-left > .item,
#navbar .navbar-right > .item {
display: none;
}
#navbar #navbar-logo {
+9 -1
View File
@@ -8,6 +8,7 @@ type File = {
NameHash: string;
Type: number;
IsViewed: boolean;
IsSubmodule: boolean;
}
type Item = {
@@ -34,6 +35,13 @@ function getIconForDiffType(pType) {
};
return diffTypes[pType];
}
function fileIcon(file) {
if (file.IsSubmodule) {
return 'octicon-file-submodule';
}
return 'octicon-file';
}
</script>
<template>
@@ -44,7 +52,7 @@ function getIconForDiffType(pType) {
:title="item.name" :href="'#diff-' + item.file.NameHash"
>
<!-- file -->
<SvgIcon name="octicon-file"/>
<SvgIcon :name="fileIcon(item.file)"/>
<span class="gt-ellipsis tw-flex-1">{{ item.name }}</span>
<SvgIcon :name="getIconForDiffType(item.file.Type).name" :class="getIconForDiffType(item.file.Type).classes"/>
</a>
@@ -129,7 +129,7 @@ function clearMergeMessage() {
{{ mergeForm.textCancel }}
</button>
<div class="ui checkbox tw-ml-1" v-if="mergeForm.isPullBranchDeletable && !autoMergeWhenSucceed">
<div class="ui checkbox tw-ml-1" v-if="mergeForm.isPullBranchDeletable">
<input name="delete_branch_after_merge" type="checkbox" v-model="deleteBranchAfterMerge" id="delete-branch-after-merge">
<label for="delete-branch-after-merge">{{ mergeForm.textDeleteBranch }}</label>
</div>
+2 -1
View File
@@ -17,7 +17,8 @@ export function initGlobalDeleteButton(): void {
// Some model/form elements will be filled by `data-id` / `data-name` / `data-data-xxx` attributes.
// If there is a form defined by `data-form`, then the form will be submitted as-is (without any modification).
// If there is no form, then the data will be posted to `data-url`.
// TODO: it's not encouraged to use this method. `show-modal` does far better than this.
// TODO: do not use this method in new code. `show-modal` / `link-action(data-modal-confirm)` does far better than this.
// FIXME: all legacy `delete-button` should be refactored to use `show-modal` or `link-action`
for (const btn of document.querySelectorAll<HTMLElement>('.delete-button')) {
btn.addEventListener('click', (e) => {
e.preventDefault();
+3 -3
View File
@@ -1,6 +1,5 @@
import $ from 'jquery';
import {GET} from '../modules/fetch.ts';
import {toggleElem, type DOMEvent} from '../utils/dom.ts';
import {toggleElem, type DOMEvent, createElementFromHTML} from '../utils/dom.ts';
import {logoutFromWorker} from '../modules/worker.ts';
const {appSubUrl, notificationSettings, assetVersionEncoded} = window.config;
@@ -158,7 +157,8 @@ async function updateNotificationTable() {
}
const data = await response.text();
if ($(data).data('sequence-number') === notificationSequenceNumber) {
const el = createElementFromHTML(data);
if (parseInt(el.getAttribute('data-sequence-number')) === notificationSequenceNumber) {
notificationDiv.outerHTML = data;
initNotificationsTable();
}
+22 -17
View File
@@ -1,35 +1,34 @@
import $ from 'jquery';
import {hideElem, showElem} from '../utils/dom.ts';
import {queryElems, toggleElem} from '../utils/dom.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
const {appSubUrl} = window.config;
export function initOrgTeamSettings() {
// Change team access mode
$('.organization.new.team input[name=permission]').on('change', () => {
const val = $('input[name=permission]:checked', '.organization.new.team').val();
if (val === 'admin') {
hideElem('.organization.new.team .team-units');
} else {
showElem('.organization.new.team .team-units');
}
});
function initOrgTeamSettings() {
// on the page "page-content organization new team"
const pageContent = document.querySelector('.page-content.organization.new.team');
if (!pageContent) return;
queryElems(pageContent, 'input[name=permission]', (el) => el.addEventListener('change', () => {
// Change team access mode
const val = pageContent.querySelector<HTMLInputElement>('input[name=permission]:checked')?.value;
toggleElem(pageContent.querySelectorAll('.team-units'), val !== 'admin');
}));
}
export function initOrgTeamSearchRepoBox() {
const $searchRepoBox = $('#search-repo-box');
function initOrgTeamSearchRepoBox() {
// on the page "page-content organization teams"
const $searchRepoBox = fomanticQuery('#search-repo-box');
$searchRepoBox.search({
minCharacters: 2,
apiSettings: {
url: `${appSubUrl}/repo/search?q={query}&uid=${$searchRepoBox.data('uid')}`,
onResponse(response) {
const items = [];
$.each(response.data, (_i, item) => {
for (const item of response.data) {
items.push({
title: item.repository.full_name.split('/')[1],
description: item.repository.full_name,
});
});
}
return {results: items};
},
},
@@ -37,3 +36,9 @@ export function initOrgTeamSearchRepoBox() {
showNoResults: false,
});
}
export function initOrgTeam() {
if (!document.querySelector('.page-content.organization')) return;
initOrgTeamSettings();
initOrgTeamSearchRepoBox();
}
-17
View File
@@ -1,17 +0,0 @@
import {singleAnchorRegex, rangeAnchorRegex} from './repo-code.ts';
test('singleAnchorRegex', () => {
expect(singleAnchorRegex.test('#L0')).toEqual(false);
expect(singleAnchorRegex.test('#L1')).toEqual(true);
expect(singleAnchorRegex.test('#L01')).toEqual(false);
expect(singleAnchorRegex.test('#n0')).toEqual(false);
expect(singleAnchorRegex.test('#n1')).toEqual(true);
expect(singleAnchorRegex.test('#n01')).toEqual(false);
});
test('rangeAnchorRegex', () => {
expect(rangeAnchorRegex.test('#L0-L10')).toEqual(false);
expect(rangeAnchorRegex.test('#L1-L10')).toEqual(true);
expect(rangeAnchorRegex.test('#L01-L10')).toEqual(false);
expect(rangeAnchorRegex.test('#L1-L01')).toEqual(false);
});
+57 -101
View File
@@ -1,12 +1,8 @@
import $ from 'jquery';
import {svg} from '../svg.ts';
import {invertFileFolding} from './file-fold.ts';
import {createTippy} from '../modules/tippy.ts';
import {clippie} from 'clippie';
import {toAbsoluteUrl} from '../utils.ts';
export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/;
export const rangeAnchorRegex = /^#(L[1-9][0-9]*)-(L[1-9][0-9]*)$/;
import {addDelegatedEventListener} from '../utils/dom.ts';
function changeHash(hash: string) {
if (window.history.pushState) {
@@ -16,20 +12,11 @@ function changeHash(hash: string) {
}
}
function isBlame() {
return Boolean(document.querySelector('div.blame'));
}
// it selects the code lines defined by range: `L1-L3` (3 lines) or `L2` (singe line)
function selectRange(range: string): Element {
for (const el of document.querySelectorAll('.code-view tr.active')) el.classList.remove('active');
const elLineNums = document.querySelectorAll(`.code-view td.lines-num span[data-line-number]`);
function getLineEls() {
return document.querySelectorAll(`.code-view td.lines-code${isBlame() ? '.blame-code' : ''}`);
}
function selectRange($linesEls, $selectionEndEl, $selectionStartEls?) {
for (const el of $linesEls) {
el.closest('tr').classList.remove('active');
}
// add hashchange to permalink
const refInNewIssue = document.querySelector('a.ref-in-new-issue');
const copyPermalink = document.querySelector('a.copy-line-permalink');
const viewGitBlame = document.querySelector('a.view_git_blame');
@@ -59,37 +46,30 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls?) {
copyPermalink.setAttribute('data-url', link);
};
if ($selectionStartEls) {
let a = parseInt($selectionEndEl[0].getAttribute('rel').slice(1));
let b = parseInt($selectionStartEls[0].getAttribute('rel').slice(1));
let c;
if (a !== b) {
if (a > b) {
c = a;
a = b;
b = c;
}
const classes = [];
for (let i = a; i <= b; i++) {
classes.push(`[rel=L${i}]`);
}
$linesEls.filter(classes.join(',')).each(function () {
this.closest('tr').classList.add('active');
});
changeHash(`#L${a}-L${b}`);
const rangeFields = range ? range.split('-') : [];
const start = rangeFields[0] ?? '';
if (!start) return null;
const stop = rangeFields[1] || start;
updateIssueHref(`L${a}-L${b}`);
updateViewGitBlameFragment(`L${a}-L${b}`);
updateCopyPermalinkUrl(`L${a}-L${b}`);
return;
}
// format is i.e. 'L14-L26'
let startLineNum = parseInt(start.substring(1));
let stopLineNum = parseInt(stop.substring(1));
if (startLineNum > stopLineNum) {
const tmp = startLineNum;
startLineNum = stopLineNum;
stopLineNum = tmp;
range = `${stop}-${start}`;
}
$selectionEndEl[0].closest('tr').classList.add('active');
changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`);
updateIssueHref($selectionEndEl[0].getAttribute('rel'));
updateViewGitBlameFragment($selectionEndEl[0].getAttribute('rel'));
updateCopyPermalinkUrl($selectionEndEl[0].getAttribute('rel'));
const first = elLineNums[startLineNum - 1] ?? null;
for (let i = startLineNum - 1; i <= stopLineNum - 1 && i < elLineNums.length; i++) {
elLineNums[i].closest('tr').classList.add('active');
}
changeHash(`#${range}`);
updateIssueHref(range);
updateViewGitBlameFragment(range);
updateCopyPermalinkUrl(range);
return first;
}
function showLineButton() {
@@ -103,6 +83,8 @@ function showLineButton() {
// find active row and add button
const tr = document.querySelector('.code-view tr.active');
if (!tr) return;
const td = tr.querySelector('td.lines-num');
const btn = document.createElement('button');
btn.classList.add('code-line-button', 'ui', 'basic', 'button');
@@ -128,62 +110,36 @@ function showLineButton() {
}
export function initRepoCodeView() {
if ($('.code-view .lines-num').length > 0) {
$(document).on('click', '.lines-num span', function (e) {
const linesEls = getLineEls();
const selectedEls = Array.from(linesEls).filter((el) => {
return el.matches(`[rel=${this.getAttribute('id')}]`);
});
if (!document.querySelector('.code-view .lines-num')) return;
let from;
if (e.shiftKey) {
from = Array.from(linesEls).filter((el) => {
return el.closest('tr').classList.contains('active');
});
}
selectRange($(linesEls), $(selectedEls), from ? $(from) : null);
window.getSelection().removeAllRanges();
showLineButton();
});
$(window).on('hashchange', () => {
let m = rangeAnchorRegex.exec(window.location.hash);
const $linesEls = $(getLineEls());
let $first;
if (m) {
$first = $linesEls.filter(`[rel=${m[1]}]`);
if ($first.length) {
selectRange($linesEls, $first, $linesEls.filter(`[rel=${m[2]}]`));
// show code view menu marker (don't show in blame page)
if (!isBlame()) {
showLineButton();
}
$('html, body').scrollTop($first.offset().top - 200);
return;
}
}
m = singleAnchorRegex.exec(window.location.hash);
if (m) {
$first = $linesEls.filter(`[rel=L${m[2]}]`);
if ($first.length) {
selectRange($linesEls, $first);
// show code view menu marker (don't show in blame page)
if (!isBlame()) {
showLineButton();
}
$('html, body').scrollTop($first.offset().top - 200);
}
}
}).trigger('hashchange');
}
$(document).on('click', '.fold-file', ({currentTarget}) => {
invertFileFolding(currentTarget.closest('.file-content'), currentTarget);
let selRangeStart: string;
addDelegatedEventListener(document, 'click', '.lines-num span', (el: HTMLElement, e: KeyboardEvent) => {
if (!selRangeStart || !e.shiftKey) {
selRangeStart = el.getAttribute('id');
selectRange(selRangeStart);
} else {
const selRangeStop = el.getAttribute('id');
selectRange(`${selRangeStart}-${selRangeStop}`);
}
window.getSelection().removeAllRanges();
showLineButton();
});
$(document).on('click', '.copy-line-permalink', async ({currentTarget}) => {
await clippie(toAbsoluteUrl(currentTarget.getAttribute('data-url')));
const onHashChange = () => {
if (!window.location.hash) return;
const range = window.location.hash.substring(1);
const first = selectRange(range);
if (first) {
// set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual';
first.scrollIntoView({block: 'start'});
showLineButton();
}
};
onHashChange();
window.addEventListener('hashchange', onHashChange);
addDelegatedEventListener(document, 'click', '.copy-line-permalink', (el) => {
clippie(toAbsoluteUrl(el.getAttribute('data-url')));
});
}
+5
View File
@@ -19,6 +19,7 @@ import {
import {POST, GET} from '../modules/fetch.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {createTippy} from '../modules/tippy.ts';
import {invertFileFolding} from './file-fold.ts';
const {pageData, i18n} = window.config;
@@ -244,4 +245,8 @@ export function initRepoDiffView() {
initRepoDiffFileViewToggle();
initViewedCheckboxListenerFor();
initExpandAndCollapseFilesButton();
addDelegatedEventListener(document, 'click', '.fold-file', (el) => {
invertFileFolding(el.closest('.file-content'), el);
});
}
+3 -5
View File
@@ -1,4 +1,3 @@
import $ from 'jquery';
import {POST} from '../modules/fetch.ts';
import {queryElems, toggleElem} from '../utils/dom.ts';
import {initIssueSidebarComboList} from './repo-issue-sidebar-combolist.ts';
@@ -9,9 +8,8 @@ function initBranchSelector() {
if (!elSelectBranch) return;
const urlUpdateIssueRef = elSelectBranch.getAttribute('data-url-update-issueref');
const $selectBranch = $(elSelectBranch);
const $branchMenu = $selectBranch.find('.reference-list-menu');
$branchMenu.find('.item:not(.no-select)').on('click', async function (e) {
const elBranchMenu = elSelectBranch.querySelector('.reference-list-menu');
queryElems(elBranchMenu, '.item:not(.no-select)', (el) => el.addEventListener('click', async function (e) {
e.preventDefault();
const selectedValue = this.getAttribute('data-id'); // eg: "refs/heads/my-branch"
const selectedText = this.getAttribute('data-name'); // eg: "my-branch"
@@ -29,7 +27,7 @@ function initBranchSelector() {
document.querySelector<HTMLInputElement>(selectedHiddenSelector).value = selectedValue;
elSelectBranch.querySelector('.text-branch-name').textContent = selectedText;
}
});
}));
}
function initRepoIssueDue() {
+4 -17
View File
@@ -373,10 +373,6 @@ export async function handleReply(el) {
export function initRepoPullRequestReview() {
if (window.location.hash && window.location.hash.startsWith('#issuecomment-')) {
// set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
if (window.history.scrollRestoration !== 'manual') {
window.history.scrollRestoration = 'manual';
}
const commentDiv = document.querySelector(window.location.hash);
if (commentDiv) {
// get the name of the parent id
@@ -384,14 +380,6 @@ export function initRepoPullRequestReview() {
if (groupID && groupID.startsWith('code-comments-')) {
const id = groupID.slice(14);
const ancestorDiffBox = commentDiv.closest('.diff-file-box');
// on pages like conversation, there is no diff header
const diffHeader = ancestorDiffBox?.querySelector('.diff-file-header');
// offset is for scrolling
let offset = 30;
if (diffHeader) {
offset += $('.diff-detail-box').outerHeight() + $(diffHeader).outerHeight();
}
hideElem(`#show-outdated-${id}`);
showElem(`#code-comments-${id}, #code-preview-${id}, #hide-outdated-${id}`);
@@ -399,12 +387,11 @@ export function initRepoPullRequestReview() {
if (ancestorDiffBox?.getAttribute('data-folded') === 'true') {
setFileFolding(ancestorDiffBox, ancestorDiffBox.querySelector('.fold-file'), false);
}
window.scrollTo({
top: $(commentDiv).offset().top - offset,
behavior: 'instant',
});
}
// set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual';
// wait for a while because some elements (eg: image, editor, etc.) may change the viewport's height.
setTimeout(() => commentDiv.scrollIntoView({block: 'start'}), 100);
}
}
+1 -1
View File
@@ -107,7 +107,7 @@ function initRepoSettingsBranches() {
let matched = false;
const statusCheck = el.getAttribute('data-status-check');
for (const pattern of validPatterns) {
if (minimatch(statusCheck, pattern)) {
if (minimatch(statusCheck, pattern, {noext: true})) { // https://github.com/go-gitea/gitea/issues/33121 disable extended glob syntax
matched = true;
break;
}
+2 -3
View File
@@ -41,7 +41,7 @@ import {initUserSettings} from './features/user-settings.ts';
import {initRepoActivityTopAuthorsChart, initRepoArchiveLinks} from './features/repo-common.ts';
import {initRepoMigrationStatusChecker} from './features/repo-migrate.ts';
import {initRepoDiffView} from './features/repo-diff.ts';
import {initOrgTeamSearchRepoBox, initOrgTeamSettings} from './features/org-team.ts';
import {initOrgTeam} from './features/org-team.ts';
import {initUserAuthWebAuthn, initUserAuthWebAuthnRegister} from './features/user-auth-webauthn.ts';
import {initRepoRelease, initRepoReleaseNew} from './features/repo-release.ts';
import {initRepoEditor} from './features/repo-editor.ts';
@@ -167,8 +167,7 @@ onDomReady(() => {
initNotificationCount,
initNotificationsTable,
initOrgTeamSearchRepoBox,
initOrgTeamSettings,
initOrgTeam,
initRepoActivityTopAuthorsChart,
initRepoArchiveLinks,
+2
View File
@@ -28,6 +28,7 @@ import octiconEye from '../../public/assets/img/svg/octicon-eye.svg';
import octiconFile from '../../public/assets/img/svg/octicon-file.svg';
import octiconFileDirectoryFill from '../../public/assets/img/svg/octicon-file-directory-fill.svg';
import octiconFileDirectoryOpenFill from '../../public/assets/img/svg/octicon-file-directory-open-fill.svg';
import octiconFileSubmodule from '../../public/assets/img/svg/octicon-file-submodule.svg';
import octiconFilter from '../../public/assets/img/svg/octicon-filter.svg';
import octiconGear from '../../public/assets/img/svg/octicon-gear.svg';
import octiconGitBranch from '../../public/assets/img/svg/octicon-git-branch.svg';
@@ -104,6 +105,7 @@ const svgs = {
'octicon-file': octiconFile,
'octicon-file-directory-fill': octiconFileDirectoryFill,
'octicon-file-directory-open-fill': octiconFileDirectoryOpenFill,
'octicon-file-submodule': octiconFileSubmodule,
'octicon-filter': octiconFilter,
'octicon-gear': octiconGear,
'octicon-git-branch': octiconGitBranch,
+1
View File
@@ -50,6 +50,7 @@ test('parseIssueNewHref', () => {
expect(parseIssueNewHref('/owner/repo/issues/new?query')).toEqual({ownerName: 'owner', repoName: 'repo', pathType: 'issues'});
expect(parseIssueNewHref('/sub/owner/repo/issues/new#hash')).toEqual({ownerName: 'owner', repoName: 'repo', pathType: 'issues'});
expect(parseIssueNewHref('/sub/owner/repo/compare/feature/branch-1...fix/branch-2')).toEqual({ownerName: 'owner', repoName: 'repo', pathType: 'pulls'});
expect(parseIssueNewHref('/other')).toEqual({});
});
test('parseUrl', () => {
+1 -1
View File
@@ -40,7 +40,7 @@ export function parseIssueHref(href: string): IssuePathInfo {
export function parseIssueNewHref(href: string): IssuePathInfo {
const path = (href || '').replace(/[#?].*$/, '');
const [_, ownerName, repoName, pathTypeField] = /([^/]+)\/([^/]+)\/(issues\/new|compare\/.+\.\.\.)/.exec(path) || [];
const pathType = pathTypeField.startsWith('issues/new') ? 'issues' : 'pulls';
const pathType = pathTypeField ? (pathTypeField.startsWith('issues/new') ? 'issues' : 'pulls') : undefined;
return {ownerName, repoName, pathType};
}