mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 03:18:24 +00:00 
			
		
		
		
	Merge branch 'main' into lunny/automerge_support_delete_branch
This commit is contained in:
		
							
								
								
									
										89
									
								
								web_src/js/features/repo-issue-sidebar-combolist.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								web_src/js/features/repo-issue-sidebar-combolist.ts
									
									
									
									
									
										Normal 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 | ||||
|       } | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
| @@ -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"]')); | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -57,10 +57,21 @@ export async function renderMermaid() { | ||||
|       btn.setAttribute('data-clipboard-text', source); | ||||
|       mermaidBlock.append(btn); | ||||
|  | ||||
|       const updateIframeHeight = () => { | ||||
|         iframe.style.height = `${iframe.contentWindow.document.body.clientHeight}px`; | ||||
|       }; | ||||
|  | ||||
|       // update height when element's visibility state changes, for example when the diagram is inside | ||||
|       // a <details> + <summary> block and the <details> block becomes visible upon user interaction, it | ||||
|       // would initially set a incorrect height and the correct height is set during this callback. | ||||
|       (new IntersectionObserver(() => { | ||||
|         updateIframeHeight(); | ||||
|       }, {root: document.documentElement})).observe(iframe); | ||||
|  | ||||
|       iframe.addEventListener('load', () => { | ||||
|         pre.replaceWith(mermaidBlock); | ||||
|         mermaidBlock.classList.remove('tw-hidden'); | ||||
|         iframe.style.height = `${iframe.contentWindow.document.body.clientHeight}px`; | ||||
|         updateIframeHeight(); | ||||
|         setTimeout(() => { // avoid flash of iframe background | ||||
|           mermaidBlock.classList.remove('is-loading'); | ||||
|           iframe.classList.remove('tw-invisible'); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user