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  }