You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

repo-diff.js 7.6KB

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