mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 03:25:11 +01:00 
			
		
		
		
	Backport #30162 by @silverwind CSS is pretty slim already and the `.ui.toggle.checkbox` sliders on admin page also still work. The only necessary JS is the one that links `input` and `label` so that it can be toggled via label. All checkboxes except the markdown ones render at `--checkbox-size: 16px` now. <img width="174" alt="Screenshot 2024-03-28 at 22 15 10" src="https://github.com/go-gitea/gitea/assets/115237/3455c1bb-166b-47e4-9847-2d20dd1f04db"> <img width="499" alt="Screenshot 2024-03-28 at 21 00 07" src="https://github.com/go-gitea/gitea/assets/115237/412be2b3-d5a0-478a-b17b-43e6bc12e8ce"> <img width="83" alt="Screenshot 2024-03-28 at 22 14 34" src="https://github.com/go-gitea/gitea/assets/115237/d8c89838-a420-4723-8c49-89405bb39474"> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: delvh <dev.lh@web.de>
		
			
				
	
	
		
			736 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			736 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import $ from 'jquery';
 | |
| import {htmlEscape} from 'escape-goat';
 | |
| import {showTemporaryTooltip, createTippy} from '../modules/tippy.js';
 | |
| import {hideElem, showElem, toggleElem} from '../utils/dom.js';
 | |
| import {setFileFolding} from './file-fold.js';
 | |
| import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js';
 | |
| import {toAbsoluteUrl} from '../utils.js';
 | |
| import {initDropzone} from './common-global.js';
 | |
| import {POST, GET} from '../modules/fetch.js';
 | |
| 
 | |
| const {appSubUrl} = window.config;
 | |
| 
 | |
| export function initRepoIssueTimeTracking() {
 | |
|   $(document).on('click', '.issue-add-time', () => {
 | |
|     $('.issue-start-time-modal').modal({
 | |
|       duration: 200,
 | |
|       onApprove() {
 | |
|         $('#add_time_manual_form').trigger('submit');
 | |
|       },
 | |
|     }).modal('show');
 | |
|     $('.issue-start-time-modal input').on('keydown', (e) => {
 | |
|       if ((e.keyCode || e.key) === 13) {
 | |
|         $('#add_time_manual_form').trigger('submit');
 | |
|       }
 | |
|     });
 | |
|   });
 | |
|   $(document).on('click', '.issue-start-time, .issue-stop-time', () => {
 | |
|     $('#toggle_stopwatch_form').trigger('submit');
 | |
|   });
 | |
|   $(document).on('click', '.issue-cancel-time', () => {
 | |
|     $('#cancel_stopwatch_form').trigger('submit');
 | |
|   });
 | |
|   $(document).on('click', 'button.issue-delete-time', function () {
 | |
|     const sel = `.issue-delete-time-modal[data-id="${$(this).data('id')}"]`;
 | |
|     $(sel).modal({
 | |
|       duration: 200,
 | |
|       onApprove() {
 | |
|         $(`${sel} form`).trigger('submit');
 | |
|       },
 | |
|     }).modal('show');
 | |
|   });
 | |
| }
 | |
| 
 | |
| async function updateDeadline(deadlineString) {
 | |
|   hideElem('#deadline-err-invalid-date');
 | |
|   document.getElementById('deadline-loader')?.classList.add('is-loading');
 | |
| 
 | |
|   let realDeadline = null;
 | |
|   if (deadlineString !== '') {
 | |
|     const newDate = Date.parse(deadlineString);
 | |
| 
 | |
|     if (Number.isNaN(newDate)) {
 | |
|       document.getElementById('deadline-loader')?.classList.remove('is-loading');
 | |
|       showElem('#deadline-err-invalid-date');
 | |
|       return false;
 | |
|     }
 | |
|     realDeadline = new Date(newDate);
 | |
|   }
 | |
| 
 | |
|   try {
 | |
|     const response = await POST(document.getElementById('update-issue-deadline-form').getAttribute('action'), {
 | |
|       data: {due_date: realDeadline},
 | |
|     });
 | |
| 
 | |
|     if (response.ok) {
 | |
|       window.location.reload();
 | |
|     } else {
 | |
|       throw new Error('Invalid response');
 | |
|     }
 | |
|   } catch (error) {
 | |
|     console.error(error);
 | |
|     document.getElementById('deadline-loader').classList.remove('is-loading');
 | |
|     showElem('#deadline-err-invalid-date');
 | |
|   }
 | |
| }
 | |
