1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 18:28:37 +00:00

Add label/author/assignee filters to the user/org home issue list (#32779)

Replace #26661, fix #25979

Not perfect, but usable and much better than before. Since it is quite
complex, I am not quite sure whether there would be any regression, if
any, I will fix in first time.

I have tested the related pages many times: issue list, milestone issue
list, project view, user issue list, org issue list.
This commit is contained in:
wxiaoguang
2024-12-11 14:33:24 +08:00
committed by GitHub
parent 734ddf7118
commit e619384098
27 changed files with 338 additions and 319 deletions

View File

@@ -90,24 +90,6 @@
left: 0;
}
.repository .filter.menu .ui.dropdown.label-filter .menu .info {
display: inline-block;
padding: 0.5rem 0;
font-size: 12px;
width: 100%;
white-space: nowrap;
margin-left: 10px;
margin-right: 8px;
text-align: left;
}
.repository .filter.menu .ui.dropdown.label-filter .menu .info code {
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius);
padding: 1px 2px;
font-size: 11px;
}
/* For the secondary pointing menu, respect its own border-bottom */
/* style reference: https://semantic-ui.com/collections/menu.html#pointing */
.repository .ui.tabs.container .ui.menu:not(.secondary.pointing) {

View File

@@ -73,3 +73,21 @@
font-size: 12px;
min-width: fit-content;
}
.label-filter-exclude-info {
display: inline-block;
padding: 0.5rem 0;
font-size: 12px;
width: 100%;
white-space: nowrap;
margin-left: 10px;
margin-right: 8px;
text-align: left;
}
.label-filter-exclude-info code {
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius);
padding: 1px 2px;
font-size: 11px;
}

View File

