code.gitea.io/gitea@v1.22.3/web_src/js/features/repo-legacy.js (about)

     1  import $ from 'jquery';
     2  import {
     3    initRepoIssueBranchSelect, initRepoIssueCodeCommentCancel, initRepoIssueCommentDelete,
     4    initRepoIssueComments, initRepoIssueDependencyDelete, initRepoIssueReferenceIssue,
     5    initRepoIssueTitleEdit, initRepoIssueWipToggle,
     6    initRepoPullRequestUpdate, updateIssuesMeta, initIssueTemplateCommentEditors, initSingleCommentEditor,
     7  } from './repo-issue.js';
     8  import {initUnicodeEscapeButton} from './repo-unicode-escape.js';
     9  import {svg} from '../svg.js';
    10  import {htmlEscape} from 'escape-goat';
    11  import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue';
    12  import {
    13    initRepoCloneLink, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown,
    14  } from './repo-common.js';
    15  import {initCitationFileCopyContent} from './citation.js';
    16  import {initCompLabelEdit} from './comp/LabelEdit.js';
    17  import {initRepoDiffConversationNav} from './repo-diff.js';
    18  import {initCompReactionSelector} from './comp/ReactionSelector.js';
    19  import {initRepoSettingBranches} from './repo-settings.js';
    20  import {initRepoPullRequestMergeForm} from './repo-issue-pr-form.js';
    21  import {initRepoPullRequestCommitStatus} from './repo-issue-pr-status.js';
    22  import {hideElem, queryElemChildren, showElem} from '../utils/dom.js';
    23  import {POST} from '../modules/fetch.js';
    24  import {initRepoIssueCommentEdit} from './repo-issue-edit.js';
    25  
    26  // if there are draft comments, confirm before reloading, to avoid losing comments
    27  function reloadConfirmDraftComment() {
    28    const commentTextareas = [
    29      document.querySelector('.edit-content-zone:not(.tw-hidden) textarea'),
    30      document.querySelector('#comment-form textarea'),
    31    ];
    32    for (const textarea of commentTextareas) {
    33      // Most users won't feel too sad if they lose a comment with 10 chars, they can re-type these in seconds.
    34      // But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy.
    35      if (textarea && textarea.value.trim().length > 10) {
    36        textarea.parentElement.scrollIntoView();
    37        if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) {
    38          return;
    39        }
    40        break;
    41      }
    42    }
    43    window.location.reload();
    44  }
    45  
    46  export function initRepoCommentForm() {
    47    const $commentForm = $('.comment.form');
    48    if (!$commentForm.length) return;
    49  
    50    if ($commentForm.find('.field.combo-editor-dropzone').length) {
    51      // at the moment, if a form has multiple combo-markdown-editors, it must be an issue template form
    52      initIssueTemplateCommentEditors($commentForm);
    53    } else if ($commentForm.find('.combo-markdown-editor').length) {
    54      // it's quite unclear about the "comment form" elements, sometimes it's for issue comment, sometimes it's for file editor/uploader message
    55      initSingleCommentEditor($commentForm);
    56    }
    57  
    58    function initBranchSelector() {
    59      const elSelectBranch = document.querySelector('.ui.dropdown.select-branch');
    60      if (!elSelectBranch) return;
    61  
    62      const urlUpdateIssueRef = elSelectBranch.getAttribute('data-url-update-issueref');
    63      const $selectBranch = $(elSelectBranch);
    64      const $branchMenu = $selectBranch.find('.reference-list-menu');
    65      $branchMenu.find('.item:not(.no-select)').on('click', async function (e) {
    66        e.preventDefault();
    67        const selectedValue = this.getAttribute('data-id'); // eg: "refs/heads/my-branch"
    68        const selectedText = this.getAttribute('data-name'); // eg: "my-branch"
    69        if (urlUpdateIssueRef) {
    70          // for existing issue, send request to update issue ref, and reload page
    71          try {
    72            await POST(urlUpdateIssueRef, {data: new URLSearchParams({ref: selectedValue})});
    73            window.location.reload();
    74          } catch (error) {
    75            console.error(error);
    76          }
    77        } else {
    78          // for new issue, only update UI&form, do not send request/reload
    79          const selectedHiddenSelector = this.getAttribute('data-id-selector');
    80          document.querySelector(selectedHiddenSelector).value = selectedValue;
    81          elSelectBranch.querySelector('.text-branch-name').textContent = selectedText;
    82        }
    83      });
    84      $selectBranch.find('.reference.column').on('click', function () {
    85        hideElem($selectBranch.find('.scrolling.reference-list-menu'));
    86        showElem(this.getAttribute('data-target'));
    87        queryElemChildren(this.parentNode, '.branch-tag-item', (el) => el.classList.remove('active'));
    88        this.classList.add('active');
    89        return false;
    90      });
    91    }
    92  
    93    initBranchSelector();
    94  
    95    // List submits
    96    function initListSubmits(selector, outerSelector) {
    97      const $list = $(`.ui.${outerSelector}.list`);
    98      const $noSelect = $list.find('.no-select');
    99      const $listMenu = $(`.${selector} .menu`);
   100      let hasUpdateAction = $listMenu.data('action') === 'update';
   101      const items = {};
   102  
   103      $(`.${selector}`).dropdown({
   104        'action': 'nothing', // do not hide the menu if user presses Enter
   105        fullTextSearch: 'exact',
   106        async onHide() {
   107          hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
   108          if (hasUpdateAction) {
   109            // TODO: Add batch functionality and make this 1 network request.
   110            const itemEntries = Object.entries(items);
   111            for (const [elementId, item] of itemEntries) {
   112              await updateIssuesMeta(
   113                item['update-url'],
   114                item.action,
   115                item['issue-id'],
   116                elementId,
   117              );
   118            }
   119            if (itemEntries.length) {
   120              reloadConfirmDraftComment();
   121            }
   122          }
   123        },
   124      });
   125  
   126      $listMenu.find('.item:not(.no-select)').on('click', function (e) {
   127        e.preventDefault();
   128        if ($(this).hasClass('ban-change')) {
   129          return false;
   130        }
   131  
   132        hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
   133  
   134        const clickedItem = this; // eslint-disable-line unicorn/no-this-assignment
   135        const scope = this.getAttribute('data-scope');
   136  
   137        $(this).parent().find('.item').each(function () {
   138          if (scope) {
   139            // Enable only clicked item for scoped labels
   140            if (this.getAttribute('data-scope') !== scope) {
   141              return true;
   142            }
   143            if (this !== clickedItem && !$(this).hasClass('checked')) {
   144              return true;
   145            }
   146          } else if (this !== clickedItem) {
   147            // Toggle for other labels
   148            return true;
   149          }
   150  
   151          if ($(this).hasClass('checked')) {
   152            $(this).removeClass('checked');
   153            $(this).find('.octicon-check').addClass('tw-invisible');
   154            if (hasUpdateAction) {
   155              if (!($(this).data('id') in items)) {
   156                items[$(this).data('id')] = {
   157                  'update-url': $listMenu.data('update-url'),
   158                  action: 'detach',
   159                  'issue-id': $listMenu.data('issue-id'),
   160                };
   161              } else {
   162                delete items[$(this).data('id')];
   163              }
   164            }
   165          } else {
   166            $(this).addClass('checked');
   167            $(this).find('.octicon-check').removeClass('tw-invisible');
   168            if (hasUpdateAction) {
   169              if (!($(this).data('id') in items)) {
   170                items[$(this).data('id')] = {
   171                  'update-url': $listMenu.data('update-url'),
   172                  action: 'attach',
   173                  'issue-id': $listMenu.data('issue-id'),
   174                };
   175              } else {
   176                delete items[$(this).data('id')];
   177              }
   178            }
   179          }
   180        });
   181  
   182        // TODO: Which thing should be done for choosing review requests
   183        // to make chosen items be shown on time here?
   184        if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
   185          return false;
   186        }
   187  
   188        const listIds = [];
   189        $(this).parent().find('.item').each(function () {
   190          if ($(this).hasClass('checked')) {
   191            listIds.push($(this).data('id'));
   192            $($(this).data('id-selector')).removeClass('tw-hidden');
   193          } else {
   194            $($(this).data('id-selector')).addClass('tw-hidden');
   195          }
   196        });
   197        if (!listIds.length) {
   198          $noSelect.removeClass('tw-hidden');
   199        } else {
   200          $noSelect.addClass('tw-hidden');
   201        }
   202        $($(this).parent().data('id')).val(listIds.join(','));
   203        return false;
   204      });
   205      $listMenu.find('.no-select.item').on('click', function (e) {
   206        e.preventDefault();
   207        if (hasUpdateAction) {
   208          (async () => {
   209            await updateIssuesMeta(
   210              $listMenu.data('update-url'),
   211              'clear',
   212              $listMenu.data('issue-id'),
   213              '',
   214            );
   215            reloadConfirmDraftComment();
   216          })();
   217        }
   218  
   219        $(this).parent().find('.item').each(function () {
   220          $(this).removeClass('checked');
   221          $(this).find('.octicon-check').addClass('tw-invisible');
   222        });
   223  
   224        if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
   225          return false;
   226        }
   227  
   228        $list.find('.item').each(function () {
   229          $(this).addClass('tw-hidden');
   230        });
   231        $noSelect.removeClass('tw-hidden');
   232        $($(this).parent().data('id')).val('');
   233      });
   234    }
   235  
   236    // Init labels and assignees
   237    initListSubmits('select-label', 'labels');
   238    initListSubmits('select-assignees', 'assignees');
   239    initListSubmits('select-assignees-modify', 'assignees');
   240    initListSubmits('select-reviewers-modify', 'assignees');
   241  
   242    function selectItem(select_id, input_id) {
   243      const $menu = $(`${select_id} .menu`);
   244      const $list = $(`.ui${select_id}.list`);
   245      const hasUpdateAction = $menu.data('action') === 'update';
   246  
   247      $menu.find('.item:not(.no-select)').on('click', function () {
   248        $(this).parent().find('.item').each(function () {
   249          $(this).removeClass('selected active');
   250        });
   251  
   252        $(this).addClass('selected active');
   253        if (hasUpdateAction) {
   254          (async () => {
   255            await updateIssuesMeta(
   256              $menu.data('update-url'),
   257              '',
   258              $menu.data('issue-id'),
   259              $(this).data('id'),
   260            );
   261            reloadConfirmDraftComment();
   262          })();
   263        }
   264  
   265        let icon = '';
   266        if (input_id === '#milestone_id') {
   267          icon = svg('octicon-milestone', 18, 'tw-mr-2');
   268        } else if (input_id === '#project_id') {
   269          icon = svg('octicon-project', 18, 'tw-mr-2');
   270        } else if (input_id === '#assignee_id') {
   271          icon = `<img class="ui avatar image tw-mr-2" alt="avatar" src=${$(this).data('avatar')}>`;
   272        }
   273  
   274        $list.find('.selected').html(`
   275          <a class="item muted sidebar-item-link" href=${$(this).data('href')}>
   276            ${icon}
   277            ${htmlEscape($(this).text())}
   278          </a>
   279        `);
   280  
   281        $(`.ui${select_id}.list .no-select`).addClass('tw-hidden');
   282        $(input_id).val($(this).data('id'));
   283      });
   284      $menu.find('.no-select.item').on('click', function () {
   285        $(this).parent().find('.item:not(.no-select)').each(function () {
   286          $(this).removeClass('selected active');
   287        });
   288  
   289        if (hasUpdateAction) {
   290          (async () => {
   291            await updateIssuesMeta(
   292              $menu.data('update-url'),
   293              '',
   294              $menu.data('issue-id'),
   295              $(this).data('id'),
   296            );
   297            reloadConfirmDraftComment();
   298          })();
   299        }
   300  
   301        $list.find('.selected').html('');
   302        $list.find('.no-select').removeClass('tw-hidden');
   303        $(input_id).val('');
   304      });
   305    }
   306  
   307    // Milestone, Assignee, Project
   308    selectItem('.select-project', '#project_id');
   309    selectItem('.select-milestone', '#milestone_id');
   310    selectItem('.select-assignee', '#assignee_id');
   311  }
   312  
   313  export function initRepository() {
   314    if (!$('.page-content.repository').length) return;
   315  
   316    initRepoBranchTagSelector('.js-branch-tag-selector');
   317  
   318    // Options
   319    if ($('.repository.settings.options').length > 0) {
   320      // Enable or select internal/external wiki system and issue tracker.
   321      $('.enable-system').on('change', function () {
   322        if (this.checked) {
   323          $($(this).data('target')).removeClass('disabled');
   324          if (!$(this).data('context')) $($(this).data('context')).addClass('disabled');
   325        } else {
   326          $($(this).data('target')).addClass('disabled');
   327          if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled');
   328        }
   329      });
   330      $('.enable-system-radio').on('change', function () {
   331        if (this.value === 'false') {
   332          $($(this).data('target')).addClass('disabled');
   333          if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled');
   334        } else if (this.value === 'true') {
   335          $($(this).data('target')).removeClass('disabled');
   336          if ($(this).data('context') !== undefined) $($(this).data('context')).addClass('disabled');
   337        }
   338      });
   339      const $trackerIssueStyleRadios = $('.js-tracker-issue-style');
   340      $trackerIssueStyleRadios.on('change input', () => {
   341        const checkedVal = $trackerIssueStyleRadios.filter(':checked').val();
   342        $('#tracker-issue-style-regex-box').toggleClass('disabled', checkedVal !== 'regexp');
   343      });
   344    }
   345  
   346    // Labels
   347    initCompLabelEdit('.repository.labels');
   348  
   349    // Milestones
   350    if ($('.repository.new.milestone').length > 0) {
   351      $('#clear-date').on('click', () => {
   352        $('#deadline').val('');
   353        return false;
   354      });
   355    }
   356  
   357    // Repo Creation
   358    if ($('.repository.new.repo').length > 0) {
   359      $('input[name="gitignores"], input[name="license"]').on('change', () => {
   360        const gitignores = $('input[name="gitignores"]').val();
   361        const license = $('input[name="license"]').val();
   362        if (gitignores || license) {
   363          document.querySelector('input[name="auto_init"]').checked = true;
   364        }
   365      });
   366    }
   367  
   368    // Compare or pull request
   369    const $repoDiff = $('.repository.diff');
   370    if ($repoDiff.length) {
   371      initRepoCommonBranchOrTagDropdown('.choose.branch .dropdown');
   372      initRepoCommonFilterSearchDropdown('.choose.branch .dropdown');
   373    }
   374  
   375    initRepoCloneLink();
   376    initCitationFileCopyContent();
   377    initRepoSettingBranches();
   378  
   379    // Issues
   380    if ($('.repository.view.issue').length > 0) {
   381      initRepoIssueCommentEdit();
   382  
   383      initRepoIssueBranchSelect();
   384      initRepoIssueTitleEdit();
   385      initRepoIssueWipToggle();
   386      initRepoIssueComments();
   387  
   388      initRepoDiffConversationNav();
   389      initRepoIssueReferenceIssue();
   390  
   391      initRepoIssueCommentDelete();
   392      initRepoIssueDependencyDelete();
   393      initRepoIssueCodeCommentCancel();
   394      initRepoPullRequestUpdate();
   395      initCompReactionSelector();
   396  
   397      initRepoPullRequestMergeForm();
   398      initRepoPullRequestCommitStatus();
   399    }
   400  
   401    // Pull request
   402    const $repoComparePull = $('.repository.compare.pull');
   403    if ($repoComparePull.length > 0) {
   404      // show pull request form
   405      $repoComparePull.find('button.show-form').on('click', function (e) {
   406        e.preventDefault();
   407        hideElem($(this).parent());
   408  
   409        const $form = $repoComparePull.find('.pullrequest-form');
   410        showElem($form);
   411      });
   412    }
   413  
   414    initUnicodeEscapeButton();
   415  }