| 
 | |
| export function initRepoIssueDue() {
 | |
|   $(document).on('click', '.issue-due-edit', () => {
 | |
|     toggleElem('#deadlineForm');
 | |
|   });
 | |
|   $(document).on('click', '.issue-due-remove', () => {
 | |
|     updateDeadline('');
 | |
|   });
 | |
|   $(document).on('submit', '.issue-due-form', () => {
 | |
|     updateDeadline($('#deadlineDate').val());
 | |
|     return false;
 | |
|   });
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @param {HTMLElement} item
 | |
|  */
 | |
| function excludeLabel(item) {
 | |
|   const href = item.getAttribute('href');
 | |
|   const id = item.getAttribute('data-label-id');
 | |
| 
 | |
|   const regStr = `labels=((?:-?[0-9]+%2c)*)(${id})((?:%2c-?[0-9]+)*)&`;
 | |
|   const newStr = 'labels=$1-$2$3&';
 | |
| 
 | |
|   window.location = href.replace(new RegExp(regStr), newStr);
 | |
| }
 | |
| 
 | |
| export function initRepoIssueSidebarList() {
 | |
|   const repolink = $('#repolink').val();
 | |
|   const repoId = $('#repoId').val();
 | |
|   const crossRepoSearch = $('#crossRepoSearch').val();
 | |
|   const tp = $('#type').val();
 | |
|   let issueSearchUrl = `${appSubUrl}/${repolink}/issues/search?q={query}&type=${tp}`;
 | |
|   if (crossRepoSearch === 'true') {
 | |
|     issueSearchUrl = `${appSubUrl}/issues/search?q={query}&priority_repo_id=${repoId}&type=${tp}`;
 | |
|   }
 | |
|   $('#new-dependency-drop-list')
 | |
|     .dropdown({
 | |
|       apiSettings: {
 | |
|         url: issueSearchUrl,
 | |
|         onResponse(response) {
 | |
|           const filteredResponse = {success: true, results: []};
 | |
|           const currIssueId = $('#new-dependency-drop-list').data('issue-id');
 | |
|           // Parse the response from the api to work with our dropdown
 | |
|           $.each(response, (_i, issue) => {
 | |
|             // Don't list current issue in the dependency list.
 | |
|             if (issue.id === currIssueId) {
 | |
|               return;
 | |
|             }
 | |
|             filteredResponse.results.push({
 | |
|               name: `#${issue.number} ${htmlEscape(issue.title)
 | |
|               }<div class="text small gt-word-break">${htmlEscape(issue.repository.full_name)}</div>`,
 | |
|               value: issue.id,
 | |
|             });
 | |
|           });
 | |
|           return filteredResponse;
 | |
|         },
 | |
|         cache: false,
 | |
|       },
 | |
| 
 | |
|       fullTextSearch: true,
 | |
|     });
 | |
| 
 | |
|   $('.menu a.label-filter-item').each(function () {
 | |
|     $(this).on('click', function (e) {
 | |
|       if (e.altKey) {
 | |
|         e.preventDefault();
 | |
|         excludeLabel(this);
 | |
|       }
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   $('.menu .ui.dropdown.label-filter').on('keydown', (e) => {
 | |
|     if (e.altKey && e.keyCode === 13) {
 | |
|       const selectedItem = document.querySelector('.menu .ui.dropdown.label-filter .menu .item.selected');
 | |
|       if (selectedItem) {
 | |
|         excludeLabel(selectedItem);
 | |
|       }
 | |
|     }
 | |
|   });
 | |
|   $('.ui.dropdown.label-filter, .ui.dropdown.select-label').dropdown('setting', {'hideDividers': 'empty'}).dropdown('refreshItems');
 | |
| }
 | |
| 
 | |
| export function initRepoIssueCommentDelete() {
 | |
|   // Delete comment
 | |
|   $(document).on('click', '.delete-comment', async function () {
 | |
|     const $this = $(this);
 | |
|     if (window.confirm($this.data('locale'))) {
 | |
|       try {
 | |
|         const response = await POST($this.data('url'));
 | |
|         if (!response.ok) throw new Error('Failed to delete comment');
 | |
|         const $conversationHolder = $this.closest('.conversation-holder');
 | |
|         const $parentTimelineItem = $this.closest('.timeline-item');
 | |
|         const $parentTimelineGroup = $this.closest('.timeline-item-group');
 | |
|         // Check if this was a pending comment.
 | |
|         if ($conversationHolder.find('.pending-label').length) {
 | |
|           const counter = document.querySelector('#review-box .review-comments-counter');
 | |
|           let num = parseInt(counter?.getAttribute('data-pending-comment-number')) - 1 || 0;
 | |
|           num = Math.max(num, 0);
 | |
|           counter.setAttribute('data-pending-comment-number', num);
 | |
|           counter.textContent = String(num);
 | |
|         }
 | |
| 
 | |
|         $(`#${$this.data('comment-id')}`).remove();
 | |
|         if ($conversationHolder.length && !$conversationHolder.find('.comment').length) {
 | |
|           const path = $conversationHolder.data('path');
 | |
|           const side = $conversationHolder.data('side');
 | |
|           const idx = $conversationHolder.data('idx');
 | |
|           const lineType = $conversationHolder.closest('tr').data('line-type');
 | |
|           if (lineType === 'same') {
 | |
|             $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).removeClass('tw-invisible');
 | |
|           } else {
 | |
|             $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).removeClass('tw-invisible');
 | |
|           }
 | |
|           $conversationHolder.remove();
 | |
|         }
 | |
|         // Check if there is no review content, move the time avatar upward to avoid overlapping the content below.
 | |
|         if (!$parentTimelineGroup.find('.timeline-item.comment').length && !$parentTimelineItem.find('.conversation-holder').length) {
 | |
|           const $timelineAvatar = $parentTimelineGroup.find('.timeline-avatar');
 | |
|           $timelineAvatar.removeClass('timeline-avatar-offset');
 | |
|         }
 | |
|       } catch (error) {
 | |
|         console.error(error);
 | |
|       }
 | |
|     }
 | |
|     return false;
 | |
|   });
 | |
| }
 | |
| 
 | |
| export function initRepoIssueDependencyDelete() {
 | |
|   // Delete Issue dependency
 | |
|   $(document).on('click', '.delete-dependency-button', (e) => {
 | |
|     const id = e.currentTarget.getAttribute('data-id');
 | |
|     const type = e.currentTarget.getAttribute('data-type');
 | |
| 
 | |
|     $('.remove-dependency').modal({
 | |
|       closable: false,
 | |
|       duration: 200,
 | |
|       onApprove: () => {
 | |
|         $('#removeDependencyID').val(id);
 | |
|         $('#dependencyType').val(type);
 | |
|         $('#removeDependencyForm').trigger('submit');
 | |
|       },
 | |
|     }).modal('show');
 | |
|   });
 | |
| }
 | |
| 
 | |
| export function initRepoIssueCodeCommentCancel() {
 | |
|   // Cancel inline code comment
 | |
|   $(document).on('click', '.cancel-code-comment', (e) => {
 | |
|     const $form = $(e.currentTarget).closest('form');
 | |
|     if ($form.length > 0 && $form.hasClass('comment-form')) {
 | |
|       $form.addClass('tw-hidden');
 | |
|       showElem($form.closest('.comment-code-cloud').find('button.comment-form-reply'));
 | |
|     } else {
 | |
|       $form.closest('.comment-code-cloud').remove();
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| export function initRepoPullRequestUpdate() {
 | |
|   // Pull Request update button
 | |
|   const $pullUpdateButton = $('.update-button > button');
 | |
|   $pullUpdateButton.on('click', async function (e) {
 | |
|     e.preventDefault();
 | |
|     const $this = $(this);
 | |
|     const redirect = $this.data('redirect');
 | |
|     $this.addClass('is-loading');
 | |
|     let response;
 | |
|     try {
 | |
|       response = await POST($this.data('do'));
 | |
|     } catch (error) {
 | |
|       console.error(error);
 | |
|     } finally {
 | |
|       $this.removeClass('is-loading');
 | |
|     }
 | |
|     let data;
 | |
|     try {
 | |
|       data = await response?.json(); // the response is probably not a JSON
 | |
|     } catch (error) {
 | |
|       console.error(error);
 | |
|     }
 | |
|     if (data?.redirect) {
 | |
|       window.location.href = data.redirect;
 | |
|     } else if (redirect) {
 | |
|       window.location.href = redirect;
 | |
|     } else {
 | |
|       window.location.reload();
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   $('.update-button > .dropdown').dropdown({
 | |
|     onChange(_text, _value, $choice) {
 | |
|       const $url = $choice.data('do');
 | |
|       if ($url) {
 | |
|         $pullUpdateButton.find('.button-text').text($choice.text());
 | |
|         $pullUpdateButton.data('do', $url);
 | |
|       }
 | |
|     },
 | |
|   });
 | |
| }
 | |
| 
 | |
| export function initRepoPullRequestMergeInstruction() {
 | |
|   $('.show-instruction').on('click', () => {
 | |
|     toggleElem($('.instruct-content'));
 | |
|   });
 | |
| }
 | |
| 
 | |
| export function initRepoPullRequestAllowMaintainerEdit() {
 | |
|   const wrapper = document.getElementById('allow-edits-from-maintainers');
 | |
|   if (!wrapper) return;
 | |
| 
 | |
|   wrapper.querySelector('input[type="checkbox"]')?.addEventListener('change', async (e) => {
 | |
|     const checked = e.target.checked;
 | |
|     const url = `${wrapper.getAttribute('data-url')}/set_allow_maintainer_edit`;
 | |
|     wrapper.classList.add('is-loading');
 | |
|     e.target.disabled = true;
 | |
|     try {
 | |
|       const response = await POST(url, {data: {allow_maintainer_edit: checked}});
 | |
|       if (!response.ok) {
 | |
|         throw new Error('Failed to update maintainer edit permission');
 | |
|       }
 | |
|     } catch (error) {
 | |
|       console.error(error);
 | |
|       showTemporaryTooltip(wrapper, wrapper.getAttribute('data-prompt-error'));
 | |
|     } finally {
 | |
|       wrapper.classList.remove('is-loading');
 | |
|       e.target.disabled = false;
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| export function initRepoIssueReferenceRepositorySearch() {
 | |
|   $('.issue_reference_repository_search')
 | |
|     .dropdown({
 | |
|       apiSettings: {
 | |
|         url: `${appSubUrl}/repo/search?q={query}&limit=20`,
 | |
|         onResponse(response) {
 | |
|           const filteredResponse = {success: true, results: []};
 | |
|           $.each(response.data, (_r, repo) => {
 | |
|             filteredResponse.results.push({
 | |
|               name: htmlEscape(repo.repository.full_name),
 | |
|               value: repo.repository.full_name,
 | |
|             });
 | |
|           });
 | |
|           return filteredResponse;
 | |
|         },
 | |
|         cache: false,
 | |
|       },
 | |
|       onChange(_value, _text, $choice) {
 | |
|         const $form = $choice.closest('form');
 | |
|         if (!$form.length) return;
 | |
| 
 | |
|         $form[0].setAttribute('action', `${appSubUrl}/${_text}/issues/new`);
 | |
|       },
 | |
|       fullTextSearch: true,
 | |
|     });
 | |
| }
 | |
| 
 | |
| export function initRepoIssueWipTitle() {
 | |
|   $('.title_wip_desc > a').on('click', (e) => {
 | |
|     e.preventDefault();
 | |
| 
 | |
|     const $issueTitle = $('#issue_title');
 | |
|     $issueTitle.trigger('focus');
 | |
|     const value = $issueTitle.val().trim().toUpperCase();
 | |
| 
 | |
|     const wipPrefixes = $('.title_wip_desc').data('wip-prefixes');
 | |
|     for (const prefix of wipPrefixes) {
 | |
|       if (value.startsWith(prefix.toUpperCase())) {
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     $issueTitle.val(`${wipPrefixes[0]} ${$issueTitle.val()}`);
 | |
|   });
 | |
| }
 | |
| 
 | |
| export async function updateIssuesMeta(url, action, issue_ids, id) {
 | |
|   try {
 | |
|     const response = await POST(url, {data: new URLSearchParams({action, issue_ids, id})});
 | |
|     if (!response.ok) {
 | |
|       throw new Error('Failed to update issues meta');
 | |
|     }
 | |
|   } catch (error) {
 | |
|     console.error(error);
 | |
|   }
 | |
| }
 | |
| 
 | |
| export function initRepoIssueComments() {
 | |
|   if (!$('.repository.view.issue .timeline').length) return;
 | |
| 
 | |
|   $('.re-request-review').on('click', async function (e) {
 | |
|     e.preventDefault();
 | |
|     const url = $(this).data('update-url');
 | |
|     const issueId = $(this).data('issue-id');
 | |
|     const id = $(this).data('id');
 | |
|     const isChecked = $(this).hasClass('checked');
 | |
| 
 | |
|     await updateIssuesMeta(url, isChecked ? 'detach' : 'attach', issueId, id);
 | |
|     window.location.reload();
 | |
|   });
 | |
| 
 | |
|   document.addEventListener('click', (e) => {
 | |
|     const urlTarget = document.querySelector(':target');
 | |
|     if (!urlTarget) return;
 | |
| 
 | |
|     const urlTargetId = urlTarget.id;
 | |
|     if (!urlTargetId) return;
 | |
| 
 | |
|     if (!/^(issue|pull)(comment)?-\d+$/.test(urlTargetId)) return;
 | |
| 
 | |
|     if (!e.target.closest(`#${urlTargetId}`)) {
 | |
|       const scrollPosition = $(window).scrollTop();
 | |
|       window.location.hash = '';
 | |
|       $(window).scrollTop(scrollPosition);
 | |
|       window.history.pushState(null, null, ' ');
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| export async function handleReply($el) {
 | |
|   hideElem($el);
 | |
|   const $form = $el.closest('.comment-code-cloud').find('.comment-form');
 | |
|   $form.removeClass('tw-hidden');
 | |
| 
 | |
|   const $textarea = $form.find('textarea');
 | |
|   let editor = getComboMarkdownEditor($textarea);
 | |
|   if (!editor) {
 | |
|     // FIXME: the initialization of the dropzone is not consistent.
 | |
|     // When the page is loaded, the dropzone is initialized by initGlobalDropzone, but the editor is not initialized.
 | |
|     // When the form is submitted and partially reload, none of them is initialized.
 | |
|     const dropzone = $form.find('.dropzone')[0];
 | |
|     if (!dropzone.dropzone) initDropzone(dropzone);
 | |
|     editor = await initComboMarkdownEditor($form.find('.combo-markdown-editor'));
 | |
|   }
 | |
|   editor.focus();
 | |
|   return editor;
 | |
| }
 | |
| 
 | |
| 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
 | |
|       const groupID = commentDiv.closest('div[id^="code-comments-"]')?.getAttribute('id');
 | |
|       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();
 | |
|         }
 | |
| 
 | |
|         document.getElementById(`show-outdated-${id}`).classList.add('tw-hidden');
 | |
|         document.getElementById(`code-comments-${id}`).classList.remove('tw-hidden');
 | |
|         document.getElementById(`code-preview-${id}`).classList.remove('tw-hidden');
 | |
|         document.getElementById(`hide-outdated-${id}`).classList.remove('tw-hidden');
 | |
|         // if the comment box is folded, expand it
 | |
|         if (ancestorDiffBox.getAttribute('data-folded') === 'true') {
 | |
|           setFileFolding(ancestorDiffBox, ancestorDiffBox.querySelector('.fold-file'), false);
 | |
|         }
 | |
| 
 | |
|         window.scrollTo({
 | |
|           top: $(commentDiv).offset().top - offset,
 | |
|           behavior: 'instant',
 | |
|         });
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   $(document).on('click', '.show-outdated', function (e) {
 | |
|     e.preventDefault();
 | |
|     const id = $(this).data('comment');
 | |
|     $(this).addClass('tw-hidden');
 | |
|     $(`#code-comments-${id}`).removeClass('tw-hidden');
 | |
|     $(`#code-preview-${id}`).removeClass('tw-hidden');
 | |
|     $(`#hide-outdated-${id}`).removeClass('tw-hidden');
 | |
|   });
 | |
| 
 | |
|   $(document).on('click', '.hide-outdated', function (e) {
 | |
|     e.preventDefault();
 | |
|     const id = $(this).data('comment');
 | |
|     $(this).addClass('tw-hidden');
 | |
|     $(`#code-comments-${id}`).addClass('tw-hidden');
 | |
|     $(`#code-preview-${id}`).addClass('tw-hidden');
 | |
|     $(`#show-outdated-${id}`).removeClass('tw-hidden');
 | |
|   });
 | |
| 
 | |
|   $(document).on('click', 'button.comment-form-reply', async function (e) {
 | |
|     e.preventDefault();
 | |
|     await handleReply($(this));
 | |
|   });
 | |
| 
 | |
|   const $reviewBox = $('.review-box-panel');
 | |
|   if ($reviewBox.length === 1) {
 | |
|     const _promise = initComboMarkdownEditor($reviewBox.find('.combo-markdown-editor'));
 | |
|   }
 | |
| 
 | |
|   // The following part is only for diff views
 | |
|   if (!$('.repository.pull.diff').length) return;
 | |
| 
 | |
|   const $reviewBtn = $('.js-btn-review');
 | |
|   const $panel = $reviewBtn.parent().find('.review-box-panel');
 | |
|   const $closeBtn = $panel.find('.close');
 | |
| 
 | |
|   if ($reviewBtn.length && $panel.length) {
 | |
|     const tippy = createTippy($reviewBtn[0], {
 | |
|       content: $panel[0],
 | |
|       placement: 'bottom',
 | |
|       trigger: 'click',
 | |
|       maxWidth: 'none',
 | |
|       interactive: true,
 | |
|       hideOnClick: true,
 | |
|     });
 | |
| 
 | |
|     $closeBtn.on('click', (e) => {
 | |
|       e.preventDefault();
 | |
|       tippy.hide();
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   $(document).on('click', '.add-code-comment', async function (e) {
 | |
|     if ($(e.target).hasClass('btn-add-single')) return; // https://github.com/go-gitea/gitea/issues/4745
 | |
|     e.preventDefault();
 | |
| 
 | |
|     const isSplit = $(this).closest('.code-diff').hasClass('code-diff-split');
 | |
|     const side = $(this).data('side');
 | |
|     const idx = $(this).data('idx');
 | |
|     const path = $(this).closest('[data-path]').data('path');
 | |
|     const $tr = $(this).closest('tr');
 | |
|     const lineType = $tr.data('line-type');
 | |
| 
 | |
|     let $ntr = $tr.next();
 | |
|     if (!$ntr.hasClass('add-comment')) {
 | |
|       $ntr = $(`
 | |
|         <tr class="add-comment" data-line-type="${lineType}">
 | |
|           ${isSplit ? `
 | |
|             <td class="add-comment-left" colspan="4"></td>
 | |
|             <td class="add-comment-right" colspan="4"></td>
 | |
|           ` : `
 | |
|             <td class="add-comment-left add-comment-right" colspan="5"></td>
 | |
|           `}
 | |
|         </tr>`);
 | |
|       $tr.after($ntr);
 | |
|     }
 | |
| 
 | |
|     const $td = $ntr.find(`.add-comment-${side}`);
 | |
|     const $commentCloud = $td.find('.comment-code-cloud');
 | |
|     if (!$commentCloud.length && !$ntr.find('button[name="pending_review"]').length) {
 | |
|       try {
 | |
|         const response = await GET(this.closest('[data-new-comment-url]')?.getAttribute('data-new-comment-url'));
 | |
|         const html = await response.text();
 | |
|         $td.html(html);
 | |
|         $td.find("input[name='line']").val(idx);
 | |
|         $td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed');
 | |
|         $td.find("input[name='path']").val(path);
 | |
| 
 | |
|         initDropzone($td.find('.dropzone')[0]);
 | |
|         const editor = await initComboMarkdownEditor($td.find('.combo-markdown-editor'));
 | |
|         editor.focus();
 | |
|       } catch (error) {
 | |
|         console.error(error);
 | |
|       }
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| export function initRepoIssueReferenceIssue() {
 | |
|   // Reference issue
 | |
|   $(document).on('click', '.reference-issue', function (event) {
 | |
|     const $this = $(this);
 | |
|     const content = $(`#${$this.data('target')}`).text();
 | |
|     const poster = $this.data('poster-username');
 | |
|     const reference = toAbsoluteUrl($this.data('reference'));
 | |
|     const $modal = $($this.data('modal'));
 | |
|     $modal.find('textarea[name="content"]').val(`${content}\n\n_Originally posted by @${poster} in ${reference}_`);
 | |
|     $modal.modal('show');
 | |
| 
 | |
|     event.preventDefault();
 | |
|   });
 | |
| }
 | |
| 
 | |
| export function initRepoIssueWipToggle() {
 | |
|   // Toggle WIP
 | |
|   $('.toggle-wip a, .toggle-wip button').on('click', async (e) => {
 | |
|     e.preventDefault();
 | |
|     const toggleWip = e.currentTarget.closest('.toggle-wip');
 | |
|     const title = toggleWip.getAttribute('data-title');
 | |
|     const wipPrefix = toggleWip.getAttribute('data-wip-prefix');
 | |
|     const updateUrl = toggleWip.getAttribute('data-update-url');
 | |
| 
 | |
|     try {
 | |
|       const params = new URLSearchParams();
 | |
|       params.append('title', title?.startsWith(wipPrefix) ? title.slice(wipPrefix.length).trim() : `${wipPrefix.trim()} ${title}`);
 | |
| 
 | |
|       const response = await POST(updateUrl, {data: params});
 | |
|       if (!response.ok) {
 | |
|         throw new Error('Failed to toggle WIP status');
 | |
|       }
 | |
|       window.location.reload();
 | |
|     } catch (error) {
 | |
|       console.error(error);
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| async function pullrequest_targetbranch_change(update_url) {
 | |
|   const targetBranch = $('#pull-target-branch').data('branch');
 | |
|   const $branchTarget = $('#branch_target');
 | |
|   if (targetBranch === $branchTarget.text()) {
 | |
|     window.location.reload();
 | |
|     return false;
 | |
|   }
 | |
|   try {
 | |
|     await POST(update_url, {data: new URLSearchParams({target_branch: targetBranch})});
 | |
|   } catch (error) {
 | |
|     console.error(error);
 | |
|   } finally {
 | |
|     window.location.reload();
 | |
|   }
 | |
| }
 | |
| 
 | |
| export function initRepoIssueTitleEdit() {
 | |
|   // Edit issue title
 | |
|   const $issueTitle = $('#issue-title');
 | |
|   const $editInput = $('#edit-title-input input');
 | |
| 
 | |
|   const editTitleToggle = function () {
 | |
|     toggleElem($issueTitle);
 | |
|     toggleElem($('.not-in-edit'));
 | |
|     toggleElem($('#edit-title-input'));
 | |
|     toggleElem($('#pull-desc'));
 | |
|     toggleElem($('#pull-desc-edit'));
 | |
|     toggleElem($('.in-edit'));
 | |
|     toggleElem($('.new-issue-button'));
 | |
|     $('#issue-title-wrapper').toggleClass('edit-active');
 | |
|     $editInput[0].focus();
 | |
|     $editInput[0].select();
 | |
|     return false;
 | |
|   };
 | |
| 
 | |
|   $('#edit-title').on('click', editTitleToggle);
 | |
|   $('#cancel-edit-title').on('click', editTitleToggle);
 | |
|   $('#save-edit-title').on('click', editTitleToggle).on('click', async function () {
 | |
|     const pullrequest_target_update_url = this.getAttribute('data-target-update-url');
 | |
|     if (!$editInput.val().length || $editInput.val() === $issueTitle.text()) {
 | |
|       $editInput.val($issueTitle.text());
 | |
|       await pullrequest_targetbranch_change(pullrequest_target_update_url);
 | |
|     } else {
 | |
|       try {
 | |
|         const params = new URLSearchParams();
 | |
|         params.append('title', $editInput.val());
 | |
|         const response = await POST(this.getAttribute('data-update-url'), {data: params});
 | |
|         const data = await response.json();
 | |
|         $editInput.val(data.title);
 | |
|         $issueTitle.text(data.title);
 | |
|         if (pullrequest_target_update_url) {
 | |
|           await pullrequest_targetbranch_change(pullrequest_target_update_url); // it will reload the window
 | |
|         } else {
 | |
|           window.location.reload();
 | |
|         }
 | |
|       } catch (error) {
 | |
|         console.error(error);
 | |
|       }
 | |
|     }
 | |
|     return false;
 | |
|   });
 | |
| }
 | |
| 
 | |
| export function initRepoIssueBranchSelect() {
 | |
|   const changeBranchSelect = function () {
 | |
|     const $selectionTextField = $('#pull-target-branch');
 | |
| 
 | |
|     const baseName = $selectionTextField.data('basename');
 | |
|     const branchNameNew = $(this).data('branch');
 | |
|     const branchNameOld = $selectionTextField.data('branch');
 | |
| 
 | |
|     // Replace branch name to keep translation from HTML template
 | |
|     $selectionTextField.html($selectionTextField.html().replace(
 | |
|       `${baseName}:${branchNameOld}`,
 | |
|       `${baseName}:${branchNameNew}`,
 | |
|     ));
 | |
|     $selectionTextField.data('branch', branchNameNew); // update branch name in setting
 | |
|   };
 | |
|   $('#branch-select > .item').on('click', changeBranchSelect);
 | |
| }
 | |
| 
 | |
| export function initSingleCommentEditor($commentForm) {
 | |
|   // pages:
 | |
|   // * normal new issue/pr page, no status-button
 | |
|   // * issue/pr view page, with comment form, has status-button
 | |
|   const opts = {};
 | |
|   const statusButton = document.getElementById('status-button');
 | |
|   if (statusButton) {
 | |
|     opts.onContentChanged = (editor) => {
 | |
|       const statusText = statusButton.getAttribute(editor.value().trim() ? 'data-status-and-comment' : 'data-status');
 | |
|       statusButton.textContent = statusText;
 | |
|     };
 | |
|   }
 | |
|   initComboMarkdownEditor($commentForm.find('.combo-markdown-editor'), opts);
 | |
| }
 | |
| 
 | |
| export function initIssueTemplateCommentEditors($commentForm) {
 | |
|   // pages:
 | |
|   // * new issue with issue template
 | |
|   const $comboFields = $commentForm.find('.combo-editor-dropzone');
 | |
| 
 | |
|   const initCombo = async ($combo) => {
 | |
|     const $dropzoneContainer = $combo.find('.form-field-dropzone');
 | |
|     const $formField = $combo.find('.form-field-real');
 | |
|     const $markdownEditor = $combo.find('.combo-markdown-editor');
 | |
| 
 | |
|     const editor = await initComboMarkdownEditor($markdownEditor, {
 | |
|       onContentChanged: (editor) => {
 | |
|         $formField.val(editor.value());
 | |
|       },
 | |
|     });
 | |
| 
 | |
|     $formField.on('focus', async () => {
 | |
|       // deactivate all markdown editors
 | |
|       showElem($commentForm.find('.combo-editor-dropzone .form-field-real'));
 | |
|       hideElem($commentForm.find('.combo-editor-dropzone .combo-markdown-editor'));
 | |
|       hideElem($commentForm.find('.combo-editor-dropzone .form-field-dropzone'));
 | |
| 
 | |
|       // activate this markdown editor
 | |
|       hideElem($formField);
 | |
|       showElem($markdownEditor);
 | |
|       showElem($dropzoneContainer);
 | |
| 
 | |
|       await editor.switchToUserPreference();
 | |
|       editor.focus();
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   for (const el of $comboFields) {
 | |
|     initCombo($(el));
 | |
|   }
 | |
| }
 | |
| 
 | |
| // This function used to show and hide archived label on issue/pr
 | |
| //  page in the sidebar where we select the labels
 | |
| //  If we have any archived label tagged to issue and pr. We will show that
 | |
| //  archived label with checked classed otherwise we will hide it
 | |
| //  with the help of this function.
 | |
| //  This function runs globally.
 | |
| export function initArchivedLabelHandler() {
 | |
|   if (!document.querySelector('.archived-label-hint')) return;
 | |
|   for (const label of document.querySelectorAll('[data-is-archived]')) {
 | |
|     toggleElem(label, label.classList.contains('checked'));
 | |
|   }
 | |
| }
 |