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