Add reviewers selection to new pull request (#32403)

Users could add reviewers when creating new PRs.

---------

Co-authored-by: splitt3r <splitt3r@users.noreply.github.com>
Co-authored-by: Sebastian Sauer <sauer.sebastian@gmail.com>
Co-authored-by: bb-ben <70356237+bboerben@users.noreply.github.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Calvin K 2024-11-09 12:48:31 +08:00 committed by GitHub
parent d80f99ef04
commit 18aeca5320
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 503 additions and 271 deletions

View file

@ -0,0 +1,89 @@
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {POST} from '../modules/fetch.ts';
import {queryElemChildren, toggleElem} from '../utils/dom.ts';
// if there are draft comments, confirm before reloading, to avoid losing comments
export function issueSidebarReloadConfirmDraftComment() {
const commentTextareas = [
document.querySelector<HTMLTextAreaElement>('.edit-content-zone:not(.tw-hidden) textarea'),
document.querySelector<HTMLTextAreaElement>('#comment-form textarea'),
];
for (const textarea of commentTextareas) {
// Most users won't feel too sad if they lose a comment with 10 chars, they can re-type these in seconds.
// But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy.
if (textarea && textarea.value.trim().length > 10) {
textarea.parentElement.scrollIntoView();
if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) {
return;
}
break;
}
}
window.location.reload();
}
function collectCheckedValues(elDropdown: HTMLElement) {
return Array.from(elDropdown.querySelectorAll('.menu > .item.checked'), (el) => el.getAttribute('data-value'));
}
export function initIssueSidebarComboList(container: HTMLElement) {
if (!container) return;
const updateUrl = container.getAttribute('data-update-url');
const elDropdown = container.querySelector<HTMLElement>(':scope > .ui.dropdown');
const elList = container.querySelector<HTMLElement>(':scope > .ui.list');
const elComboValue = container.querySelector<HTMLInputElement>(':scope > .combo-value');
const initialValues = collectCheckedValues(elDropdown);
elDropdown.addEventListener('click', (e) => {
const elItem = (e.target as HTMLElement).closest('.item');
if (!elItem) return;
e.preventDefault();
if (elItem.getAttribute('data-can-change') !== 'true') return;
elItem.classList.toggle('checked');
elComboValue.value = collectCheckedValues(elDropdown).join(',');
});
const updateToBackend = async (changedValues) => {
let changed = false;
for (const value of initialValues) {
if (!changedValues.includes(value)) {
await POST(updateUrl, {data: new URLSearchParams({action: 'detach', id: value})});
changed = true;
}
}
for (const value of changedValues) {
if (!initialValues.includes(value)) {
await POST(updateUrl, {data: new URLSearchParams({action: 'attach', id: value})});
changed = true;
}
}
if (changed) issueSidebarReloadConfirmDraftComment();
};
const syncList = (changedValues) => {
const elEmptyTip = elList.querySelector('.item.empty-list');
queryElemChildren(elList, '.item:not(.empty-list)', (el) => el.remove());
for (const value of changedValues) {
const el = elDropdown.querySelector<HTMLElement>(`.menu > .item[data-value="${value}"]`);
const listItem = el.cloneNode(true) as HTMLElement;
listItem.querySelector('svg.octicon-check')?.remove();
elList.append(listItem);
}
const hasItems = Boolean(elList.querySelector('.item:not(.empty-list)'));
toggleElem(elEmptyTip, !hasItems);
};
fomanticQuery(elDropdown).dropdown({
action: 'nothing', // do not hide the menu if user presses Enter
fullTextSearch: 'exact',
async onHide() {
const changedValues = collectCheckedValues(elDropdown);
if (updateUrl) {
await updateToBackend(changedValues); // send requests to backend and reload the page
} else {
syncList(changedValues); // only update the list in the sidebar
}
},
});
}

View file

