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 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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, queryElemSiblings, hideElem, showElem} from '../utils/dom.js';
  11. import {POST, GET} from '../modules/fetch.js';
  12. const {pageData, i18n} = window.config;
  13. function initRepoDiffReviewButton() {
  14. const reviewBox = document.getElementById('review-box');
  15. if (!reviewBox) return;
  16. const counter = reviewBox.querySelector('.review-comments-counter');
  17. if (!counter) return;
  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.getAttribute('data-pending-comment-number')) + 1 || 1;
  23. counter.setAttribute('data-pending-comment-number', num);
  24. counter.textContent = num;
  25. reviewBox.classList.remove('pulse');
  26. requestAnimationFrame(() => {
  27. reviewBox.classList.add('pulse');
  28. });
  29. });
  30. });
  31. }
  32. function initRepoDiffFileViewToggle() {
  33. $('.file-view-toggle').on('click', function () {
  34. for (const el of queryElemSiblings(this)) {
  35. el.classList.remove('active');
  36. }
  37. this.classList.add('active');
  38. const target = document.querySelector(this.getAttribute('data-toggle-selector'));
  39. if (!target) return;
  40. hideElem(queryElemSiblings(target));
  41. showElem(target);
  42. });
  43. }
  44. function initRepoDiffConversationForm() {
  45. $(document).on('submit', '.conversation-holder form', async (e) => {
  46. e.preventDefault();
  47. const $form = $(e.target);
  48. const textArea = e.target.querySelector('textarea');
  49. if (!validateTextareaNonEmpty(textArea)) {
  50. return;
  51. }
  52. if (e.target.classList.contains('is-loading')) return;
  53. try {
  54. e.target.classList.add('is-loading');
  55. const formData = new FormData($form[0]);
  56. // if the form is submitted by a button, append the button's name and value to the form data
  57. const submitter = submitEventSubmitter(e);
  58. const isSubmittedByButton = (submitter?.nodeName === 'BUTTON') || (submitter?.nodeName === 'INPUT' && submitter.type === 'submit');
  59. if (isSubmittedByButton && submitter.name) {
  60. formData.append(submitter.name, submitter.value);
  61. }
  62. const response = await POST(e.target.getAttribute('action'), {data: formData});
  63. const $newConversationHolder = $(await response.text());
  64. const {path, side, idx} = $newConversationHolder.data();
  65. $form.closest('.conversation-holder').replaceWith($newConversationHolder);
  66. let selector;
  67. if ($form.closest('tr').data('line-type') === 'same') {
  68. selector = `[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`;
  69. } else {
  70. selector = `[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`;
  71. }
  72. for (const el of document.querySelectorAll(selector)) {
  73. el.classList.add('tw-invisible');
  74. }
  75. $newConversationHolder.find('.dropdown').dropdown();
  76. } catch (error) {
  77. console.error('Error:', error);
  78. showErrorToast(i18n.network_error);
  79. } finally {
  80. e.target.classList.remove('is-loading');
  81. }
  82. });
  83. $(document).on('click', '.resolve-conversation', async function (e) {
  84. e.preventDefault();
  85. const comment_id = $(this).data('comment-id');
  86. const origin = $(this).data('origin');
  87. const action = $(this).data('action');
  88. const url = $(this).data('update-url');
  89. try {
  90. const response = await POST(url, {data: new URLSearchParams({origin, action, comment_id})});
  91. const data = await response.text();
  92. if ($(this).closest('.conversation-holder').length) {
  93. const $conversation = $(data);
  94. $(this).closest('.conversation-holder').replaceWith($conversation);
  95. $conversation.find('.dropdown').dropdown();
  96. initCompReactionSelector($conversation);
  97. } else {
  98. window.location.reload();
  99. }
  100. } catch (error) {
  101. console.error('Error:', error);
  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(.tw-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()[0].getAttribute('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(.tw-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()[0].getAttribute('id');
  123. window.location.href = `#${anchor}`;
  124. });
  125. }
  126. // Will be called when the show more (files) button has been pressed
  127. function onShowMoreFiles() {
  128. initRepoIssueContentHistory();
  129. initViewedCheckboxListenerFor();
  130. countAndUpdateViewedFiles();
  131. initImageDiff();
  132. }
  133. export async function loadMoreFiles(url) {
  134. const target = document.querySelector('a#diff-show-more-files');
  135. if (target?.classList.contains('disabled') || pageData.diffFileInfo.isLoadingNewData) {
  136. return;
  137. }
  138. pageData.diffFileInfo.isLoadingNewData = true;
  139. target?.classList.add('disabled');
  140. try {
  141. const response = await GET(url);
  142. const resp = await response.text();
  143. const $resp = $(resp);
  144. // the response is a full HTML page, we need to extract the relevant contents:
  145. // 1. append the newly loaded file list items to the existing list
  146. $('#diff-incomplete').replaceWith($resp.find('#diff-file-boxes').children());
  147. // 2. re-execute the script to append the newly loaded items to the JS variables to refresh the DiffFileTree
  148. $('body').append($resp.find('script#diff-data-script'));
  149. onShowMoreFiles();
  150. } catch (error) {
  151. console.error('Error:', error);
  152. showErrorToast('An error occurred while loading more files.');
  153. } finally {
  154. target?.classList.remove('disabled');
  155. pageData.diffFileInfo.isLoadingNewData = false;
  156. }
  157. }
  158. function initRepoDiffShowMore() {
  159. $(document).on('click', 'a#diff-show-more-files', (e) => {
  160. e.preventDefault();
  161. const linkLoadMore = e.target.getAttribute('data-href');
  162. loadMoreFiles(linkLoadMore);
  163. });
  164. $(document).on('click', 'a.diff-load-button', async (e) => {
  165. e.preventDefault();
  166. const $target = $(e.target);
  167. if (e.target.classList.contains('disabled')) {
  168. return;
  169. }
  170. e.target.classList.add('disabled');
  171. const url = $target.data('href');
  172. try {
  173. const response = await GET(url);
  174. const resp = await response.text();
  175. if (!resp) {
  176. return;
  177. }
  178. $target.parent().replaceWith($(resp).find('#diff-file-boxes .diff-file-body .file-body').children());
  179. onShowMoreFiles();
  180. } catch (error) {
  181. console.error('Error:', error);
  182. } finally {
  183. e.target.classList.remove('disabled');
  184. }
  185. });
  186. }
  187. export function initRepoDiffView() {
  188. initRepoDiffConversationForm();
  189. if (!$('#diff-file-list').length) return;
  190. initDiffFileTree();
  191. initDiffCommitSelect();
  192. initRepoDiffShowMore();
  193. initRepoDiffReviewButton();
  194. initRepoDiffFileViewToggle();
  195. initViewedCheckboxListenerFor();
  196. initExpandAndCollapseFilesButton();
  197. }