@@ -5,13 +5,6 @@
gap: .5rem;
}
.list-header-sort {
display: flex;
align-items: center;
padding-left: 1rem;
padding-right: 1rem;
}
.list-header-search {
display: flex;
flex: 1;
@@ -21,8 +14,22 @@
min-width: 200px; /* to enable flexbox wrapping on mobile */
}
.list-header-search .input {
.list-header-search > .ui.input {
flex: 1;
min-width: 100px !important;
}
.list-header-search > .ui.input .ui.dropdown {
min-width: auto !important;
}
.list-header-filters {
display: flex;
align-items: center;
}
.list-header-filters > .item {
padding: 5px 0 5px 10px;
}
@media (max-width: 767.98px) {
@@ -32,8 +39,7 @@
.list-header-toggle {
order: 1;
}
.list-header-sort {
.list-header-filters {
order: 2;
margin-left: auto;
}
}

View File

@@ -95,10 +95,9 @@ function initRepoIssueListCheckboxes() {
function initDropdownUserRemoteSearch(el: Element) {
let searchUrl = el.getAttribute('data-search-url');
const actionJumpUrl = el.getAttribute('data-action-jump-url');
const selectedUserId = parseInt(el.getAttribute('data-selected-user-id'));
let selectedUsername = '';
if (!searchUrl.includes('?')) searchUrl += '?';
let selectedUsername = el.getAttribute('data-selected-username') || '';
const $searchDropdown = fomanticQuery(el);
const elMenu = el.querySelector('.menu');
const elSearchInput = el.querySelector<HTMLInputElement>('.ui.search input');
const elItemFromInput = el.querySelector('.menu > .item-from-input');
@@ -110,17 +109,27 @@ function initDropdownUserRemoteSearch(el: Element) {
},
});
const selectUsername = (username: string) => {
queryElems(elMenu, '.item.active, .item.selected', (el) => el.classList.remove('active', 'selected'));
elMenu.querySelector(`.item[data-value="${CSS.escape(username)}"]`)?.classList.add('selected');
};
type ProcessedResult = {value: string, name: string};
const processedResults: ProcessedResult[] = []; // to be used by dropdown to generate menu items
const syncItemFromInput = () => {
elItemFromInput.setAttribute('data-value', elSearchInput.value);
elItemFromInput.textContent = elSearchInput.value;
toggleElem(elItemFromInput, !processedResults.length);
const inputVal = elSearchInput.value.trim();
elItemFromInput.setAttribute('data-value', inputVal);
elItemFromInput.textContent = inputVal;
const showItemFromInput = !processedResults.length && inputVal !== '';
toggleElem(elItemFromInput, showItemFromInput);
selectUsername(showItemFromInput ? inputVal : selectedUsername);
};
elSearchInput.value = selectedUsername;
if (!searchUrl) {
elSearchInput.addEventListener('input', syncItemFromInput);
} else {
if (!searchUrl.includes('?')) searchUrl += '?';
$searchDropdown.dropdown('setting', 'apiSettings', {
cache: false,
url: `${searchUrl}&q={query}`,
@@ -130,11 +139,10 @@ function initDropdownUserRemoteSearch(el: Element) {
for (const item of resp.results) {
let html = `<img class="ui avatar tw-align-middle" src="${htmlEscape(item.avatar_link)}" aria-hidden="true" alt="" width="20" height="20"><span class="gt-ellipsis">${htmlEscape(item.username)}</span>`;
if (item.full_name) html += `<span class="search-fullname tw-ml-2">${htmlEscape(item.full_name)}</span>`;
if (selectedUserId === item.user_id) selectedUsername = item.username;
if (selectedUsername.toLowerCase() === item.username.toLowerCase()) selectedUsername = item.username;
processedResults.push({value: item.username, name: html});
}
resp.results = processedResults;
syncItemFromInput();
return resp;
},
});
@@ -146,9 +154,8 @@ function initDropdownUserRemoteSearch(el: Element) {
const dropdownTemplates = $searchDropdown.dropdown('setting', 'templates');
$searchDropdown.dropdown('internal', 'setup', dropdownSetup);
dropdownSetup.menu = function (values) {
const menu = $searchDropdown.find('> .menu')[0];
// remove old dynamic items
for (const el of menu.querySelectorAll(':scope > .dynamic-item')) {
for (const el of elMenu.querySelectorAll(':scope > .dynamic-item')) {
el.remove();
}
@@ -160,16 +167,11 @@ function initDropdownUserRemoteSearch(el: Element) {
}
const div = document.createElement('div');
div.classList.add('divider', 'dynamic-item');
menu.append(div, ...newMenuItems);
elMenu.append(div, ...newMenuItems);
}
$searchDropdown.dropdown('refresh');
// defer our selection to the next tick, because dropdown will set the selection item after this `menu` function
setTimeout(() => {
for (const el of menu.querySelectorAll('.item.active, .item.selected')) {
el.classList.remove('active', 'selected');
}
menu.querySelector(`.item[data-value="${CSS.escape(selectedUsername)}"]`)?.classList.add('selected');
}, 0);
setTimeout(() => syncItemFromInput(), 0);
};
}
@@ -221,8 +223,12 @@ async function initIssuePinSort() {
}
export function initRepoIssueList() {
if (!document.querySelector('.page-content.repository.issue-list, .page-content.repository.milestone-issue-list')) return;
initRepoIssueListCheckboxes();
queryElems(document, '.ui.dropdown.user-remote-search', (el) => initDropdownUserRemoteSearch(el));
initIssuePinSort();
if (document.querySelector('.page-content.repository.issue-list, .page-content.repository.milestone-issue-list')) {
initRepoIssueListCheckboxes();
queryElems(document, '.ui.dropdown.user-remote-search', (el) => initDropdownUserRemoteSearch(el));
initIssuePinSort();
} else if (document.querySelector('.page-content.dashboard.issues')) {
// user or org home: issue list, pull request list
queryElems(document, '.ui.dropdown.user-remote-search', (el) => initDropdownUserRemoteSearch(el));
}
}