code.gitea.io/gitea@v1.21.7/web_src/js/features/repo-diff.js (about) 1 import $ from 'jquery'; 2 import {initCompReactionSelector} from './comp/ReactionSelector.js'; 3 import {initRepoIssueContentHistory} from './repo-issue-content.js'; 4 import {initDiffFileTree} from './repo-diff-filetree.js'; 5 import {initDiffCommitSelect} from './repo-diff-commitselect.js'; 6 import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.js'; 7 import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js'; 8 import {initImageDiff} from './imagediff.js'; 9 import {showErrorToast} from '../modules/toast.js'; 10 import {submitEventSubmitter} from '../utils/dom.js'; 11 12 const {csrfToken, pageData, i18n} = window.config; 13 14 function initRepoDiffReviewButton() { 15 const $reviewBox = $('#review-box'); 16 const $counter = $reviewBox.find('.review-comments-counter'); 17 18 $(document).on('click', 'button[name="pending_review"]', (e) => { 19 const $form = $(e.target).closest('form'); 20 // Watch for the form's submit event. 21 $form.on('submit', () => { 22 const num = parseInt($counter.attr('data-pending-comment-number')) + 1 || 1; 23 $counter.attr('data-pending-comment-number', num); 24 $counter.text(num); 25 // Force the browser to reflow the DOM. This is to ensure that the browser replay the animation 26 $reviewBox.removeClass('pulse'); 27 $reviewBox.width(); 28 $reviewBox.addClass('pulse'); 29 }); 30 }); 31 } 32 33 function initRepoDiffFileViewToggle() { 34 $('.file-view-toggle').on('click', function () { 35 const $this = $(this); 36 $this.parent().children().removeClass('active'); 37 $this.addClass('active'); 38 39 const $target = $($this.data('toggle-selector')); 40 $target.parent().children().addClass('gt-hidden'); 41 $target.removeClass('gt-hidden'); 42 }); 43 } 44 45 function initRepoDiffConversationForm() { 46 $(document).on('submit', '.conversation-holder form', async (e) => { 47 e.preventDefault(); 48 49 const $form = $(e.target); 50 const $textArea = $form.find('textarea'); 51 if (!validateTextareaNonEmpty($textArea)) { 52 return; 53 } 54 55 if ($form.hasClass('is-loading')) return; 56 try { 57 $form.addClass('is-loading'); 58 const formData = new FormData($form[0]); 59 60 // if the form is submitted by a button, append the button's name and value to the form data 61 const submitter = submitEventSubmitter(e); 62 const isSubmittedByButton = (submitter?.nodeName === 'BUTTON') || (submitter?.nodeName === 'INPUT' && submitter.type === 'submit'); 63 if (isSubmittedByButton && submitter.name) { 64 formData.append(submitter.name, submitter.value); 65 } 66 const formDataString = String(new URLSearchParams(formData)); 67 const $newConversationHolder = $(await $.post($form.attr('action'), formDataString)); 68 const {path, side, idx} = $newConversationHolder.data(); 69 70 $form.closest('.conversation-holder').replaceWith($newConversationHolder); 71 if ($form.closest('tr').data('line-type') === 'same') { 72 $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).addClass('gt-invisible'); 73 } else { 74 $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).addClass('gt-invisible'); 75 } 76 $newConversationHolder.find('.dropdown').dropdown(); 77 initCompReactionSelector($newConversationHolder); 78 } catch { // here the caught error might be a jQuery AJAX error (thrown by await $.post), which is not good to use for error message handling 79 showErrorToast(i18n.network_error); 80 } finally { 81 $form.removeClass('is-loading'); 82 } 83 }); 84 85 $(document).on('click', '.resolve-conversation', async function (e) { 86 e.preventDefault(); 87 const comment_id = $(this).data('comment-id'); 88 const origin = $(this).data('origin'); 89 const action = $(this).data('action'); 90 const url = $(this).data('update-url'); 91 92 const data = await $.post(url, {_csrf: csrfToken, origin, action, comment_id}); 93 94 if ($(this).closest('.conversation-holder').length) { 95 const conversation = $(data); 96 $(this).closest('.conversation-holder').replaceWith(conversation); 97 conversation.find('.dropdown').dropdown(); 98 initCompReactionSelector(conversation); 99 } else { 100 window.location.reload(); 101 } 102 }); 103 } 104 105 export function initRepoDiffConversationNav() { 106 // Previous/Next code review conversation 107 $(document).on('click', '.previous-conversation', (e) => { 108 const $conversation = $(e.currentTarget).closest('.comment-code-cloud'); 109 const $conversations = $('.comment-code-cloud:not(.gt-hidden)'); 110 const index = $conversations.index($conversation); 111 const previousIndex = index > 0 ? index - 1 : $conversations.length - 1; 112 const $previousConversation = $conversations.eq(previousIndex); 113 const anchor = $previousConversation.find('.comment').first().attr('id'); 114 window.location.href = `#${anchor}`; 115 }); 116 $(document).on('click', '.next-conversation', (e) => { 117 const $conversation = $(e.currentTarget).closest('.comment-code-cloud'); 118 const $conversations = $('.comment-code-cloud:not(.gt-hidden)'); 119 const index = $conversations.index($conversation); 120 const nextIndex = index < $conversations.length - 1 ? index + 1 : 0; 121 const $nextConversation = $conversations.eq(nextIndex); 122 const anchor = $nextConversation.find('.comment').first().attr('id'); 123 window.location.href = `#${anchor}`; 124 }); 125 } 126 127 // Will be called when the show more (files) button has been pressed 128 function onShowMoreFiles() { 129 initRepoIssueContentHistory(); 130 initViewedCheckboxListenerFor(); 131 countAndUpdateViewedFiles(); 132 initImageDiff(); 133 } 134 135 export function loadMoreFiles(url) { 136 const $target = $('a#diff-show-more-files'); 137 if ($target.hasClass('disabled') || pageData.diffFileInfo.isLoadingNewData) { 138 return; 139 } 140 141 pageData.diffFileInfo.isLoadingNewData = true; 142 $target.addClass('disabled'); 143 $.ajax({ 144 type: 'GET', 145 url, 146 }).done((resp) => { 147 const $resp = $(resp); 148 // the response is a full HTML page, we need to extract the relevant contents: 149 // 1. append the newly loaded file list items to the existing list 150 $('#diff-incomplete').replaceWith($resp.find('#diff-file-boxes').children()); 151 // 2. re-execute the script to append the newly loaded items to the JS variables to refresh the DiffFileTree 152 $('body').append($resp.find('script#diff-data-script')); 153 154 onShowMoreFiles(); 155 }).always(() => { 156 $target.removeClass('disabled'); 157 pageData.diffFileInfo.isLoadingNewData = false; 158 }); 159 } 160 161 function initRepoDiffShowMore() { 162 $(document).on('click', 'a#diff-show-more-files', (e) => { 163 e.preventDefault(); 164 165 const $target = $(e.target); 166 const linkLoadMore = $target.attr('data-href'); 167 loadMoreFiles(linkLoadMore); 168 }); 169 170 $(document).on('click', 'a.diff-load-button', (e) => { 171 e.preventDefault(); 172 const $target = $(e.target); 173 174 if ($target.hasClass('disabled')) { 175 return; 176 } 177 178 $target.addClass('disabled'); 179 180 const url = $target.data('href'); 181 $.ajax({ 182 type: 'GET', 183 url, 184 }).done((resp) => { 185 if (!resp) { 186 $target.removeClass('disabled'); 187 return; 188 } 189 $target.parent().replaceWith($(resp).find('#diff-file-boxes .diff-file-body .file-body').children()); 190 onShowMoreFiles(); 191 }).fail(() => { 192 $target.removeClass('disabled'); 193 }); 194 }); 195 } 196 197 export function initRepoDiffView() { 198 initRepoDiffConversationForm(); 199 const diffFileList = $('#diff-file-list'); 200 if (diffFileList.length === 0) return; 201 initDiffFileTree(); 202 initDiffCommitSelect(); 203 initRepoDiffShowMore(); 204 initRepoDiffReviewButton(); 205 initRepoDiffFileViewToggle(); 206 initViewedCheckboxListenerFor(); 207 initExpandAndCollapseFilesButton(); 208 }