@ -4,26 +4,7 @@ import {updateIssuesMeta} from './repo-common.ts';
import {svg} from '../svg.ts';
import {htmlEscape} from 'escape-goat';
import {toggleElem} from '../utils/dom.ts';
// if there are draft comments, confirm before reloading, to avoid losing comments
function reloadConfirmDraftComment() {
const commentTextareas = [
document.querySelector('.edit-content-zone:not(.tw-hidden) textarea'),
document.querySelector('#comment-form textarea'),
];
for (const textarea of commentTextareas) {
// Most users won't feel too sad if they lose a comment with 10 chars, they can re-type these in seconds.
// But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy.
if (textarea && textarea.value.trim().length > 10) {
textarea.parentElement.scrollIntoView();
if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) {
return;
}
break;
}
}
window.location.reload();
}
import {initIssueSidebarComboList, issueSidebarReloadConfirmDraftComment} from './repo-issue-sidebar-combolist.ts';
function initBranchSelector() {
const elSelectBranch = document.querySelector('.ui.dropdown.select-branch');
@ -78,7 +59,7 @@ function initListSubmits(selector, outerSelector) {
);
}
if (itemEntries.length) {
reloadConfirmDraftComment();
issueSidebarReloadConfirmDraftComment();
}
}
},
@ -142,7 +123,7 @@ function initListSubmits(selector, outerSelector) {
// TODO: Which thing should be done for choosing review requests
// to make chosen items be shown on time here?
if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
if (selector === 'select-assignees-modify') {
return false;
}
@ -173,7 +154,7 @@ function initListSubmits(selector, outerSelector) {
$listMenu.data('issue-id'),
'',
);
reloadConfirmDraftComment();
issueSidebarReloadConfirmDraftComment();
})();
}
@ -182,7 +163,7 @@ function initListSubmits(selector, outerSelector) {
$(this).find('.octicon-check').addClass('tw-invisible');
});
if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
if (selector === 'select-assignees-modify') {
return false;
}
@ -213,7 +194,7 @@ function selectItem(select_id, input_id) {
$menu.data('issue-id'),
$(this).data('id'),
);
reloadConfirmDraftComment();
issueSidebarReloadConfirmDraftComment();
})();
}
@ -249,7 +230,7 @@ function selectItem(select_id, input_id) {
$menu.data('issue-id'),
$(this).data('id'),
);
reloadConfirmDraftComment();
issueSidebarReloadConfirmDraftComment();
})();
}
@ -276,14 +257,14 @@ export function initRepoIssueSidebar() {
initBranchSelector();
initRepoIssueDue();
// Init labels and assignees
// TODO: refactor the legacy initListSubmits&selectItem to initIssueSidebarComboList
initListSubmits('select-label', 'labels');
initListSubmits('select-assignees', 'assignees');
initListSubmits('select-assignees-modify', 'assignees');
initListSubmits('select-reviewers-modify', 'assignees');
// Milestone, Assignee, Project
selectItem('.select-project', '#project_id');
selectItem('.select-milestone', '#milestone_id');
selectItem('.select-assignee', '#assignee_id');
// init the combo list: a dropdown for selecting reviewers, and a list for showing selected reviewers and related actions
initIssueSidebarComboList(document.querySelector('.issue-sidebar-combo[data-sidebar-combo-for="reviewers"]'));
}

View file

@ -8,7 +8,6 @@ import {parseIssuePageInfo, toAbsoluteUrl} from '../utils.ts';
import {GET, POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {initRepoIssueSidebar} from './repo-issue-sidebar.ts';
import {updateIssuesMeta} from './repo-common.ts';
const {appSubUrl} = window.config;
@ -326,17 +325,6 @@ export function initRepoIssueWipTitle() {
export function initRepoIssueComments() {
if (!$('.repository.view.issue .timeline').length) return;
$('.re-request-review').on('click', async function (e) {
e.preventDefault();
const url = this.getAttribute('data-update-url');
const issueId = this.getAttribute('data-issue-id');
const id = this.getAttribute('data-id');
const isChecked = this.classList.contains('checked');
await updateIssuesMeta(url, isChecked ? 'detach' : 'attach', issueId, id);
window.location.reload();
});
document.addEventListener('click', (e) => {
const urlTarget = document.querySelector(':target');
if (!urlTarget) return;