]> source.dussan.org Git - gitea.git/commitdiff
Split `index.js` to separate files (#17315)
authorwxiaoguang <wxiaoguang@gmail.com>
Sat, 16 Oct 2021 17:28:04 +0000 (01:28 +0800)
committerGitHub <noreply@github.com>
Sat, 16 Oct 2021 17:28:04 +0000 (01:28 +0800)
* split `index.js` to separate files

* tune clipboard

* fix promise

* fix document

* remove intermediate empty file

* fix async event listener

* use `export function` instead of `export {}`, add more comments

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
42 files changed:
docs/content/doc/developers/guidelines-frontend.md
templates/repo/branch/list.tmpl
web_src/js/code/linebutton.js [deleted file]
web_src/js/components/DashboardRepoList.js
web_src/js/components/RepoActivityTopAuthors.vue
web_src/js/components/RepoBranchTagDropdown.js
web_src/js/components/VueComponentLoader.js
web_src/js/features/admin-common.js [new file with mode: 0644]
web_src/js/features/admin-emails.js [new file with mode: 0644]
web_src/js/features/clipboard.js
web_src/js/features/common-global.js [new file with mode: 0644]
web_src/js/features/common-issue.js [new file with mode: 0644]
web_src/js/features/common-organization.js [new file with mode: 0644]
web_src/js/features/comp/ColorPicker.js [new file with mode: 0644]
web_src/js/features/comp/CommentSimpleMDE.js [new file with mode: 0644]
web_src/js/features/comp/ImagePaste.js [new file with mode: 0644]
web_src/js/features/comp/LabelEdit.js [new file with mode: 0644]
web_src/js/features/comp/MarkupContentPreview.js [new file with mode: 0644]
web_src/js/features/comp/ReactionSelector.js [new file with mode: 0644]
web_src/js/features/comp/SearchUserBox.js [new file with mode: 0644]
web_src/js/features/comp/WebHookEditor.js [new file with mode: 0644]
web_src/js/features/install.js [new file with mode: 0644]
web_src/js/features/org-team.js [new file with mode: 0644]
web_src/js/features/repo-branch.js [new file with mode: 0644]
web_src/js/features/repo-code.js [new file with mode: 0644]
web_src/js/features/repo-commit.js [new file with mode: 0644]
web_src/js/features/repo-common.js [new file with mode: 0644]
web_src/js/features/repo-diff.js [new file with mode: 0644]
web_src/js/features/repo-editor.js [new file with mode: 0644]
web_src/js/features/repo-home.js [new file with mode: 0644]
web_src/js/features/repo-issue.js [new file with mode: 0644]
web_src/js/features/repo-legacy.js [new file with mode: 0644]
web_src/js/features/repo-migrate.js [new file with mode: 0644]
web_src/js/features/repo-release.js [new file with mode: 0644]
web_src/js/features/repo-settings.js [new file with mode: 0644]
web_src/js/features/repo-template.js [new file with mode: 0644]
web_src/js/features/repo-wiki.js [new file with mode: 0644]
web_src/js/features/sshkey-helper.js [new file with mode: 0644]
web_src/js/features/user-auth-u2f.js [new file with mode: 0644]
web_src/js/features/user-auth.js [new file with mode: 0644]
web_src/js/features/user-settings.js [new file with mode: 0644]
web_src/js/index.js

index 86286127aa409d26aaa8d7cb87f3c133496f93ee..f30b0d1cbd8864f9f3cc585d56a881370da99823 100644 (file)
@@ -39,13 +39,6 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h
 6. The backend can pass complex data to the frontend by using `ctx.PageData["myModuleData"] = map[]{}`
 7. Simple pages and SEO-related pages use Go HTML Template render to generate static Fomantic-UI HTML output. Complex pages can use Vue2 (or Vue3 in future).
 
-## Legacy Problems and Solutions
-
-### Too much code in `web_src/index.js`
-
-Previously, most JavaScript code was written into `web_src/index.js` directly, making the file unmaintainable.
-Try to keep this file small by creating new modules instead. These modules can be put in the `web_src/js/features` directory for now.
-
 ### Vue2/Vue3 and JSX
 
 Gitea is using Vue2 now, we plan to upgrade to Vue3. We decided not to introduce JSX to keep the HTML and the JavaScript code separated.
index 1e21863ee18a97e72889b41edd56b1b7438cff09..cadf91df041c556752c09c061eb50be43dbc8e38 100644 (file)
                                                                                        {{if .IsDeleted}}
                                                                                                <a class="ui basic jump button icon poping up undo-button" href data-url="{{$.Link}}/restore?branch_id={{.DeletedBranch.ID | urlquery}}&name={{.DeletedBranch.Name | urlquery}}" data-content="{{$.i18n.Tr "repo.branch.restore" (.Name)}}" data-variation="tiny inverted" data-position="top right"><span class="text blue">{{svg "octicon-reply"}}</span></a>
                                                                                        {{else}}
-                                                                                               <a class="ui basic jump button icon poping up delete-branch-button" href data-url="{{$.Link}}/delete?name={{.Name | urlquery}}" data-content="{{$.i18n.Tr "repo.branch.delete" (.Name)}}" data-variation="tiny inverted" data-position="top right" data-name="{{.Name}}">
+                                                                                               <a class="ui basic jump button icon poping up delete-button delete-branch-button" href data-url="{{$.Link}}/delete?name={{.Name | urlquery}}" data-content="{{$.i18n.Tr "repo.branch.delete" (.Name)}}" data-variation="tiny inverted" data-position="top right" data-name="{{.Name}}">
                                                                                                        {{svg "octicon-trash"}}
                                                                                                </a>
                                                                                        {{end}}
diff --git a/web_src/js/code/linebutton.js b/web_src/js/code/linebutton.js
deleted file mode 100644 (file)
index c177ca6..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-import {svg} from '../svg.js';
-
-export function showLineButton() {
-  if ($('.code-line-menu').length === 0) return;
-  $('.code-line-button').remove();
-  $('.code-view td.lines-code.active').closest('tr').find('td:eq(0)').first().prepend(
-    $(`<button class="code-line-button">${svg('octicon-kebab-horizontal')}</button>`)
-  );
-  $('.code-line-menu').appendTo($('.code-view'));
-  $('.code-line-button').popup({popup: $('.code-line-menu'), on: 'click'});
-}
index 7ae62af0a012c6966e85f015a167d2019fb2a4f2..04eb304c80077391a98c06a775a8abe268dff68e 100644 (file)
@@ -348,7 +348,7 @@ function initVueComponents() {
 }
 
 
-function initDashboardRepoList() {
+export function initDashboardRepoList() {
   const el = document.getElementById('dashboard-repo-list');
   const dashboardRepoListData = pageData.dashboardRepoList || null;
   if (!el || !dashboardRepoListData) return;
@@ -366,5 +366,3 @@ function initDashboardRepoList() {
     },
   });
 }
-
-export {initDashboardRepoList};
index d510695b1df63b27f6388eaafddd48779bb6b6fc..37b6df91878f9d1a2c0c4f5c31637b9d6c68d1cd 100644 (file)
@@ -101,10 +101,9 @@ const sfc = {
   }
 };
 
-function initRepoActivityTopAuthorsChart() {
+export function initRepoActivityTopAuthorsChart() {
   initVueApp('#repo-activity-top-authors-chart', sfc);
 }
 
-export default sfc;
-export {initRepoActivityTopAuthorsChart};
+export default sfc; // this line is necessary to activate the IDE's Vue plugin
 </script>
index a0be57ab3d3b63a5bf376e30aa5856928abac817..50c71d5bacc025f49371973039f852f864ae1fa9 100644 (file)
@@ -1,6 +1,7 @@
 import Vue from 'vue';
+import {vueDelimiters} from './VueComponentLoader.js';
 
-function initRepoBranchTagDropdown(selector) {
+export function initRepoBranchTagDropdown(selector) {
   $(selector).each(function () {
     const $dropdown = $(this);
     const $data = $dropdown.find('.data');
@@ -26,7 +27,7 @@ function initRepoBranchTagDropdown(selector) {
     $data.remove();
     new Vue({
       el: this,
-      delimiters: ['${', '}'],
+      delimiters: vueDelimiters,
       data,
       computed: {
         filteredItems() {
@@ -157,5 +158,3 @@ function initRepoBranchTagDropdown(selector) {
     });
   });
 }
-
-export {initRepoBranchTagDropdown};
index 6b2a2cbd58f62e589ae9c038e0a0b9bbf274e05a..110782bbb1d8f8b32bee9c2100d10b3b806b43ff 100644 (file)
@@ -1,10 +1,10 @@
 import Vue from 'vue';
 import {svgs} from '../svg.js';
 
-const vueDelimiters = ['${', '}'];
+export const vueDelimiters = ['${', '}'];
 
 let vueEnvInited = false;
-function initVueEnv() {
+export function initVueEnv() {
   if (vueEnvInited) return;
   vueEnvInited = true;
 
@@ -14,7 +14,7 @@ function initVueEnv() {
 }
 
 let vueSvgInited = false;
-function initVueSvg() {
+export function initVueSvg() {
   if (vueSvgInited) return;
   vueSvgInited = true;
 
@@ -36,8 +36,7 @@ function initVueSvg() {
   }
 }
 
-
-function initVueApp(el, opts = {}) {
+export function initVueApp(el, opts = {}) {
   if (typeof el === 'string') {
     el = document.querySelector(el);
   }
@@ -48,5 +47,3 @@ function initVueApp(el, opts = {}) {
     delimiters: vueDelimiters,
   }, opts));
 }
-
-export {vueDelimiters, initVueEnv, initVueSvg, initVueApp};
diff --git a/web_src/js/features/admin-common.js b/web_src/js/features/admin-common.js
new file mode 100644 (file)
index 0000000..4f12c18
--- /dev/null
@@ -0,0 +1,214 @@
+const {csrf} = window.config;
+
+export function initAdminCommon() {
+  if ($('.admin').length === 0) {
+    return;
+  }
+
+  // New user
+  if ($('.admin.new.user').length > 0 || $('.admin.edit.user').length > 0) {
+    $('#login_type').on('change', function () {
+      if ($(this).val().substring(0, 1) === '0') {
+        $('#user_name').removeAttr('disabled');
+        $('#login_name').removeAttr('required');
+        $('.non-local').hide();
+        $('.local').show();
+        $('#user_name').focus();
+
+        if ($(this).data('password') === 'required') {
+          $('#password').attr('required', 'required');
+        }
+      } else {
+        if ($('.admin.edit.user').length > 0) {
+          $('#user_name').attr('disabled', 'disabled');
+        }
+        $('#login_name').attr('required', 'required');
+        $('.non-local').show();
+        $('.local').hide();
+        $('#login_name').focus();
+
+        $('#password').removeAttr('required');
+      }
+    });
+  }
+
+  function onSecurityProtocolChange() {
+    if ($('#security_protocol').val() > 0) {
+      $('.has-tls').show();
+    } else {
+      $('.has-tls').hide();
+    }
+  }
+
+  function onUsePagedSearchChange() {
+    if ($('#use_paged_search').prop('checked')) {
+      $('.search-page-size').show()
+        .find('input').attr('required', 'required');
+    } else {
+      $('.search-page-size').hide()
+        .find('input').removeAttr('required');
+    }
+  }
+
+  function onOAuth2Change(applyDefaultValues) {
+    $('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url').hide();
+    $('.open_id_connect_auto_discovery_url input[required]').removeAttr('required');
+
+    const provider = $('#oauth2_provider').val();
+    switch (provider) {
+      case 'openidConnect':
+        $('.open_id_connect_auto_discovery_url input').attr('required', 'required');
+        $('.open_id_connect_auto_discovery_url').show();
+        break;
+      default:
+        if ($(`#${provider}_customURLSettings`).data('required')) {
+          $('#oauth2_use_custom_url').attr('checked', 'checked');
+        }
+        if ($(`#${provider}_customURLSettings`).data('available')) {
+          $('.oauth2_use_custom_url').show();
+        }
+    }
+    onOAuth2UseCustomURLChange(applyDefaultValues);
+  }
+
+  function onOAuth2UseCustomURLChange(applyDefaultValues) {
+    const provider = $('#oauth2_provider').val();
+    $('.oauth2_use_custom_url_field').hide();
+    $('.oauth2_use_custom_url_field input[required]').removeAttr('required');
+
+    if ($('#oauth2_use_custom_url').is(':checked')) {
+      for (const custom of ['token_url', 'auth_url', 'profile_url', 'email_url', 'tenant']) {
+        if (applyDefaultValues) {
+          $(`#oauth2_${custom}`).val($(`#${provider}_${custom}`).val());
+        }
+        if ($(`#${provider}_${custom}`).data('available')) {
+          $(`.oauth2_${custom} input`).attr('required', 'required');
+          $(`.oauth2_${custom}`).show();
+        }
+      }
+    }
+  }
+
+  function onVerifyGroupMembershipChange() {
+    if ($('#groups_enabled').is(':checked')) {
+      $('#groups_enabled_change').show();
+    } else {
+      $('#groups_enabled_change').hide();
+    }
+  }
+
+  // New authentication
+  if ($('.admin.new.authentication').length > 0) {
+    $('#auth_type').on('change', function () {
+      $('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size, .sspi').hide();
+
+      $('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required], .sspi input[required]').removeAttr('required');
+      $('.binddnrequired').removeClass('required');
+
+      const authType = $(this).val();
+      switch (authType) {
+        case '2': // LDAP
+          $('.ldap').show();
+          $('.binddnrequired input, .ldap div.required:not(.dldap) input').attr('required', 'required');
+          $('.binddnrequired').addClass('required');
+          break;
+        case '3': // SMTP
+          $('.smtp').show();
+          $('.has-tls').show();
+          $('.smtp div.required input, .has-tls').attr('required', 'required');
+          break;
+        case '4': // PAM
+          $('.pam').show();
+          $('.pam input').attr('required', 'required');
+          break;
+        case '5': // LDAP
+          $('.dldap').show();
+          $('.dldap div.required:not(.ldap) input').attr('required', 'required');
+          break;
+        case '6': // OAuth2
+          $('.oauth2').show();
+          $('.oauth2 div.required:not(.oauth2_use_custom_url,.oauth2_use_custom_url_field,.open_id_connect_auto_discovery_url) input').attr('required', 'required');
+          onOAuth2Change(true);
+          break;
+        case '7': // SSPI
+          $('.sspi').show();
+          $('.sspi div.required input').attr('required', 'required');
+          break;
+      }
+      if (authType === '2' || authType === '5') {
+        onSecurityProtocolChange();
+        onVerifyGroupMembershipChange();
+      }
+      if (authType === '2') {
+        onUsePagedSearchChange();
+      }
+    });
+    $('#auth_type').trigger('change');
+    $('#security_protocol').on('change', onSecurityProtocolChange);
+    $('#use_paged_search').on('change', onUsePagedSearchChange);
+    $('#oauth2_provider').on('change', () => onOAuth2Change(true));
+    $('#oauth2_use_custom_url').on('change', () => onOAuth2UseCustomURLChange(true));
+    $('#groups_enabled').on('change', onVerifyGroupMembershipChange);
+  }
+  // Edit authentication
+  if ($('.admin.edit.authentication').length > 0) {
+    const authType = $('#auth_type').val();
+    if (authType === '2' || authType === '5') {
+      $('#security_protocol').on('change', onSecurityProtocolChange);
+      $('#groups_enabled').on('change', onVerifyGroupMembershipChange);
+      onVerifyGroupMembershipChange();
+      if (authType === '2') {
+        $('#use_paged_search').on('change', onUsePagedSearchChange);
+      }
+    } else if (authType === '6') {
+      $('#oauth2_provider').on('change', () => onOAuth2Change(true));
+      $('#oauth2_use_custom_url').on('change', () => onOAuth2UseCustomURLChange(false));
+      onOAuth2Change(false);
+    }
+  }
+
+  // Notice
+  if ($('.admin.notice')) {
+    const $detailModal = $('#detail-modal');
+
+    // Attach view detail modals
+    $('.view-detail').on('click', function () {
+      $detailModal.find('.content pre').text($(this).parents('tr').find('.notice-description').text());
+      $detailModal.find('.sub.header').text($(this).parents('tr').find('.notice-created-time').text());
+      $detailModal.modal('show');
+      return false;
+    });
+
+    // Select actions
+    const $checkboxes = $('.select.table .ui.checkbox');
+    $('.select.action').on('click', function () {
+      switch ($(this).data('action')) {
+        case 'select-all':
+          $checkboxes.checkbox('check');
+          break;
+        case 'deselect-all':
+          $checkboxes.checkbox('uncheck');
+          break;
+        case 'inverse':
+          $checkboxes.checkbox('toggle');
+          break;
+      }
+    });
+    $('#delete-selection').on('click', function () {
+      const $this = $(this);
+      $this.addClass('loading disabled');
+      const ids = [];
+      $checkboxes.each(function () {
+        if ($(this).checkbox('is checked')) {
+          ids.push($(this).data('id'));
+        }
+      });
+      $.post($this.data('link'), {
+        _csrf: csrf,
+        ids
+      }).done(() => {
+        window.location.href = $this.data('redirect');
+      });
+    });
+  }
+}
diff --git a/web_src/js/features/admin-emails.js b/web_src/js/features/admin-emails.js
new file mode 100644 (file)
index 0000000..f69629d
--- /dev/null
@@ -0,0 +1,12 @@
+export function initAdminEmails() {
+  function linkEmailAction(e) {
+    const $this = $(this);
+    $('#form-uid').val($this.data('uid'));
+    $('#form-email').val($this.data('email'));
+    $('#form-primary').val($this.data('primary'));
+    $('#form-activate').val($this.data('activate'));
+    $('#change-email-modal').modal('show');
+    e.preventDefault();
+  }
+  $('.link-email-action').on('click', linkEmailAction);
+}
index 12486a208d5a3632269e22bafedded02ddb15c2e..4781f8d6ff4a4c4fca9cf0bac41e1c4e75018964 100644 (file)
@@ -1,4 +1,4 @@
-const selector = '[data-clipboard-target], [data-clipboard-text]';
+// For all DOM elements with [data-clipboard-target] or [data-clipboard-text], this copy-to-clipboard will work for them
 
 // TODO: replace these with toast-style notifications
 function onSuccess(btn) {
@@ -16,23 +16,28 @@ function onError(btn) {
   btn.dataset.content = btn.dataset.original;
 }
 
-export default async function initClipboard() {
-  for (const btn of document.querySelectorAll(selector) || []) {
-    btn.addEventListener('click', async () => {
+export default function initGlobalCopyToClipboardListener() {
+  document.addEventListener('click', async (e) => {
+    let target = e.target;
+    // in case <button data-clipboard-text><svg></button>, so we just search up to 3 levels for performance.
+    for (let i = 0; i < 3 && target; i++) {
       let text;
-      if (btn.dataset.clipboardText) {
-        text = btn.dataset.clipboardText;
-      } else if (btn.dataset.clipboardTarget) {
-        text = document.querySelector(btn.dataset.clipboardTarget)?.value;
+      if (target.dataset.clipboardText) {
+        text = target.dataset.clipboardText;
+      } else if (target.dataset.clipboardTarget) {
+        text = document.querySelector(target.dataset.clipboardTarget)?.value;
       }
-      if (!text) return;
-
-      try {
-        await navigator.clipboard.writeText(text);
-        onSuccess(btn);
-      } catch {
-        onError(btn);
+      if (text) {
+        e.preventDefault();
+        try {
+          await navigator.clipboard.writeText(text);
+          onSuccess(target);
+        } catch {
+          onError(target);
+        }
+        break;
       }
-    });
-  }
+      target = target.parentElement;
+    }
+  });
 }
diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js
new file mode 100644 (file)
index 0000000..79cdac5
--- /dev/null
@@ -0,0 +1,306 @@
+import {mqBinarySearch} from '../utils.js';
+import createDropzone from './dropzone.js';
+import {initCompColorPicker} from './comp/ColorPicker.js';
+
+import 'jquery.are-you-sure';
+
+const {csrf} = window.config;
+
+export function initGlobalFormDirtyLeaveConfirm() {
+  // Warn users that try to leave a page after entering data into a form.
+  // Except on sign-in pages, and for forms marked as 'ignore-dirty'.
+  if ($('.user.signin').length === 0) {
+    $('form:not(.ignore-dirty)').areYouSure();
+  }
+}
+
+export function initHeadNavbarContentToggle() {
+  const content = $('#navbar');
+  const toggle = $('#navbar-expand-toggle');
+  let isExpanded = false;
+  toggle.on('click', () => {
+    isExpanded = !isExpanded;
+    if (isExpanded) {
+      content.addClass('shown');
+      toggle.addClass('active');
+    } else {
+      content.removeClass('shown');
+      toggle.removeClass('active');
+    }
+  });
+}
+
+export function initFootLanguageMenu() {
+  function linkLanguageAction() {
+    const $this = $(this);
+    $.post($this.data('url')).always(() => {
+      window.location.reload();
+    });
+  }
+
+  $('.language-menu a[lang]').on('click', linkLanguageAction);
+}
+
+
+export function initGlobalEnterQuickSubmit() {
+  $('.js-quick-submit').on('keydown', function (e) {
+    if (((e.ctrlKey && !e.altKey) || e.metaKey) && (e.keyCode === 13 || e.keyCode === 10)) {
+      $(this).closest('form').trigger('submit');
+    }
+  });
+}
+
+export function initGlobalButtonClickOnEnter() {
+  $(document).on('keypress', '.ui.button', (e) => {
+    if (e.keyCode === 13 || e.keyCode === 32) { // enter key or space bar
+      $(e.target).trigger('click');
+    }
+  });
+}
+
+export function initGlobalCommon() {
+  // Show exact time
+  $('.time-since').each(function () {
+    $(this)
+      .addClass('poping up')
+      .attr('data-content', $(this).attr('title'))
+      .attr('data-variation', 'inverted tiny')
+      .attr('title', '');
+  });
+
+  // Undo Safari emoji glitch fix at high enough zoom levels
+  if (navigator.userAgent.match('Safari')) {
+    $(window).resize(() => {
+      const px = mqBinarySearch('width', 0, 4096, 1, 'px');
+      const em = mqBinarySearch('width', 0, 1024, 0.01, 'em');
+      if (em * 16 * 1.25 - px <= -1) {
+        $('body').addClass('safari-above125');
+      } else {
+        $('body').removeClass('safari-above125');
+      }
+    });
+  }
+
+  // Semantic UI modules.
+  $('.dropdown:not(.custom)').dropdown({
+    fullTextSearch: 'exact'
+  });
+  $('.jump.dropdown').dropdown({
+    action: 'hide',
+    onShow() {
+      $('.poping.up').popup('hide');
+    },
+    fullTextSearch: 'exact'
+  });
+  $('.slide.up.dropdown').dropdown({
+    transition: 'slide up',
+    fullTextSearch: 'exact'
+  });
+  $('.upward.dropdown').dropdown({
+    direction: 'upward',
+    fullTextSearch: 'exact'
+  });
+  $('.ui.checkbox').checkbox();
+  $('.ui.progress').progress({
+    showActivity: false
+  });
+  $('.poping.up').popup();
+  $('.top.menu .poping.up').popup({
+    onShow() {
+      if ($('.top.menu .menu.transition').hasClass('visible')) {
+        return false;
+      }
+    }
+  });
+  $('.tabular.menu .item').tab();
+  $('.tabable.menu .item').tab();
+
+  $('.toggle.button').on('click', function () {
+    $($(this).data('target')).slideToggle(100);
+  });
+
+  // make table <tr> element clickable like a link
+  $('tr[data-href]').on('click', function () {
+    window.location = $(this).data('href');
+  });
+
+  // make table <td> element clickable like a link
+  $('td[data-href]').click(function () {
+    window.location = $(this).data('href');
+  });
+}
+
+export async function initGlobalDropzone() {
+  // Dropzone
+  for (const el of document.querySelectorAll('.dropzone')) {
+    const $dropzone = $(el);
+    await createDropzone(el, {
+      url: $dropzone.data('upload-url'),
+      headers: {'X-Csrf-Token': csrf},
+      maxFiles: $dropzone.data('max-file'),
+      maxFilesize: $dropzone.data('max-size'),
+      acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'),
+      addRemoveLinks: true,
+      dictDefaultMessage: $dropzone.data('default-message'),
+      dictInvalidFileType: $dropzone.data('invalid-input-type'),
+      dictFileTooBig: $dropzone.data('file-too-big'),
+      dictRemoveFile: $dropzone.data('remove-file'),
+      timeout: 0,
+      thumbnailMethod: 'contain',
+      thumbnailWidth: 480,
+      thumbnailHeight: 480,
+      init() {
+        this.on('success', (_file, data) => {
+          const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
+          $dropzone.find('.files').append(input);
+        });
+        this.on('removedfile', (file) => {
+          $(`#${file.uuid}`).remove();
+          if ($dropzone.data('remove-url')) {
+            $.post($dropzone.data('remove-url'), {
+              file: file.uuid,
+              _csrf: csrf,
+            });
+          }
+        });
+      },
+    });
+  }
+}
+
+export function initGlobalLinkActions() {
+  function showDeletePopup() {
+    const $this = $(this);
+    const dataArray = $this.data();
+    let filter = '';
+    if ($this.data('modal-id')) {
+      filter += `#${$this.data('modal-id')}`;
+    }
+
+    const dialog = $(`.delete.modal${filter}`);
+    dialog.find('.name').text($this.data('name'));
+    for (const [key, value] of Object.entries(dataArray)) {
+      if (key && key.startsWith('data')) {
+        dialog.find(`.${key}`).text(value);
+      }
+    }
+
+    dialog.modal({
+      closable: false,
+      onApprove() {
+        if ($this.data('type') === 'form') {
+          $($this.data('form')).trigger('submit');
+          return;
+        }
+
+        const postData = {
+          _csrf: csrf,
+        };
+        for (const [key, value] of Object.entries(dataArray)) {
+          if (key && key.startsWith('data')) {
+            postData[key.substr(4)] = value;
+          }
+          if (key === 'id') {
+            postData['id'] = value;
+          }
+        }
+
+        $.post($this.data('url'), postData).done((data) => {
+          window.location.href = data.redirect;
+        });
+      }
+    }).modal('show');
+    return false;
+  }
+
+  function showAddAllPopup() {
+    const $this = $(this);
+    let filter = '';
+    if ($this.attr('id')) {
+      filter += `#${$this.attr('id')}`;
+    }
+
+    const dialog = $(`.addall.modal${filter}`);
+    dialog.find('.name').text($this.data('name'));
+
+    dialog.modal({
+      closable: false,
+      onApprove() {
+        if ($this.data('type') === 'form') {
+          $($this.data('form')).trigger('submit');
+          return;
+        }
+
+        $.post($this.data('url'), {
+          _csrf: csrf,
+          id: $this.data('id')
+        }).done((data) => {
+          window.location.href = data.redirect;
+        });
+      }
+    }).modal('show');
+    return false;
+  }
+
+  function linkAction(e) {
+    e.preventDefault();
+    const $this = $(this);
+    const redirect = $this.data('redirect');
+    $.post($this.data('url'), {
+      _csrf: csrf
+    }).done((data) => {
+      if (data.redirect) {
+        window.location.href = data.redirect;
+      } else if (redirect) {
+        window.location.href = redirect;
+      } else {
+        window.location.reload();
+      }
+    });
+  }
+
+  // Helpers.
+  $('.delete-button').on('click', showDeletePopup);
+  $('.link-action').on('click', linkAction);
+
+  // FIXME: this function is only used once, and not common, not well designed. should be refactored later
+  $('.add-all-button').on('click', showAddAllPopup);
+
+  // FIXME: this is only used once, and should be replace with `link-action` instead
+  $('.undo-button').on('click', function () {
+    const $this = $(this);
+    $.post($this.data('url'), {
+      _csrf: csrf,
+      id: $this.data('id')
+    }).done((data) => {
+      window.location.href = data.redirect;
+    });
+  });
+}
+
+export function initGlobalButtons() {
+  $('.show-panel.button').on('click', function () {
+    $($(this).data('panel')).show();
+  });
+
+  $('.hide-panel.button').on('click', function () {
+    $($(this).data('panel')).hide();
+  });
+
+  $('.show-modal.button').on('click', function () {
+    $($(this).data('modal')).modal('show');
+    const colorPickers = $($(this).data('modal')).find('.color-picker');
+    if (colorPickers.length > 0) {
+      initCompColorPicker();
+    }
+  });
+
+  $('.delete-post.button').on('click', function () {
+    const $this = $(this);
+    $.post($this.data('request-url'), {
+      _csrf: csrf
+    }).done(() => {
+      window.location.href = $this.data('done-url');
+    });
+  });
+}
diff --git a/web_src/js/features/common-issue.js b/web_src/js/features/common-issue.js
new file mode 100644 (file)
index 0000000..e2cf51f
--- /dev/null
@@ -0,0 +1,40 @@
+import {updateIssuesMeta} from './repo-issue.js';
+
+export function initCommonIssue() {
+  $('.issue-checkbox').on('click', () => {
+    const numChecked = $('.issue-checkbox').children('input:checked').length;
+    if (numChecked > 0) {
+      $('#issue-filters').addClass('hide');
+      $('#issue-actions').removeClass('hide');
+    } else {
+      $('#issue-filters').removeClass('hide');
+      $('#issue-actions').addClass('hide');
+    }
+  });
+
+  $('.issue-action').on('click', function () {
+    let {action, elementId, url} = this.dataset;
+    const issueIDs = $('.issue-checkbox').children('input:checked').map((_, el) => {
+      return el.dataset.issueId;
+    }).get().join(',');
+    if (elementId === '0' && url.substr(-9) === '/assignee') {
+      elementId = '';
+      action = 'clear';
+    }
+    updateIssuesMeta(url, action, issueIDs, elementId, '').then(() => {
+      // NOTICE: This reset of checkbox state targets Firefox caching behaviour, as the checkboxes stay checked after reload
+      if (action === 'close' || action === 'open') {
+        // uncheck all checkboxes
+        $('.issue-checkbox input[type="checkbox"]').each((_, e) => { e.checked = false });
+      }
+      window.location.reload();
+    });
+  });
+
+  // NOTICE: This event trigger targets Firefox caching behaviour, as the checkboxes stay checked after reload
+  // trigger ckecked event, if checkboxes are checked on load
+  $('.issue-checkbox input[type="checkbox"]:checked').first().each((_, e) => {
+    e.checked = false;
+    $(e).trigger('click');
+  });
+}
diff --git a/web_src/js/features/common-organization.js b/web_src/js/features/common-organization.js
new file mode 100644 (file)
index 0000000..9496f8f
--- /dev/null
@@ -0,0 +1,24 @@
+import {initCompLabelEdit} from './comp/LabelEdit.js';
+
+export function initCommonOrganization() {
+  if ($('.organization').length === 0) {
+    return;
+  }
+
+  if ($('.organization.settings.options').length > 0) {
+    $('#org_name').on('keyup', function () {
+      const $prompt = $('#org-name-change-prompt');
+      const $prompt_redirect = $('#org-name-change-redirect-prompt');
+      if ($(this).val().toString().toLowerCase() !== $(this).data('org-name').toString().toLowerCase()) {
+        $prompt.show();
+        $prompt_redirect.show();
+      } else {
+        $prompt.hide();
+        $prompt_redirect.hide();
+      }
+    });
+  }
+
+  // Labels
+  initCompLabelEdit('.organization.settings.labels');
+}
diff --git a/web_src/js/features/comp/ColorPicker.js b/web_src/js/features/comp/ColorPicker.js
new file mode 100644 (file)
index 0000000..669e1d1
--- /dev/null
@@ -0,0 +1,11 @@
+import createColorPicker from '../colorpicker.js';
+
+export function initCompColorPicker() {
+  createColorPicker($('.color-picker'));
+
+  $('.precolors .color').on('click', function () {
+    const color_hex = $(this).data('color-hex');
+    $('.color-picker').val(color_hex);
+    $('.minicolors-swatch-color').css('background-color', color_hex);
+  });
+}
diff --git a/web_src/js/features/comp/CommentSimpleMDE.js b/web_src/js/features/comp/CommentSimpleMDE.js
new file mode 100644 (file)
index 0000000..fbc0ec8
--- /dev/null
@@ -0,0 +1,72 @@
+import attachTribute from '../tribute.js';
+
+export function createCommentSimpleMDE($editArea) {
+  if ($editArea.length === 0) {
+    return null;
+  }
+
+  const simplemde = new SimpleMDE({
+    autoDownloadFontAwesome: false,
+    element: $editArea[0],
+    forceSync: true,
+    renderingConfig: {
+      singleLineBreaks: false
+    },
+    indentWithTabs: false,
+    tabSize: 4,
+    spellChecker: false,
+    toolbar: ['bold', 'italic', 'strikethrough', '|',
+      'heading-1', 'heading-2', 'heading-3', 'heading-bigger', 'heading-smaller', '|',
+      'code', 'quote', '|', {
+        name: 'checkbox-empty',
+        action(e) {
+          const cm = e.codemirror;
+          cm.replaceSelection(`\n- [ ] ${cm.getSelection()}`);
+          cm.focus();
+        },
+        className: 'fa fa-square-o',
+        title: 'Add Checkbox (empty)',
+      },
+      {
+        name: 'checkbox-checked',
+        action(e) {
+          const cm = e.codemirror;
+          cm.replaceSelection(`\n- [x] ${cm.getSelection()}`);
+          cm.focus();
+        },
+        className: 'fa fa-check-square-o',
+        title: 'Add Checkbox (checked)',
+      }, '|',
+      'unordered-list', 'ordered-list', '|',
+      'link', 'image', 'table', 'horizontal-rule', '|',
+      'clean-block', '|',
+      {
+        name: 'revert-to-textarea',
+        action(e) {
+          e.toTextArea();
+        },
+        className: 'fa fa-file',
+        title: 'Revert to simple textarea',
+      },
+    ]
+  });
+  $(simplemde.codemirror.getInputField()).addClass('js-quick-submit');
+  simplemde.codemirror.setOption('extraKeys', {
+    Enter: () => {
+      const tributeContainer = document.querySelector('.tribute-container');
+      if (!tributeContainer || tributeContainer.style.display === 'none') {
+        return CodeMirror.Pass;
+      }
+    },
+    Backspace: (cm) => {
+      if (cm.getInputField().trigger) {
+        cm.getInputField().trigger('input');
+      }
+      cm.execCommand('delCharBefore');
+    }
+  });
+  attachTribute(simplemde.codemirror.getInputField(), {mentions: true, emoji: true});
+  $editArea.data('simplemde', simplemde);
+  $(simplemde.codemirror.getInputField()).data('simplemde', simplemde);
+  return simplemde;
+}
diff --git a/web_src/js/features/comp/ImagePaste.js b/web_src/js/features/comp/ImagePaste.js
new file mode 100644 (file)
index 0000000..b6881dd
--- /dev/null
@@ -0,0 +1,91 @@
+const {AppSubUrl, csrf} = window.config;
+
+async function uploadFile(file, uploadUrl) {
+  const formData = new FormData();
+  formData.append('file', file, file.name);
+
+  const res = await fetch(uploadUrl, {
+    method: 'POST',
+    headers: {'X-Csrf-Token': csrf},
+    body: formData,
+  });
+  return await res.json();
+}
+
+function clipboardPastedImages(e) {
+  if (!e.clipboardData) return [];
+
+  const files = [];
+  for (const item of e.clipboardData.items || []) {
+    if (!item.type || !item.type.startsWith('image/')) continue;
+    files.push(item.getAsFile());
+  }
+
+  if (files.length) {
+    e.preventDefault();
+    e.stopPropagation();
+  }
+  return files;
+}
+
+
+function insertAtCursor(field, value) {
+  if (field.selectionStart || field.selectionStart === 0) {
+    const startPos = field.selectionStart;
+    const endPos = field.selectionEnd;
+    field.value = field.value.substring(0, startPos) + value + field.value.substring(endPos, field.value.length);
+    field.selectionStart = startPos + value.length;
+    field.selectionEnd = startPos + value.length;
+  } else {
+    field.value += value;
+  }
+}
+
+function replaceAndKeepCursor(field, oldval, newval) {
+  if (field.selectionStart || field.selectionStart === 0) {
+    const startPos = field.selectionStart;
+    const endPos = field.selectionEnd;
+    field.value = field.value.replace(oldval, newval);
+    field.selectionStart = startPos + newval.length - oldval.length;
+    field.selectionEnd = endPos + newval.length - oldval.length;
+  } else {
+    field.value = field.value.replace(oldval, newval);
+  }
+}
+
+export function initCompImagePaste($target) {
+  $target.each(function () {
+    const dropzone = this.querySelector('.dropzone');
+    if (!dropzone) {
+      return;
+    }
+    const uploadUrl = dropzone.dataset.uploadUrl;
+    const dropzoneFiles = dropzone.querySelector('.files');
+    for (const textarea of this.querySelectorAll('textarea')) {
+      textarea.addEventListener('paste', async (e) => {
+        for (const img of clipboardPastedImages(e)) {
+          const name = img.name.substr(0, img.name.lastIndexOf('.'));
+          insertAtCursor(textarea, `![${name}]()`);
+          const data = await uploadFile(img, uploadUrl);
+          replaceAndKeepCursor(textarea, `![${name}]()`, `![${name}](${AppSubUrl}/attachments/${data.uuid})`);
+          const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
+          dropzoneFiles.appendChild(input[0]);
+        }
+      }, false);
+    }
+  });
+}
+
+export function initSimpleMDEImagePaste(simplemde, dropzone, files) {
+  const uploadUrl = dropzone.dataset.uploadUrl;
+  simplemde.codemirror.on('paste', async (_, e) => {
+    for (const img of clipboardPastedImages(e)) {
+      const name = img.name.substr(0, img.name.lastIndexOf('.'));
+      const data = await uploadFile(img, uploadUrl);
+      const pos = simplemde.codemirror.getCursor();
+      simplemde.codemirror.replaceRange(`![${name}](${AppSubUrl}/attachments/${data.uuid})`, pos);
+      const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
+      files.append(input);
+    }
+  });
+}
diff --git a/web_src/js/features/comp/LabelEdit.js b/web_src/js/features/comp/LabelEdit.js
new file mode 100644 (file)
index 0000000..7d71e6e
--- /dev/null
@@ -0,0 +1,30 @@
+import {initCompColorPicker} from './ColorPicker.js';
+
+export function initCompLabelEdit(selector) {
+  if (!$(selector).length) return;
+  // Create label
+  const $newLabelPanel = $('.new-label.segment');
+  $('.new-label.button').on('click', () => {
+    $newLabelPanel.show();
+  });
+  $('.new-label.segment .cancel').on('click', () => {
+    $newLabelPanel.hide();
+  });
+
+  initCompColorPicker();
+
+  $('.edit-label-button').on('click', function () {
+    $('.edit-label .color-picker').minicolors('value', $(this).data('color'));
+    $('#label-modal-id').val($(this).data('id'));
+    $('.edit-label .new-label-input').val($(this).data('title'));
+    $('.edit-label .new-label-desc-input').val($(this).data('description'));
+    $('.edit-label .color-picker').val($(this).data('color'));
+    $('.edit-label .minicolors-swatch-color').css('background-color', $(this).data('color'));
+    $('.edit-label.modal').modal({
+      onApprove() {
+        $('.edit-label.form').trigger('submit');
+      }
+    }).modal('show');
+    return false;
+  });
+}
diff --git a/web_src/js/features/comp/MarkupContentPreview.js b/web_src/js/features/comp/MarkupContentPreview.js
new file mode 100644 (file)
index 0000000..0b05c4e
--- /dev/null
@@ -0,0 +1,21 @@
+import {initMarkupContent} from '../../markup/content.js';
+
+const {csrf} = window.config;
+
+export function initCompMarkupContentPreviewTab($form) {
+  const $tabMenu = $form.find('.tabular.menu');
+  $tabMenu.find('.item').tab();
+  $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`).on('click', function () {
+    const $this = $(this);
+    $.post($this.data('url'), {
+      _csrf: csrf,
+      mode: 'comment',
+      context: $this.data('context'),
+      text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val()
+    }, (data) => {
+      const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`);
+      $previewPanel.html(data);
+      initMarkupContent();
+    });
+  });
+}
diff --git a/web_src/js/features/comp/ReactionSelector.js b/web_src/js/features/comp/ReactionSelector.js
new file mode 100644 (file)
index 0000000..d11c966
--- /dev/null
@@ -0,0 +1,48 @@
+const {csrf} = window.config;
+
+export function initCompReactionSelector(parent) {
+  let reactions = '';
+  if (!parent) {
+    parent = $(document);
+    reactions = '.reactions > ';
+  }
+
+  parent.find(`${reactions}a.label`).popup({position: 'bottom left', metadata: {content: 'title', title: 'none'}});
+
+  parent.find(`.select-reaction > .menu > .item, ${reactions}a.label`).on('click', function (e) {
+    e.preventDefault();
+
+    if ($(this).hasClass('disabled')) return;
+
+    const actionURL = $(this).hasClass('item') ? $(this).closest('.select-reaction').data('action-url') : $(this).data('action-url');
+    const url = `${actionURL}/${$(this).hasClass('blue') ? 'unreact' : 'react'}`;
+    $.ajax({
+      type: 'POST',
+      url,
+      data: {
+        _csrf: csrf,
+        content: $(this).data('content')
+      }
+    }).done((resp) => {
+      if (resp && (resp.html || resp.empty)) {
+        const content = $(this).closest('.content');
+        let react = content.find('.segment.reactions');
+        if ((!resp.empty || resp.html === '') && react.length > 0) {
+          react.remove();
+        }
+        if (!resp.empty) {
+          react = $('<div class="ui attached segment reactions"></div>');
+          const attachments = content.find('.segment.bottom:first');
+          if (attachments.length > 0) {
+            react.insertBefore(attachments);
+          } else {
+            react.appendTo(content);
+          }
+          react.html(resp.html);
+          react.find('.dropdown').dropdown();
+          initCompReactionSelector(react);
+        }
+      }
+    });
+  });
+}
diff --git a/web_src/js/features/comp/SearchUserBox.js b/web_src/js/features/comp/SearchUserBox.js
new file mode 100644 (file)
index 0000000..9019f17
--- /dev/null
@@ -0,0 +1,36 @@
+import {htmlEscape} from 'escape-goat';
+
+const {AppSubUrl} = window.config;
+
+export function initSearchUserBox() {
+  const $searchUserBox = $('#search-user-box');
+  $searchUserBox.search({
+    minCharacters: 2,
+    apiSettings: {
+      url: `${AppSubUrl}/api/v1/users/search?q={query}`,
+      onResponse(response) {
+        const items = [];
+        const searchQueryUppercase = $searchUserBox.find('input').val().toUpperCase();
+        $.each(response.data, (_i, item) => {
+          let title = item.login;
+          if (item.full_name && item.full_name.length > 0) {
+            title += ` (${htmlEscape(item.full_name)})`;
+          }
+          const resultItem = {
+            title,
+            image: item.avatar_url
+          };
+          if (searchQueryUppercase === item.login.toUpperCase()) {
+            items.unshift(resultItem);
+          } else {
+            items.push(resultItem);
+          }
+        });
+
+        return {results: items};
+      }
+    },
+    searchFields: ['login', 'full_name'],
+    showNoResults: false
+  });
+}
diff --git a/web_src/js/features/comp/WebHookEditor.js b/web_src/js/features/comp/WebHookEditor.js
new file mode 100644 (file)
index 0000000..6911c6c
--- /dev/null
@@ -0,0 +1,40 @@
+const {csrf} = window.config;
+
+export function initWebHookEditor() {
+  if ($('.new.webhook').length === 0) {
+    return;
+  }
+
+  $('.events.checkbox input').on('change', function () {
+    if ($(this).is(':checked')) {
+      $('.events.fields').show();
+    }
+  });
+  $('.non-events.checkbox input').on('change', function () {
+    if ($(this).is(':checked')) {
+      $('.events.fields').hide();
+    }
+  });
+
+  const updateContentType = function () {
+    const visible = $('#http_method').val() === 'POST';
+    $('#content_type').parent().parent()[visible ? 'show' : 'hide']();
+  };
+  updateContentType();
+  $('#http_method').on('change', () => {
+    updateContentType();
+  });
+
+  // Test delivery
+  $('#test-delivery').on('click', function () {
+    const $this = $(this);
+    $this.addClass('loading disabled');
+    $.post($this.data('link'), {
+      _csrf: csrf
+    }).done(
+      setTimeout(() => {
+        window.location.href = $this.data('redirect');
+      }, 5000)
+    );
+  });
+}
diff --git a/web_src/js/features/install.js b/web_src/js/features/install.js
new file mode 100644 (file)
index 0000000..6083fa7
--- /dev/null
@@ -0,0 +1,91 @@
+export function initInstall() {
+  if ($('.install').length === 0) {
+    return;
+  }
+
+  if ($('#db_host').val() === '') {
+    $('#db_host').val('127.0.0.1:3306');
+    $('#db_user').val('gitea');
+    $('#db_name').val('gitea');
+  }
+
+  // Database type change detection.
+  $('#db_type').on('change', function () {
+    const sqliteDefault = 'data/gitea.db';
+    const tidbDefault = 'data/gitea_tidb';
+
+    const dbType = $(this).val();
+    if (dbType === 'SQLite3') {
+      $('#sql_settings').hide();
+      $('#pgsql_settings').hide();
+      $('#mysql_settings').hide();
+      $('#sqlite_settings').show();
+
+      if (dbType === 'SQLite3' && $('#db_path').val() === tidbDefault) {
+        $('#db_path').val(sqliteDefault);
+      }
+      return;
+    }
+
+    const dbDefaults = {
+      MySQL: '127.0.0.1:3306',
+      PostgreSQL: '127.0.0.1:5432',
+      MSSQL: '127.0.0.1:1433'
+    };
+
+    $('#sqlite_settings').hide();
+    $('#sql_settings').show();
+
+    $('#pgsql_settings').toggle(dbType === 'PostgreSQL');
+    $('#mysql_settings').toggle(dbType === 'MySQL');
+    $.each(dbDefaults, (_type, defaultHost) => {
+      if ($('#db_host').val() === defaultHost) {
+        $('#db_host').val(dbDefaults[dbType]);
+        return false;
+      }
+    });
+  });
+
+  // TODO: better handling of exclusive relations.
+  $('#offline-mode input').on('change', function () {
+    if ($(this).is(':checked')) {
+      $('#disable-gravatar').checkbox('check');
+      $('#federated-avatar-lookup').checkbox('uncheck');
+    }
+  });
+  $('#disable-gravatar input').on('change', function () {
+    if ($(this).is(':checked')) {
+      $('#federated-avatar-lookup').checkbox('uncheck');
+    } else {
+      $('#offline-mode').checkbox('uncheck');
+    }
+  });
+  $('#federated-avatar-lookup input').on('change', function () {
+    if ($(this).is(':checked')) {
+      $('#disable-gravatar').checkbox('uncheck');
+      $('#offline-mode').checkbox('uncheck');
+    }
+  });
+  $('#enable-openid-signin input').on('change', function () {
+    if ($(this).is(':checked')) {
+      if (!$('#disable-registration input').is(':checked')) {
+        $('#enable-openid-signup').checkbox('check');
+      }
+    } else {
+      $('#enable-openid-signup').checkbox('uncheck');
+    }
+  });
+  $('#disable-registration input').on('change', function () {
+    if ($(this).is(':checked')) {
+      $('#enable-captcha').checkbox('uncheck');
+      $('#enable-openid-signup').checkbox('uncheck');
+    } else {
+      $('#enable-openid-signup').checkbox('check');
+    }
+  });
+  $('#enable-captcha input').on('change', function () {
+    if ($(this).is(':checked')) {
+      $('#disable-registration').checkbox('uncheck');
+    }
+  });
+}
diff --git a/web_src/js/features/org-team.js b/web_src/js/features/org-team.js
new file mode 100644 (file)
index 0000000..d649296
--- /dev/null
@@ -0,0 +1,37 @@
+const {AppSubUrl} = window.config;
+
+export function initOrgTeamSettings() {
+  // Change team access mode
+  $('.organization.new.team input[name=permission]').on('change', () => {
+    const val = $('input[name=permission]:checked', '.organization.new.team').val();
+    if (val === 'admin') {
+      $('.organization.new.team .team-units').hide();
+    } else {
+      $('.organization.new.team .team-units').show();
+    }
+  });
+}
+
+
+export function initOrgTeamSearchRepoBox() {
+  const $searchRepoBox = $('#search-repo-box');
+  $searchRepoBox.search({
+    minCharacters: 2,
+    apiSettings: {
+      url: `${AppSubUrl}/api/v1/repos/search?q={query}&uid=${$searchRepoBox.data('uid')}`,
+      onResponse(response) {
+        const items = [];
+        $.each(response.data, (_i, item) => {
+          items.push({
+            title: item.full_name.split('/')[1],
+            description: item.full_name
+          });
+        });
+
+        return {results: items};
+      }
+    },
+    searchFields: ['full_name'],
+    showNoResults: false
+  });
+}
diff --git a/web_src/js/features/repo-branch.js b/web_src/js/features/repo-branch.js
new file mode 100644 (file)
index 0000000..c911999
--- /dev/null
@@ -0,0 +1,7 @@
+export function initRepoBranchButton() {
+  $('.show-create-branch-modal.button').on('click', function () {
+    $('#create-branch-form')[0].action = $('#create-branch-form').data('base-action') + $(this).data('branch-from');
+    $('#modal-create-branch-from-span').text($(this).data('branch-from'));
+    $($(this).data('modal')).modal('show');
+  });
+}
diff --git a/web_src/js/features/repo-code.js b/web_src/js/features/repo-code.js
new file mode 100644 (file)
index 0000000..74b3c1f
--- /dev/null
@@ -0,0 +1,145 @@
+import {svg} from '../svg.js';
+
+function changeHash(hash) {
+  if (window.history.pushState) {
+    window.history.pushState(null, null, hash);
+  } else {
+    window.location.hash = hash;
+  }
+}
+
+function selectRange($list, $select, $from) {
+  $list.removeClass('active');
+
+  // add hashchange to permalink
+  const $issue = $('a.ref-in-new-issue');
+  const $copyPermalink = $('a.copy-line-permalink');
+
+  if ($issue.length === 0 || $copyPermalink.length === 0) {
+    return;
+  }
+
+  const updateIssueHref = function(anchor) {
+    let href = $issue.attr('href');
+    href = `${href.replace(/%23L\d+$|%23L\d+-L\d+$/, '')}%23${anchor}`;
+    $issue.attr('href', href);
+  };
+
+  const updateCopyPermalinkHref = function(anchor) {
+    let link = $copyPermalink.attr('data-clipboard-text');
+    link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`;
+    $copyPermalink.attr('data-clipboard-text', link);
+  };
+
+  if ($from) {
+    let a = parseInt($select.attr('rel').substr(1));
+    let b = parseInt($from.attr('rel').substr(1));
+    let c;
+    if (a !== b) {
+      if (a > b) {
+        c = a;
+        a = b;
+        b = c;
+      }
+      const classes = [];
+      for (let i = a; i <= b; i++) {
+        classes.push(`[rel=L${i}]`);
+      }
+      $list.filter(classes.join(',')).addClass('active');
+      changeHash(`#L${a}-L${b}`);
+
+      updateIssueHref(`L${a}-L${b}`);
+      updateCopyPermalinkHref(`L${a}-L${b}`);
+      return;
+    }
+  }
+  $select.addClass('active');
+  changeHash(`#${$select.attr('rel')}`);
+
+  updateIssueHref($select.attr('rel'));
+  updateCopyPermalinkHref($select.attr('rel'));
+}
+
+function showLineButton() {
+  if ($('.code-line-menu').length === 0) return;
+  $('.code-line-button').remove();
+  $('.code-view td.lines-code.active').closest('tr').find('td:eq(0)').first().prepend(
+    $(`<button class="code-line-button">${svg('octicon-kebab-horizontal')}</button>`)
+  );
+  $('.code-line-menu').appendTo($('.code-view'));
+  $('.code-line-button').popup({popup: $('.code-line-menu'), on: 'click'});
+}
+
+export function initRepoCodeView() {
+  if ($('.code-view .lines-num').length > 0) {
+    $(document).on('click', '.lines-num span', function (e) {
+      const $select = $(this);
+      let $list;
+      if ($('div.blame').length) {
+        $list = $('.code-view td.lines-code.blame-code');
+      } else {
+        $list = $('.code-view td.lines-code');
+      }
+      selectRange($list, $list.filter(`[rel=${$select.attr('id')}]`), (e.shiftKey ? $list.filter('.active').eq(0) : null));
+
+      if (window.getSelection) {
+        window.getSelection().removeAllRanges();
+      } else {
+        document.selection.empty();
+      }
+
+      // show code view menu marker (don't show in blame page)
+      if ($('div.blame').length === 0) {
+        showLineButton();
+      }
+    });
+
+    $(window).on('hashchange', () => {
+      let m = window.location.hash.match(/^#(L\d+)-(L\d+)$/);
+      let $list;
+      if ($('div.blame').length) {
+        $list = $('.code-view td.lines-code.blame-code');
+      } else {
+        $list = $('.code-view td.lines-code');
+      }
+      let $first;
+      if (m) {
+        $first = $list.filter(`[rel=${m[1]}]`);
+        selectRange($list, $first, $list.filter(`[rel=${m[2]}]`));
+
+        // show code view menu marker (don't show in blame page)
+        if ($('div.blame').length === 0) {
+          showLineButton();
+        }
+
+        $('html, body').scrollTop($first.offset().top - 200);
+        return;
+      }
+      m = window.location.hash.match(/^#(L|n)(\d+)$/);
+      if (m) {
+        $first = $list.filter(`[rel=L${m[2]}]`);
+        selectRange($list, $first);
+
+        // show code view menu marker (don't show in blame page)
+        if ($('div.blame').length === 0) {
+          showLineButton();
+        }
+
+        $('html, body').scrollTop($first.offset().top - 200);
+      }
+    }).trigger('hashchange');
+  }
+  $(document).on('click', '.fold-file', ({currentTarget}) => {
+    const box = currentTarget.closest('.file-content');
+    const chevron = currentTarget.querySelector('a.chevron');
+    const folded = box.dataset.folded !== 'true';
+    chevron.innerHTML = svg(`octicon-chevron-${folded ? 'right' : 'down'}`, 18);
+    box.dataset.folded = String(folded);
+  });
+  $(document).on('click', '.blob-excerpt', async ({currentTarget}) => {
+    const {url, query, anchor} = currentTarget.dataset;
+    if (!url) return;
+    const blob = await $.get(`${url}?${query}&anchor=${anchor}`);
+    currentTarget.closest('tr').outerHTML = blob;
+  });
+}
diff --git a/web_src/js/features/repo-commit.js b/web_src/js/features/repo-commit.js
new file mode 100644 (file)
index 0000000..336a37d
--- /dev/null
@@ -0,0 +1,6 @@
+export function initRepoCommitButton() {
+  $('.commit-button').on('click', function (e) {
+    e.preventDefault();
+    $(this).parent().find('.commit-body').toggle();
+  });
+}
diff --git a/web_src/js/features/repo-common.js b/web_src/js/features/repo-common.js
new file mode 100644 (file)
index 0000000..c050dba
--- /dev/null
@@ -0,0 +1,100 @@
+const {csrf} = window.config;
+
+function getArchive($target, url, first) {
+  $.ajax({
+    url,
+    type: 'POST',
+    data: {
+      _csrf: csrf,
+    },
+    complete(xhr) {
+      if (xhr.status === 200) {
+        if (!xhr.responseJSON) {
+          // XXX Shouldn't happen?
+          $target.closest('.dropdown').children('i').removeClass('loading');
+          return;
+        }
+
+        if (!xhr.responseJSON.complete) {
+          $target.closest('.dropdown').children('i').addClass('loading');
+          // Wait for only three quarters of a second initially, in case it's
+          // quickly archived.
+          setTimeout(() => {
+            getArchive($target, url, false);
+          }, first ? 750 : 2000);
+        } else {
+          // We don't need to continue checking.
+          $target.closest('.dropdown').children('i').removeClass('loading');
+          window.location.href = url;
+        }
+      }
+    },
+  });
+}
+
+export function initRepoArchiveLinks() {
+  $('.archive-link').on('click', function (event) {
+    event.preventDefault();
+    const url = $(this).data('url');
+    if (!url) return;
+    getArchive($(event.target), url, true);
+  });
+}
+
+export function initRepoClone() {
+  // Quick start and repository home
+  $('#repo-clone-ssh').on('click', function () {
+    $('.clone-url').text($(this).data('link'));
+    $('#repo-clone-url').val($(this).data('link'));
+    $(this).addClass('primary');
+    $('#repo-clone-https').removeClass('primary');
+    localStorage.setItem('repo-clone-protocol', 'ssh');
+  });
+  $('#repo-clone-https').on('click', function () {
+    $('.clone-url').text($(this).data('link'));
+    $('#repo-clone-url').val($(this).data('link'));
+    $(this).addClass('primary');
+    if ($('#repo-clone-ssh').length > 0) {
+      $('#repo-clone-ssh').removeClass('primary');
+      localStorage.setItem('repo-clone-protocol', 'https');
+    }
+  });
+  $('#repo-clone-url').on('click', function () {
+    $(this).select();
+  });
+}
+
+export function initRepoCommonBranchOrTagDropdown(selector) {
+  $(selector).each(function () {
+    const $dropdown = $(this);
+    $dropdown.find('.reference.column').on('click', function () {
+      $dropdown.find('.scrolling.reference-list-menu').hide();
+      $($(this).data('target')).show();
+      return false;
+    });
+  });
+}
+
+export function initRepoCommonFilterSearchDropdown(selector) {
+  const $dropdown = $(selector);
+  $dropdown.dropdown({
+    fullTextSearch: true,
+    selectOnKeydown: false,
+    onChange(_text, _value, $choice) {
+      if ($choice.data('url')) {
+        window.location.href = $choice.data('url');
+      }
+    },
+    message: {noResults: $dropdown.data('no-results')},
+  });
+}
+
+export function initRepoCommonLanguageStats() {
+  // Language stats
+  if ($('.language-stats').length > 0) {
+    $('.language-stats').on('click', (e) => {
+      e.preventDefault();
+      $('.language-stats-details, .repository-menu').slideToggle();
+    });
+  }
+}
diff --git a/web_src/js/features/repo-diff.js b/web_src/js/features/repo-diff.js
new file mode 100644 (file)
index 0000000..4d6a1a0
--- /dev/null
@@ -0,0 +1,81 @@
+import {initCompReactionSelector} from './comp/ReactionSelector.js';
+
+const {csrf} = window.config;
+
+export function initRepoDiffReviewButton() {
+  $(document).on('click', 'button[name="is_review"]', (e) => {
+    $(e.target).closest('form').append('<input type="hidden" name="is_review" value="true">');
+  });
+}
+
+export function initRepoDiffFileViewToggle() {
+  $('.file-view-toggle').on('click', function () {
+    const $this = $(this);
+    $this.parent().children().removeClass('active');
+    $this.addClass('active');
+
+    const $target = $($this.data('toggle-selector'));
+    $target.parent().children().addClass('hide');
+    $target.removeClass('hide');
+  });
+}
+
+export function initRepoDiffConversationForm() {
+  $('.conversation-holder form').on('submit', async (e) => {
+    e.preventDefault();
+    const form = $(e.target);
+    const newConversationHolder = $(await $.post(form.attr('action'), form.serialize()));
+    const {path, side, idx} = newConversationHolder.data();
+
+    form.closest('.conversation-holder').replaceWith(newConversationHolder);
+    if (form.closest('tr').data('line-type') === 'same') {
+      $(`a.add-code-comment[data-path="${path}"][data-idx="${idx}"]`).addClass('invisible');
+    } else {
+      $(`a.add-code-comment[data-path="${path}"][data-side="${side}"][data-idx="${idx}"]`).addClass('invisible');
+    }
+    newConversationHolder.find('.dropdown').dropdown();
+    initCompReactionSelector(newConversationHolder);
+  });
+
+
+  $('.resolve-conversation').on('click', async function (e) {
+    e.preventDefault();
+    const comment_id = $(this).data('comment-id');
+    const origin = $(this).data('origin');
+    const action = $(this).data('action');
+    const url = $(this).data('update-url');
+
+    const data = await $.post(url, {_csrf: csrf, origin, action, comment_id});
+
+    if ($(this).closest('.conversation-holder').length) {
+      const conversation = $(data);
+      $(this).closest('.conversation-holder').replaceWith(conversation);
+      conversation.find('.dropdown').dropdown();
+      initCompReactionSelector(conversation);
+    } else {
+      window.location.reload();
+    }
+  });
+}
+
+export function initRepoDiffConversationNav() {
+  // Previous/Next code review conversation
+  $(document).on('click', '.previous-conversation', (e) => {
+    const $conversation = $(e.currentTarget).closest('.comment-code-cloud');
+    const $conversations = $('.comment-code-cloud:not(.hide)');
+    const index = $conversations.index($conversation);
+    const previousIndex = index > 0 ? index - 1 : $conversations.length - 1;
+    const $previousConversation = $conversations.eq(previousIndex);
+    const anchor = $previousConversation.find('.comment').first().attr('id');
+    window.location.href = `#${anchor}`;
+  });
+  $(document).on('click', '.next-conversation', (e) => {
+    const $conversation = $(e.currentTarget).closest('.comment-code-cloud');
+    const $conversations = $('.comment-code-cloud:not(.hide)');
+    const index = $conversations.index($conversation);
+    const nextIndex = index < $conversations.length - 1 ? index + 1 : 0;
+    const $nextConversation = $conversations.eq(nextIndex);
+    const anchor = $nextConversation.find('.comment').first().attr('id');
+    window.location.href = `#${anchor}`;
+  });
+}
diff --git a/web_src/js/features/repo-editor.js b/web_src/js/features/repo-editor.js
new file mode 100644 (file)
index 0000000..831b621
--- /dev/null
@@ -0,0 +1,180 @@
+import {initMarkupContent} from '../markup/content.js';
+import {createCodeEditor} from './codeeditor.js';
+
+const {csrf} = window.config;
+
+let previewFileModes;
+
+function initEditPreviewTab($form) {
+  const $tabMenu = $form.find('.tabular.menu');
+  $tabMenu.find('.item').tab();
+  const $previewTab = $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`);
+  if ($previewTab.length) {
+    previewFileModes = $previewTab.data('preview-file-modes').split(',');
+    $previewTab.on('click', function () {
+      const $this = $(this);
+      let context = `${$this.data('context')}/`;
+      const mode = $this.data('markdown-mode') || 'comment';
+      const treePathEl = $form.find('input#tree_path');
+      if (treePathEl.length > 0) {
+        context += treePathEl.val();
+      }
+      context = context.substring(0, context.lastIndexOf('/'));
+      $.post($this.data('url'), {
+        _csrf: csrf,
+        mode,
+        context,
+        text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val()
+      }, (data) => {
+        const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`);
+        $previewPanel.html(data);
+        initMarkupContent();
+      });
+    });
+  }
+}
+
+function initEditDiffTab($form) {
+  const $tabMenu = $form.find('.tabular.menu');
+  $tabMenu.find('.item').tab();
+  $tabMenu.find(`.item[data-tab="${$tabMenu.data('diff')}"]`).on('click', function () {
+    const $this = $(this);
+    $.post($this.data('url'), {
+      _csrf: csrf,
+      context: $this.data('context'),
+      content: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val()
+    }, (data) => {
+      const $diffPreviewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('diff')}"]`);
+      $diffPreviewPanel.html(data);
+    });
+  });
+}
+
+function initEditorForm() {
+  if ($('.repository .edit.form').length === 0) {
+    return;
+  }
+
+  initEditPreviewTab($('.repository .edit.form'));
+  initEditDiffTab($('.repository .edit.form'));
+}
+
+
+function getCursorPosition($e) {
+  const el = $e.get(0);
+  let pos = 0;
+  if ('selectionStart' in el) {
+    pos = el.selectionStart;
+  } else if ('selection' in document) {
+    el.focus();
+    const Sel = document.selection.createRange();
+    const SelLength = document.selection.createRange().text.length;
+    Sel.moveStart('character', -el.value.length);
+    pos = Sel.text.length - SelLength;
+  }
+  return pos;
+}
+
+export async function initRepoEditor() {
+  initEditorForm();
+
+  $('.js-quick-pull-choice-option').on('change', function () {
+    if ($(this).val() === 'commit-to-new-branch') {
+      $('.quick-pull-branch-name').show();
+      $('.quick-pull-branch-name input').prop('required', true);
+    } else {
+      $('.quick-pull-branch-name').hide();
+      $('.quick-pull-branch-name input').prop('required', false);
+    }
+    $('#commit-button').text($(this).attr('button_text'));
+  });
+
+  const $editFilename = $('#file-name');
+  $editFilename.on('keyup', function (e) {
+    const $section = $('.breadcrumb span.section');
+    const $divider = $('.breadcrumb div.divider');
+    let value;
+    let parts;
+
+    if (e.keyCode === 8 && getCursorPosition($(this)) === 0 && $section.length > 0) {
+      value = $section.last().find('a').text();
+      $(this).val(value + $(this).val());
+      $(this)[0].setSelectionRange(value.length, value.length);
+      $section.last().remove();
+      $divider.last().remove();
+    }
+    if (e.keyCode === 191) {
+      parts = $(this).val().split('/');
+      for (let i = 0; i < parts.length; ++i) {
+        value = parts[i];
+        if (i < parts.length - 1) {
+          if (value.length) {
+            $(`<span class="section"><a href="#">${value}</a></span>`).insertBefore($(this));
+            $('<div class="divider"> / </div>').insertBefore($(this));
+          }
+        } else {
+          $(this).val(value);
+        }
+        $(this)[0].setSelectionRange(0, 0);
+      }
+    }
+    parts = [];
+    $('.breadcrumb span.section').each(function () {
+      const element = $(this);
+      if (element.find('a').length) {
+        parts.push(element.find('a').text());
+      } else {
+        parts.push(element.text());
+      }
+    });
+    if ($(this).val()) parts.push($(this).val());
+    $('#tree_path').val(parts.join('/'));
+  }).trigger('keyup');
+
+  const $editArea = $('.repository.editor textarea#edit_area');
+  if (!$editArea.length) return;
+
+  const editor = await createCodeEditor($editArea[0], $editFilename[0], previewFileModes);
+
+  // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
+  // to enable or disable the commit button
+  const $commitButton = $('#commit-button');
+  const $editForm = $('.ui.edit.form');
+  const dirtyFileClass = 'dirty-file';
+
+  // Disabling the button at the start
+  if ($('input[name="page_has_posted"]').val() !== 'true') {
+    $commitButton.prop('disabled', true);
+  }
+
+  // Registering a custom listener for the file path and the file content
+  $editForm.areYouSure({
+    silent: true,
+    dirtyClass: dirtyFileClass,
+    fieldSelector: ':input:not(.commit-form-wrapper :input)',
+    change() {
+      const dirty = $(this).hasClass(dirtyFileClass);
+      $commitButton.prop('disabled', !dirty);
+    }
+  });
+
+  // Update the editor from query params, if available,
+  // only after the dirtyFileClass initialization
+  const params = new URLSearchParams(window.location.search);
+  const value = params.get('value');
+  if (value) {
+    editor.setValue(value);
+  }
+
+  $commitButton.on('click', (event) => {
+    // A modal which asks if an empty file should be committed
+    if ($editArea.val().length === 0) {
+      $('#edit-empty-content-modal').modal({
+        onApprove() {
+          $('.edit.form').trigger('submit');
+        }
+      }).modal('show');
+      event.preventDefault();
+    }
+  });
+}
diff --git a/web_src/js/features/repo-home.js b/web_src/js/features/repo-home.js
new file mode 100644 (file)
index 0000000..142698f
--- /dev/null
@@ -0,0 +1,180 @@
+import {stripTags} from '../utils.js';
+
+const {AppSubUrl, csrf} = window.config;
+
+export function initRepoTopicBar() {
+  const mgrBtn = $('#manage_topic');
+  const editDiv = $('#topic_edit');
+  const viewDiv = $('#repo-topics');
+  const saveBtn = $('#save_topic');
+  const topicDropdown = $('#topic_edit .dropdown');
+  const topicForm = $('#topic_edit.ui.form');
+  const topicPrompts = getPrompts();
+
+  mgrBtn.on('click', () => {
+    viewDiv.hide();
+    editDiv.css('display', ''); // show Semantic UI Grid
+  });
+
+  function getPrompts() {
+    const hidePrompt = $('div.hide#validate_prompt');
+    const prompts = {
+      countPrompt: hidePrompt.children('#count_prompt').text(),
+      formatPrompt: hidePrompt.children('#format_prompt').text()
+    };
+    hidePrompt.remove();
+    return prompts;
+  }
+
+  saveBtn.on('click', () => {
+    const topics = $('input[name=topics]').val();
+
+    $.post(saveBtn.data('link'), {
+      _csrf: csrf,
+      topics
+    }, (_data, _textStatus, xhr) => {
+      if (xhr.responseJSON.status === 'ok') {
+        viewDiv.children('.topic').remove();
+        if (topics.length) {
+          const topicArray = topics.split(',');
+
+          const last = viewDiv.children('a').last();
+          for (let i = 0; i < topicArray.length; i++) {
+            const link = $('<a class="ui repo-topic large label topic"></a>');
+            link.attr('href', `${AppSubUrl}/explore/repos?q=${encodeURIComponent(topicArray[i])}&topic=1`);
+            link.text(topicArray[i]);
+            link.insertBefore(last);
+          }
+        }
+        editDiv.css('display', 'none');
+        viewDiv.show();
+      }
+    }).fail((xhr) => {
+      if (xhr.status === 422) {
+        if (xhr.responseJSON.invalidTopics.length > 0) {
+          topicPrompts.formatPrompt = xhr.responseJSON.message;
+
+          const {invalidTopics} = xhr.responseJSON;
+          const topicLables = topicDropdown.children('a.ui.label');
+
+          topics.split(',').forEach((value, index) => {
+            for (let i = 0; i < invalidTopics.length; i++) {
+              if (invalidTopics[i] === value) {
+                topicLables.eq(index).removeClass('green').addClass('red');
+              }
+            }
+          });
+        } else {
+          topicPrompts.countPrompt = xhr.responseJSON.message;
+        }
+      }
+    }).always(() => {
+      topicForm.form('validate form');
+    });
+  });
+
+  topicDropdown.dropdown({
+    allowAdditions: true,
+    forceSelection: false,
+    fullTextSearch: 'exact',
+    fields: {name: 'description', value: 'data-value'},
+    saveRemoteData: false,
+    label: {
+      transition: 'horizontal flip',
+      duration: 200,
+      variation: false,
+      blue: true,
+      basic: true,
+    },
+    className: {
+      label: 'ui small label'
+    },
+    apiSettings: {
+      url: `${AppSubUrl}/api/v1/topics/search?q={query}`,
+      throttle: 500,
+      cache: false,
+      onResponse(res) {
+        const formattedResponse = {
+          success: false,
+          results: [],
+        };
+        const query = stripTags(this.urlData.query.trim());
+        let found_query = false;
+        const current_topics = [];
+        topicDropdown.find('div.label.visible.topic,a.label.visible').each((_, e) => { current_topics.push(e.dataset.value) });
+
+        if (res.topics) {
+          let found = false;
+          for (let i = 0; i < res.topics.length; i++) {
+            // skip currently added tags
+            if (current_topics.includes(res.topics[i].topic_name)) {
+              continue;
+            }
+
+            if (res.topics[i].topic_name.toLowerCase() === query.toLowerCase()) {
+              found_query = true;
+            }
+            formattedResponse.results.push({description: res.topics[i].topic_name, 'data-value': res.topics[i].topic_name});
+            found = true;
+          }
+          formattedResponse.success = found;
+        }
+
+        if (query.length > 0 && !found_query) {
+          formattedResponse.success = true;
+          formattedResponse.results.unshift({description: query, 'data-value': query});
+        } else if (query.length > 0 && found_query) {
+          formattedResponse.results.sort((a, b) => {
+            if (a.description.toLowerCase() === query.toLowerCase()) return -1;
+            if (b.description.toLowerCase() === query.toLowerCase()) return 1;
+            if (a.description > b.description) return -1;
+            if (a.description < b.description) return 1;
+            return 0;
+          });
+        }
+
+        return formattedResponse;
+      },
+    },
+    onLabelCreate(value) {
+      value = value.toLowerCase().trim();
+      this.attr('data-value', value).contents().first().replaceWith(value);
+      return $(this);
+    },
+    onAdd(addedValue, _addedText, $addedChoice) {
+      addedValue = addedValue.toLowerCase().trim();
+      $($addedChoice).attr('data-value', addedValue);
+      $($addedChoice).attr('data-text', addedValue);
+    }
+  });
+
+  $.fn.form.settings.rules.validateTopic = function (_values, regExp) {
+    const topics = topicDropdown.children('a.ui.label');
+    const status = topics.length === 0 || topics.last().attr('data-value').match(regExp);
+    if (!status) {
+      topics.last().removeClass('green').addClass('red');
+    }
+    return status && topicDropdown.children('a.ui.label.red').length === 0;
+  };
+
+  topicForm.form({
+    on: 'change',
+    inline: true,
+    fields: {
+      topics: {
+        identifier: 'topics',
+        rules: [
+          {
+            type: 'validateTopic',
+            value: /^[a-z0-9][a-z0-9-]{0,35}$/,
+            prompt: topicPrompts.formatPrompt
+          },
+          {
+            type: 'maxCount[25]',
+            prompt: topicPrompts.countPrompt
+          }
+        ]
+      },
+    }
+  });
+}
diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js
new file mode 100644 (file)
index 0000000..858398a
--- /dev/null
@@ -0,0 +1,638 @@
+import {htmlEscape} from 'escape-goat';
+import attachTribute from './tribute.js';
+import {createCommentSimpleMDE} from './comp/CommentSimpleMDE.js';
+import {initCompImagePaste} from './comp/ImagePaste.js';
+import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js';
+
+const {AppSubUrl, csrf} = window.config;
+
+export function initRepoIssueTimeTracking() {
+  $(document).on('click', '.issue-add-time', () => {
+    $('.issue-start-time-modal').modal({
+      duration: 200,
+      onApprove() {
+        $('#add_time_manual_form').trigger('submit');
+      },
+    }).modal('show');
+    $('.issue-start-time-modal input').on('keydown', (e) => {
+      if ((e.keyCode || e.key) === 13) {
+        $('#add_time_manual_form').trigger('submit');
+      }
+    });
+  });
+  $(document).on('click', '.issue-start-time, .issue-stop-time', () => {
+    $('#toggle_stopwatch_form').trigger('submit');
+  });
+  $(document).on('click', '.issue-cancel-time', () => {
+    $('#cancel_stopwatch_form').trigger('submit');
+  });
+  $(document).on('click', 'button.issue-delete-time', function () {
+    const sel = `.issue-delete-time-modal[data-id="${$(this).data('id')}"]`;
+    $(sel).modal({
+      duration: 200,
+      onApprove() {
+        $(`${sel} form`).trigger('submit');
+      },
+    }).modal('show');
+  });
+}
+
+function updateDeadline(deadlineString) {
+  $('#deadline-err-invalid-date').hide();
+  $('#deadline-loader').addClass('loading');
+
+  let realDeadline = null;
+  if (deadlineString !== '') {
+    const newDate = Date.parse(deadlineString);
+
+    if (Number.isNaN(newDate)) {
+      $('#deadline-loader').removeClass('loading');
+      $('#deadline-err-invalid-date').show();
+      return false;
+    }
+    realDeadline = new Date(newDate);
+  }
+
+  $.ajax(`${$('#update-issue-deadline-form').attr('action')}/deadline`, {
+    data: JSON.stringify({
+      due_date: realDeadline,
+    }),
+    headers: {
+      'X-Csrf-Token': csrf,
+      'X-Remote': true,
+    },
+    contentType: 'application/json',
+    type: 'POST',
+    success() {
+      window.location.reload();
+    },
+    error() {
+      $('#deadline-loader').removeClass('loading');
+      $('#deadline-err-invalid-date').show();
+    },
+  });
+}
+
+export function initRepoIssueDue() {
+  $(document).on('click', '.issue-due-edit', () => {
+    $('#deadlineForm').fadeToggle(150);
+  });
+  $(document).on('click', '.issue-due-remove', () => {
+    updateDeadline('');
+  });
+  $(document).on('submit', '.issue-due-form', () => {
+    updateDeadline($('#deadlineDate').val());
+    return false;
+  });
+}
+
+export function initRepoIssueList() {
+  const repolink = $('#repolink').val();
+  const repoId = $('#repoId').val();
+  const crossRepoSearch = $('#crossRepoSearch').val();
+  const tp = $('#type').val();
+  let issueSearchUrl = `${AppSubUrl}/api/v1/repos/${repolink}/issues?q={query}&type=${tp}`;
+  if (crossRepoSearch === 'true') {
+    issueSearchUrl = `${AppSubUrl}/api/v1/repos/issues/search?q={query}&priority_repo_id=${repoId}&type=${tp}`;
+  }
+  $('#new-dependency-drop-list')
+    .dropdown({
+      apiSettings: {
+        url: issueSearchUrl,
+        onResponse(response) {
+          const filteredResponse = {success: true, results: []};
+          const currIssueId = $('#new-dependency-drop-list').data('issue-id');
+          // Parse the response from the api to work with our dropdown
+          $.each(response, (_i, issue) => {
+            // Don't list current issue in the dependency list.
+            if (issue.id === currIssueId) {
+              return;
+            }
+            filteredResponse.results.push({
+              name: `#${issue.number} ${htmlEscape(issue.title)
+              }<div class="text small dont-break-out">${htmlEscape(issue.repository.full_name)}</div>`,
+              value: issue.id,
+            });
+          });
+          return filteredResponse;
+        },
+        cache: false,
+      },
+
+      fullTextSearch: true,
+    });
+
+  function excludeLabel(item) {
+    const href = $(item).attr('href');
+    const id = $(item).data('label-id');
+
+    const regStr = `labels=((?:-?[0-9]+%2c)*)(${id})((?:%2c-?[0-9]+)*)&`;
+    const newStr = 'labels=$1-$2$3&';
+
+    window.location = href.replace(new RegExp(regStr), newStr);
+  }
+
+  $('.menu a.label-filter-item').each(function () {
+    $(this).on('click', function (e) {
+      if (e.altKey) {
+        e.preventDefault();
+        excludeLabel(this);
+      }
+    });
+  });
+
+  $('.menu .ui.dropdown.label-filter').on('keydown', (e) => {
+    if (e.altKey && e.keyCode === 13) {
+      const selectedItems = $('.menu .ui.dropdown.label-filter .menu .item.selected');
+      if (selectedItems.length > 0) {
+        excludeLabel($(selectedItems[0]));
+      }
+    }
+  });
+}
+
+export function initRepoIssueCommentDelete() {
+  // Delete comment
+  $(document).on('click', '.delete-comment', function () {
+    const $this = $(this);
+    if (window.confirm($this.data('locale'))) {
+      $.post($this.data('url'), {
+        _csrf: csrf,
+      }).done(() => {
+        const $conversationHolder = $this.closest('.conversation-holder');
+        $(`#${$this.data('comment-id')}`).remove();
+        if ($conversationHolder.length && !$conversationHolder.find('.comment').length) {
+          const path = $conversationHolder.data('path');
+          const side = $conversationHolder.data('side');
+          const idx = $conversationHolder.data('idx');
+          const lineType = $conversationHolder.closest('tr').data('line-type');
+          if (lineType === 'same') {
+            $(`a.add-code-comment[data-path="${path}"][data-idx="${idx}"]`).removeClass('invisible');
+          } else {
+            $(`a.add-code-comment[data-path="${path}"][data-side="${side}"][data-idx="${idx}"]`).removeClass('invisible');
+          }
+          $conversationHolder.remove();
+        }
+      });
+    }
+    return false;
+  });
+}
+
+export function initRepoIssueDependencyDelete() {
+  // Delete Issue dependency
+  $(document).on('click', '.delete-dependency-button', (e) => {
+    const {id, type} = e.currentTarget.dataset;
+
+    $('.remove-dependency').modal({
+      closable: false,
+      duration: 200,
+      onApprove: () => {
+        $('#removeDependencyID').val(id);
+        $('#dependencyType').val(type);
+        $('#removeDependencyForm').trigger('submit');
+      },
+    }).modal('show');
+  });
+}
+
+export function initRepoIssueCodeCommentCancel() {
+  // Cancel inline code comment
+  $(document).on('click', '.cancel-code-comment', (e) => {
+    const form = $(e.currentTarget).closest('form');
+    if (form.length > 0 && form.hasClass('comment-form')) {
+      form.addClass('hide');
+      form.closest('.comment-code-cloud').find('button.comment-form-reply').show();
+    } else {
+      form.closest('.comment-code-cloud').remove();
+    }
+  });
+}
+
+export function initRepoIssueStatusButton() {
+  // Change status
+  const $statusButton = $('#status-button');
+  $('#comment-form textarea').on('keyup', function () {
+    const $simplemde = $(this).data('simplemde');
+    const value = ($simplemde && $simplemde.value()) ? $simplemde.value() : $(this).val();
+    $statusButton.text($statusButton.data(value.length === 0 ? 'status' : 'status-and-comment'));
+  });
+  $statusButton.on('click', () => {
+    $('#status').val($statusButton.data('status-val'));
+    $('#comment-form').trigger('submit');
+  });
+}
+
+export function initRepoPullRequestMerge() {
+  // Pull Request merge button
+  const $mergeButton = $('.merge-button > button');
+  $mergeButton.on('click', function (e) {
+    e.preventDefault();
+    $(`.${$(this).data('do')}-fields`).show();
+    $(this).parent().hide();
+    $('.instruct-toggle').hide();
+    $('.instruct-content').hide();
+  });
+  $('.merge-button > .dropdown').dropdown({
+    onChange(_text, _value, $choice) {
+      if ($choice.data('do')) {
+        $mergeButton.find('.button-text').text($choice.text());
+        $mergeButton.data('do', $choice.data('do'));
+      }
+    }
+  });
+  $('.merge-cancel').on('click', function (e) {
+    e.preventDefault();
+    $(this).closest('.form').hide();
+    $mergeButton.parent().show();
+    $('.instruct-toggle').show();
+  });
+}
+
+export function initRepoPullRequestUpdate() {
+  // Pull Request update button
+  const $pullUpdateButton = $('.update-button > button');
+  $pullUpdateButton.on('click', function (e) {
+    e.preventDefault();
+    const $this = $(this);
+    const redirect = $this.data('redirect');
+    $this.addClass('loading');
+    $.post($this.data('do'), {
+      _csrf: csrf
+    }).done((data) => {
+      if (data.redirect) {
+        window.location.href = data.redirect;
+      } else if (redirect) {
+        window.location.href = redirect;
+      } else {
+        window.location.reload();
+      }
+    });
+  });
+
+  $('.update-button > .dropdown').dropdown({
+    onChange(_text, _value, $choice) {
+      const $url = $choice.data('do');
+      if ($url) {
+        $pullUpdateButton.find('.button-text').text($choice.text());
+        $pullUpdateButton.data('do', $url);
+      }
+    }
+  });
+}
+
+export function initRepoPullRequestMergeInstruction() {
+  $('.show-instruction').on('click', () => {
+    $('.instruct-content').toggle();
+  });
+}
+
+export function initRepoIssueReferenceRepositorySearch() {
+  $('.issue_reference_repository_search')
+    .dropdown({
+      apiSettings: {
+        url: `${AppSubUrl}/api/v1/repos/search?q={query}&limit=20`,
+        onResponse(response) {
+          const filteredResponse = {success: true, results: []};
+          $.each(response.data, (_r, repo) => {
+            filteredResponse.results.push({
+              name: htmlEscape(repo.full_name),
+              value: repo.full_name
+            });
+          });
+          return filteredResponse;
+        },
+        cache: false,
+      },
+      onChange(_value, _text, $choice) {
+        const $form = $choice.closest('form');
+        $form.attr('action', `${AppSubUrl}/${_text}/issues/new`);
+      },
+      fullTextSearch: true
+    });
+}
+
+
+export function initRepoIssueWipTitle() {
+  $('.title_wip_desc > a').on('click', (e) => {
+    e.preventDefault();
+
+    const $issueTitle = $('#issue_title');
+    $issueTitle.focus();
+    const value = $issueTitle.val().trim().toUpperCase();
+
+    const wipPrefixes = $('.title_wip_desc').data('wip-prefixes');
+    for (const prefix of wipPrefixes) {
+      if (value.startsWith(prefix.toUpperCase())) {
+        return;
+      }
+    }
+
+    $issueTitle.val(`${wipPrefixes[0]} ${$issueTitle.val()}`);
+  });
+}
+
+export function updateIssuesMeta(url, action, issueIds, elementId) {
+  return new Promise((resolve, reject) => {
+    $.ajax({
+      type: 'POST',
+      url,
+      data: {
+        _csrf: csrf,
+        action,
+        issue_ids: issueIds,
+        id: elementId,
+      },
+      success: resolve,
+      error: reject,
+    });
+  });
+}
+
+export function initRepoIssueComments() {
+  if ($('.repository.view.issue .timeline').length === 0) return;
+
+  $('.re-request-review').on('click', function (event) {
+    const url = $(this).data('update-url');
+    const issueId = $(this).data('issue-id');
+    const id = $(this).data('id');
+    const isChecked = $(this).hasClass('checked');
+
+    event.preventDefault();
+    updateIssuesMeta(
+      url,
+      isChecked ? 'detach' : 'attach',
+      issueId,
+      id,
+    ).then(() => {
+      window.location.reload();
+    });
+    return false;
+  });
+
+  $('.dismiss-review-btn').on('click', function (e) {
+    e.preventDefault();
+    const $this = $(this);
+    const $dismissReviewModal = $this.next();
+    $dismissReviewModal.modal('show');
+  });
+
+  $(document).on('click', (event) => {
+    const urlTarget = $(':target');
+    if (urlTarget.length === 0) return;
+
+    const urlTargetId = urlTarget.attr('id');
+    if (!urlTargetId) return;
+    if (!/^(issue|pull)(comment)?-\d+$/.test(urlTargetId)) return;
+
+    const $target = $(event.target);
+
+    if ($target.closest(`#${urlTargetId}`).length === 0) {
+      const scrollPosition = $(window).scrollTop();
+      window.location.hash = '';
+      $(window).scrollTop(scrollPosition);
+      window.history.pushState(null, null, ' ');
+    }
+  });
+}
+
+
+function assignMenuAttributes(menu) {
+  const id = Math.floor(Math.random() * Math.floor(1000000));
+  menu.attr('data-write', menu.attr('data-write') + id);
+  menu.attr('data-preview', menu.attr('data-preview') + id);
+  menu.find('.item').each(function () {
+    const tab = $(this).attr('data-tab') + id;
+    $(this).attr('data-tab', tab);
+  });
+  menu.parent().find("*[data-tab='write']").attr('data-tab', `write${id}`);
+  menu.parent().find("*[data-tab='preview']").attr('data-tab', `preview${id}`);
+  initCompMarkupContentPreviewTab(menu.parent('.form'));
+  return id;
+}
+
+export function initRepoPullRequestReview() {
+  if (window.location.hash && window.location.hash.startsWith('#issuecomment-')) {
+    const commentDiv = $(window.location.hash);
+    if (commentDiv) {
+      // get the name of the parent id
+      const groupID = commentDiv.closest('div[id^="code-comments-"]').attr('id');
+      if (groupID && groupID.startsWith('code-comments-')) {
+        const id = groupID.substr(14);
+        $(`#show-outdated-${id}`).addClass('hide');
+        $(`#code-comments-${id}`).removeClass('hide');
+        $(`#code-preview-${id}`).removeClass('hide');
+        $(`#hide-outdated-${id}`).removeClass('hide');
+        commentDiv[0].scrollIntoView();
+      }
+    }
+  }
+
+  $(document).on('click', '.show-outdated', function (e) {
+    e.preventDefault();
+    const id = $(this).data('comment');
+    $(this).addClass('hide');
+    $(`#code-comments-${id}`).removeClass('hide');
+    $(`#code-preview-${id}`).removeClass('hide');
+    $(`#hide-outdated-${id}`).removeClass('hide');
+  });
+
+  $(document).on('click', '.hide-outdated', function (e) {
+    e.preventDefault();
+    const id = $(this).data('comment');
+    $(this).addClass('hide');
+    $(`#code-comments-${id}`).addClass('hide');
+    $(`#code-preview-${id}`).addClass('hide');
+    $(`#show-outdated-${id}`).removeClass('hide');
+  });
+
+  $(document).on('click', 'button.comment-form-reply', function (e) {
+    e.preventDefault();
+    $(this).hide();
+    const form = $(this).closest('.comment-code-cloud').find('.comment-form');
+    form.removeClass('hide');
+    const $textarea = form.find('textarea');
+    let $simplemde;
+    if ($textarea.data('simplemde')) {
+      $simplemde = $textarea.data('simplemde');
+    } else {
+      attachTribute($textarea.get(), {mentions: true, emoji: true});
+      $simplemde = createCommentSimpleMDE($textarea);
+      $textarea.data('simplemde', $simplemde);
+    }
+    $textarea.focus();
+    $simplemde.codemirror.focus();
+    assignMenuAttributes(form.find('.menu'));
+  });
+
+  const $reviewBox = $('.review-box');
+  if ($reviewBox.length === 1) {
+    createCommentSimpleMDE($reviewBox.find('textarea'));
+    initCompImagePaste($reviewBox);
+  }
+
+  // The following part is only for diff views
+  if ($('.repository.pull.diff').length === 0) {
+    return;
+  }
+
+  $('.btn-review').on('click', function (e) {
+    e.preventDefault();
+    $(this).closest('.dropdown').find('.menu').toggle('visible');
+  }).closest('.dropdown').find('.close').on('click', function (e) {
+    e.preventDefault();
+    $(this).closest('.menu').toggle('visible');
+  });
+
+  $('a.add-code-comment').on('click', async function (e) {
+    if ($(e.target).hasClass('btn-add-single')) return; // https://github.com/go-gitea/gitea/issues/4745
+    e.preventDefault();
+
+    const isSplit = $(this).closest('.code-diff').hasClass('code-diff-split');
+    const side = $(this).data('side');
+    const idx = $(this).data('idx');
+    const path = $(this).data('path');
+    const tr = $(this).closest('tr');
+    const lineType = tr.data('line-type');
+
+    let ntr = tr.next();
+    if (!ntr.hasClass('add-comment')) {
+      ntr = $(`
+        <tr class="add-comment" data-line-type="${lineType}">
+          ${isSplit ? `
+            <td class="lines-num"></td>
+            <td class="lines-type-marker"></td>
+            <td class="add-comment-left"></td>
+            <td class="lines-num"></td>
+            <td class="lines-type-marker"></td>
+            <td class="add-comment-right"></td>
+          ` : `
+            <td colspan="2" class="lines-num"></td>
+            <td class="add-comment-left add-comment-right" colspan="2"></td>
+          `}
+        </tr>`);
+      tr.after(ntr);
+    }
+
+    const td = ntr.find(`.add-comment-${side}`);
+    let commentCloud = td.find('.comment-code-cloud');
+    if (commentCloud.length === 0 && !ntr.find('button[name="is_review"]').length) {
+      const data = await $.get($(this).data('new-comment-url'));
+      td.html(data);
+      commentCloud = td.find('.comment-code-cloud');
+      assignMenuAttributes(commentCloud.find('.menu'));
+      td.find("input[name='line']").val(idx);
+      td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed');
+      td.find("input[name='path']").val(path);
+      const $textarea = commentCloud.find('textarea');
+      attachTribute($textarea.get(), {mentions: true, emoji: true});
+      const $simplemde = createCommentSimpleMDE($textarea);
+      $textarea.focus();
+      $simplemde.codemirror.focus();
+    }
+  });
+}
+
+export function initRepoIssueReferenceIssue() {
+  // Reference issue
+  $(document).on('click', '.reference-issue', function (event) {
+    const $this = $(this);
+    $this.closest('.dropdown').find('.menu').toggle('visible');
+
+    const content = $(`#comment-${$this.data('target')}`).text();
+    const poster = $this.data('poster-username');
+    const reference = $this.data('reference');
+    const $modal = $($this.data('modal'));
+    $modal.find('textarea[name="content"]').val(`${content}\n\n_Originally posted by @${poster} in ${reference}_`);
+    $modal.modal('show');
+
+    event.preventDefault();
+  });
+}
+
+export function initRepoIssueWipToggle() {
+  // Toggle WIP
+  $('.toggle-wip a, .toggle-wip button').on('click', async (e) => {
+    e.preventDefault();
+    const {title, wipPrefix, updateUrl} = e.currentTarget.closest('.toggle-wip').dataset;
+    await $.post(updateUrl, {
+      _csrf: csrf,
+      title: title?.startsWith(wipPrefix) ? title.substr(wipPrefix.length).trim() : `${wipPrefix.trim()} ${title}`,
+    });
+    window.location.reload();
+  });
+}
+
+
+export function initRepoIssueTitleEdit() {
+  // Edit issue title
+  const $issueTitle = $('#issue-title');
+  const $editInput = $('#edit-title-input input');
+
+  const editTitleToggle = function () {
+    $issueTitle.toggle();
+    $('.not-in-edit').toggle();
+    $('#edit-title-input').toggle();
+    $('#pull-desc').toggle();
+    $('#pull-desc-edit').toggle();
+    $('.in-edit').toggle();
+    $('#issue-title-wrapper').toggleClass('edit-active');
+    $editInput.focus();
+    return false;
+  };
+
+  $('#edit-title').on('click', editTitleToggle);
+  $('#cancel-edit-title').on('click', editTitleToggle);
+  $('#save-edit-title').on('click', editTitleToggle).on('click', function () {
+    const pullrequest_targetbranch_change = function (update_url) {
+      const targetBranch = $('#pull-target-branch').data('branch');
+      const $branchTarget = $('#branch_target');
+      if (targetBranch === $branchTarget.text()) {
+        return false;
+      }
+      $.post(update_url, {
+        _csrf: csrf,
+        target_branch: targetBranch
+      }).done((data) => {
+        $branchTarget.text(data.base_branch);
+      }).always(() => {
+        window.location.reload();
+      });
+    };
+
+    const pullrequest_target_update_url = $(this).data('target-update-url');
+    if ($editInput.val().length === 0 || $editInput.val() === $issueTitle.text()) {
+      $editInput.val($issueTitle.text());
+      pullrequest_targetbranch_change(pullrequest_target_update_url);
+    } else {
+      $.post($(this).data('update-url'), {
+        _csrf: csrf,
+        title: $editInput.val()
+      }, (data) => {
+        $editInput.val(data.title);
+        $issueTitle.text(data.title);
+        pullrequest_targetbranch_change(pullrequest_target_update_url);
+        window.location.reload();
+      });
+    }
+    return false;
+  });
+}
+
+export function initRepoIssueBranchSelect() {
+  const changeBranchSelect = function () {
+    const selectionTextField = $('#pull-target-branch');
+
+    const baseName = selectionTextField.data('basename');
+    const branchNameNew = $(this).data('branch');
+    const branchNameOld = selectionTextField.data('branch');
+
+    // Replace branch name to keep translation from HTML template
+    selectionTextField.html(selectionTextField.html().replace(
+      `${baseName}:${branchNameOld}`,
+      `${baseName}:${branchNameNew}`
+    ));
+    selectionTextField.data('branch', branchNameNew); // update branch name in setting
+  };
+  $('#branch-select > .item').on('click', changeBranchSelect);
+}
diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js
new file mode 100644 (file)
index 0000000..a1c405d
--- /dev/null
@@ -0,0 +1,574 @@
+import {createCommentSimpleMDE} from './comp/CommentSimpleMDE.js';
+import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js';
+import {initCompImagePaste, initSimpleMDEImagePaste} from './comp/ImagePaste.js';
+import {
+  initRepoIssueBranchSelect, initRepoIssueCodeCommentCancel,
+  initRepoIssueCommentDelete,
+  initRepoIssueComments, initRepoIssueDependencyDelete,
+  initRepoIssueReferenceIssue, initRepoIssueStatusButton,
+  initRepoIssueTitleEdit,
+  initRepoIssueWipToggle, initRepoPullRequestMerge, initRepoPullRequestUpdate,
+  updateIssuesMeta,
+} from './repo-issue.js';
+import {svg} from '../svg.js';
+import {htmlEscape} from 'escape-goat';
+import {initRepoBranchTagDropdown} from '../components/RepoBranchTagDropdown.js';
+import {
+  initRepoClone,
+  initRepoCommonBranchOrTagDropdown,
+  initRepoCommonFilterSearchDropdown,
+  initRepoCommonLanguageStats,
+} from './repo-common.js';
+import {initCompLabelEdit} from './comp/LabelEdit.js';
+import {initRepoDiffConversationNav} from './repo-diff.js';
+import attachTribute from './tribute.js';
+import createDropzone from './dropzone.js';
+import {initCommentContent, initMarkupContent} from '../markup/content.js';
+import {initCompReactionSelector} from './comp/ReactionSelector.js';
+import {initRepoSettingBranches} from './repo-settings.js';
+
+const {csrf} = window.config;
+
+const commentMDEditors = {};
+
+// FIXME: the usage of `autoSimpleMDE` is quite messy, the refactor should be done very carefully in future.
+let autoSimpleMDE;
+
+export function initRepoCommentForm() {
+  if ($('.comment.form').length === 0) {
+    return;
+  }
+
+  function initBranchSelector() {
+    const $selectBranch = $('.ui.select-branch');
+    const $branchMenu = $selectBranch.find('.reference-list-menu');
+    const $isNewIssue = $branchMenu.hasClass('new-issue');
+    $branchMenu.find('.item:not(.no-select)').click(function () {
+      const selectedValue = $(this).data('id');
+      const editMode = $('#editing_mode').val();
+      $($(this).data('id-selector')).val(selectedValue);
+      if ($isNewIssue) {
+        $selectBranch.find('.ui .branch-name').text($(this).data('name'));
+        return;
+      }
+
+      if (editMode === 'true') {
+        const form = $('#update_issueref_form');
+        $.post(form.attr('action'), {_csrf: csrf, ref: selectedValue}, () => window.location.reload());
+      } else if (editMode === '') {
+        $selectBranch.find('.ui .branch-name').text(selectedValue);
+      }
+    });
+    $selectBranch.find('.reference.column').on('click', function () {
+      $selectBranch.find('.scrolling.reference-list-menu').css('display', 'none');
+      $selectBranch.find('.reference .text').removeClass('black');
+      $($(this).data('target')).css('display', 'block');
+      $(this).find('.text').addClass('black');
+      return false;
+    });
+  }
+
+  autoSimpleMDE = createCommentSimpleMDE($('.comment.form textarea:not(.review-textarea)'));
+  initBranchSelector();
+  initCompMarkupContentPreviewTab($('.comment.form'));
+  initCompImagePaste($('.comment.form'));
+
+  // Listsubmit
+  function initListSubmits(selector, outerSelector) {
+    const $list = $(`.ui.${outerSelector}.list`);
+    const $noSelect = $list.find('.no-select');
+    const $listMenu = $(`.${selector} .menu`);
+    let hasUpdateAction = $listMenu.data('action') === 'update';
+    const items = {};
+
+    $(`.${selector}`).dropdown('setting', 'onHide', () => {
+      hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
+      if (hasUpdateAction) {
+        const promises = [];
+        Object.keys(items).forEach((elementId) => {
+          const item = items[elementId];
+          const promise = updateIssuesMeta(
+            item['update-url'],
+            item.action,
+            item['issue-id'],
+            elementId,
+          );
+          promises.push(promise);
+        });
+        Promise.all(promises).then(() => window.location.reload());
+      }
+    });
+
+    $listMenu.find('.item:not(.no-select)').on('click', function (e) {
+      e.preventDefault();
+      if ($(this).hasClass('ban-change')) {
+        return false;
+      }
+
+      hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
+      if ($(this).hasClass('checked')) {
+        $(this).removeClass('checked');
+        $(this).find('.octicon-check').addClass('invisible');
+        if (hasUpdateAction) {
+          if (!($(this).data('id') in items)) {
+            items[$(this).data('id')] = {
+              'update-url': $listMenu.data('update-url'),
+              action: 'detach',
+              'issue-id': $listMenu.data('issue-id'),
+            };
+          } else {
+            delete items[$(this).data('id')];
+          }
+        }
+      } else {
+        $(this).addClass('checked');
+        $(this).find('.octicon-check').removeClass('invisible');
+        if (hasUpdateAction) {
+          if (!($(this).data('id') in items)) {
+            items[$(this).data('id')] = {
+              'update-url': $listMenu.data('update-url'),
+              action: 'attach',
+              'issue-id': $listMenu.data('issue-id'),
+            };
+          } else {
+            delete items[$(this).data('id')];
+          }
+        }
+      }
+
+      // TODO: Which thing should be done for choosing review requests
+      // to make chosen items be shown on time here?
+      if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
+        return false;
+      }
+
+      const listIds = [];
+      $(this).parent().find('.item').each(function () {
+        if ($(this).hasClass('checked')) {
+          listIds.push($(this).data('id'));
+          $($(this).data('id-selector')).removeClass('hide');
+        } else {
+          $($(this).data('id-selector')).addClass('hide');
+        }
+      });
+      if (listIds.length === 0) {
+        $noSelect.removeClass('hide');
+      } else {
+        $noSelect.addClass('hide');
+      }
+      $($(this).parent().data('id')).val(listIds.join(','));
+      return false;
+    });
+    $listMenu.find('.no-select.item').on('click', function (e) {
+      e.preventDefault();
+      if (hasUpdateAction) {
+        updateIssuesMeta(
+          $listMenu.data('update-url'),
+          'clear',
+          $listMenu.data('issue-id'),
+          '',
+        ).then(() => window.location.reload());
+      }
+
+      $(this).parent().find('.item').each(function () {
+        $(this).removeClass('checked');
+        $(this).find('.octicon').addClass('invisible');
+      });
+
+      if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
+        return false;
+      }
+
+      $list.find('.item').each(function () {
+        $(this).addClass('hide');
+      });
+      $noSelect.removeClass('hide');
+      $($(this).parent().data('id')).val('');
+    });
+  }
+
+  // Init labels and assignees
+  initListSubmits('select-label', 'labels');
+  initListSubmits('select-assignees', 'assignees');
+  initListSubmits('select-assignees-modify', 'assignees');
+  initListSubmits('select-reviewers-modify', 'assignees');
+
+  function selectItem(select_id, input_id) {
+    const $menu = $(`${select_id} .menu`);
+    const $list = $(`.ui${select_id}.list`);
+    const hasUpdateAction = $menu.data('action') === 'update';
+
+    $menu.find('.item:not(.no-select)').on('click', function () {
+      $(this).parent().find('.item').each(function () {
+        $(this).removeClass('selected active');
+      });
+
+      $(this).addClass('selected active');
+      if (hasUpdateAction) {
+        updateIssuesMeta(
+          $menu.data('update-url'),
+          '',
+          $menu.data('issue-id'),
+          $(this).data('id'),
+        ).then(() => window.location.reload());
+      }
+
+      let icon = '';
+      if (input_id === '#milestone_id') {
+        icon = svg('octicon-milestone', 18, 'mr-3');
+      } else if (input_id === '#project_id') {
+        icon = svg('octicon-project', 18, 'mr-3');
+      } else if (input_id === '#assignee_id') {
+        icon = `<img class="ui avatar image mr-3" src=${$(this).data('avatar')}>`;
+      }
+
+      $list.find('.selected').html(`
+        <a class="item muted sidebar-item-link" href=${$(this).data('href')}>
+          ${icon}
+          ${htmlEscape($(this).text())}
+        </a>
+      `);
+
+      $(`.ui${select_id}.list .no-select`).addClass('hide');
+      $(input_id).val($(this).data('id'));
+    });
+    $menu.find('.no-select.item').on('click', function () {
+      $(this).parent().find('.item:not(.no-select)').each(function () {
+        $(this).removeClass('selected active');
+      });
+
+      if (hasUpdateAction) {
+        updateIssuesMeta(
+          $menu.data('update-url'),
+          '',
+          $menu.data('issue-id'),
+          $(this).data('id'),
+        ).then(() => window.location.reload());
+      }
+
+      $list.find('.selected').html('');
+      $list.find('.no-select').removeClass('hide');
+      $(input_id).val('');
+    });
+  }
+
+  // Milestone, Assignee, Project
+  selectItem('.select-project', '#project_id');
+  selectItem('.select-milestone', '#milestone_id');
+  selectItem('.select-assignee', '#assignee_id');
+}
+
+
+export async function initRepository() {
+  if ($('.repository').length === 0) {
+    return;
+  }
+
+
+  // Commit statuses
+  $('.commit-statuses-trigger').each(function () {
+    $(this)
+      .popup({
+        on: 'click',
+        position: ($('.repository.file.list').length > 0 ? 'right center' : 'left center'),
+      });
+  });
+
+  // File list and commits
+  if ($('.repository.file.list').length > 0 ||
+    $('.repository.commits').length > 0 || $('.repository.release').length > 0) {
+    initRepoBranchTagDropdown('.choose.reference .dropdown');
+  }
+
+  // Wiki
+  if ($('.repository.wiki.view').length > 0) {
+    initRepoCommonFilterSearchDropdown('.choose.page .dropdown');
+  }
+
+  // Options
+  if ($('.repository.settings.options').length > 0) {
+    // Enable or select internal/external wiki system and issue tracker.
+    $('.enable-system').on('change', function () {
+      if (this.checked) {
+        $($(this).data('target')).removeClass('disabled');
+        if (!$(this).data('context')) $($(this).data('context')).addClass('disabled');
+      } else {
+        $($(this).data('target')).addClass('disabled');
+        if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled');
+      }
+    });
+    $('.enable-system-radio').on('change', function () {
+      if (this.value === 'false') {
+        $($(this).data('target')).addClass('disabled');
+        if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).removeClass('disabled');
+      } else if (this.value === 'true') {
+        $($(this).data('target')).removeClass('disabled');
+        if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).addClass('disabled');
+      }
+    });
+  }
+
+  // Labels
+  initCompLabelEdit('.repository.labels');
+
+  // Milestones
+  if ($('.repository.new.milestone').length > 0) {
+    $('#clear-date').on('click', () => {
+      $('#deadline').val('');
+      return false;
+    });
+  }
+
+  // Repo Creation
+  if ($('.repository.new.repo').length > 0) {
+    $('input[name="gitignores"], input[name="license"]').on('change', () => {
+      const gitignores = $('input[name="gitignores"]').val();
+      const license = $('input[name="license"]').val();
+      if (gitignores || license) {
+        $('input[name="auto_init"]').prop('checked', true);
+      }
+    });
+  }
+
+  // Issues
+  if ($('.repository.view.issue').length > 0) {
+    initRepoIssueBranchSelect();
+    initRepoIssueTitleEdit();
+    initRepoIssueWipToggle();
+    initRepoIssueComments();
+
+    // Issue/PR Context Menus
+    $('.context-dropdown').dropdown({
+      action: 'hide',
+    });
+
+    initRepoDiffConversationNav();
+    initRepoIssueQuoteReply();
+    initRepoIssueReferenceIssue();
+
+    // Edit issue or comment content
+    $(document).on('click', '.edit-content', async function (event) {
+      $(this).closest('.dropdown').find('.menu').toggle('visible');
+      const $segment = $(this).closest('.header').next();
+      const $editContentZone = $segment.find('.edit-content-zone');
+      const $renderContent = $segment.find('.render-content');
+      const $rawContent = $segment.find('.raw-content');
+      let $textarea;
+      let $simplemde;
+
+      // Setup new form
+      if ($editContentZone.html().length === 0) {
+        $editContentZone.html($('#edit-content-form').html());
+        $textarea = $editContentZone.find('textarea');
+        attachTribute($textarea.get(), {mentions: true, emoji: true});
+
+        let dz;
+        const $dropzone = $editContentZone.find('.dropzone');
+        if ($dropzone.length === 1) {
+          $dropzone.data('saved', false);
+
+          const fileUuidDict = {};
+          dz = await createDropzone($dropzone[0], {
+            url: $dropzone.data('upload-url'),
+            headers: {'X-Csrf-Token': csrf},
+            maxFiles: $dropzone.data('max-file'),
+            maxFilesize: $dropzone.data('max-size'),
+            acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'),
+            addRemoveLinks: true,
+            dictDefaultMessage: $dropzone.data('default-message'),
+            dictInvalidFileType: $dropzone.data('invalid-input-type'),
+            dictFileTooBig: $dropzone.data('file-too-big'),
+            dictRemoveFile: $dropzone.data('remove-file'),
+            timeout: 0,
+            thumbnailMethod: 'contain',
+            thumbnailWidth: 480,
+            thumbnailHeight: 480,
+            init() {
+              this.on('success', (file, data) => {
+                fileUuidDict[file.uuid] = {
+                  submitted: false,
+                };
+                const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
+                $dropzone.find('.files').append(input);
+              });
+              this.on('removedfile', (file) => {
+                $(`#${file.uuid}`).remove();
+                if ($dropzone.data('remove-url') && !fileUuidDict[file.uuid].submitted) {
+                  $.post($dropzone.data('remove-url'), {
+                    file: file.uuid,
+                    _csrf: csrf,
+                  });
+                }
+              });
+              this.on('submit', () => {
+                $.each(fileUuidDict, (fileUuid) => {
+                  fileUuidDict[fileUuid].submitted = true;
+                });
+              });
+              this.on('reload', () => {
+                $.getJSON($editContentZone.data('attachment-url'), (data) => {
+                  dz.removeAllFiles(true);
+                  $dropzone.find('.files').empty();
+                  $.each(data, function () {
+                    const imgSrc = `${$dropzone.data('link-url')}/${this.uuid}`;
+                    dz.emit('addedfile', this);
+                    dz.emit('thumbnail', this, imgSrc);
+                    dz.emit('complete', this);
+                    dz.files.push(this);
+                    fileUuidDict[this.uuid] = {
+                      submitted: true,
+                    };
+                    $dropzone.find(`img[src='${imgSrc}']`).css('max-width', '100%');
+                    const input = $(`<input id="${this.uuid}" name="files" type="hidden">`).val(this.uuid);
+                    $dropzone.find('.files').append(input);
+                  });
+                });
+              });
+            },
+          });
+          dz.emit('reload');
+        }
+        // Give new write/preview data-tab name to distinguish from others
+        const $editContentForm = $editContentZone.find('.ui.comment.form');
+        const $tabMenu = $editContentForm.find('.tabular.menu');
+        $tabMenu.attr('data-write', $editContentZone.data('write'));
+        $tabMenu.attr('data-preview', $editContentZone.data('preview'));
+        $tabMenu.find('.write.item').attr('data-tab', $editContentZone.data('write'));
+        $tabMenu.find('.preview.item').attr('data-tab', $editContentZone.data('preview'));
+        $editContentForm.find('.write').attr('data-tab', $editContentZone.data('write'));
+        $editContentForm.find('.preview').attr('data-tab', $editContentZone.data('preview'));
+        $simplemde = createCommentSimpleMDE($textarea);
+        commentMDEditors[$editContentZone.data('write')] = $simplemde;
+        initCompMarkupContentPreviewTab($editContentForm);
+        if ($dropzone.length === 1) {
+          initSimpleMDEImagePaste($simplemde, $dropzone[0], $dropzone.find('.files'));
+        }
+
+        $editContentZone.find('.cancel.button').on('click', () => {
+          $renderContent.show();
+          $editContentZone.hide();
+          if (dz) {
+            dz.emit('reload');
+          }
+        });
+        $editContentZone.find('.save.button').on('click', () => {
+          $renderContent.show();
+          $editContentZone.hide();
+          const $attachments = $dropzone.find('.files').find('[name=files]').map(function () {
+            return $(this).val();
+          }).get();
+          $.post($editContentZone.data('update-url'), {
+            _csrf: csrf,
+            content: $textarea.val(),
+            context: $editContentZone.data('context'),
+            files: $attachments,
+          }, (data) => {
+            if (data.length === 0 || data.content.length === 0) {
+              $renderContent.html($('#no-content').html());
+              $rawContent.text('');
+            } else {
+              $renderContent.html(data.content);
+              $rawContent.text($textarea.val());
+            }
+            const $content = $segment;
+            if (!$content.find('.dropzone-attachments').length) {
+              if (data.attachments !== '') {
+                $content.append(`
+                  <div class="dropzone-attachments">
+                  </div>
+                `);
+                $content.find('.dropzone-attachments').replaceWith(data.attachments);
+              }
+            } else if (data.attachments === '') {
+              $content.find('.dropzone-attachments').remove();
+            } else {
+              $content.find('.dropzone-attachments').replaceWith(data.attachments);
+            }
+            if (dz) {
+              dz.emit('submit');
+              dz.emit('reload');
+            }
+            initMarkupContent();
+            initCommentContent();
+          });
+        });
+      } else {
+        $textarea = $segment.find('textarea');
+        $simplemde = commentMDEditors[$editContentZone.data('write')];
+      }
+
+      // Show write/preview tab and copy raw content as needed
+      $editContentZone.show();
+      $renderContent.hide();
+      if ($textarea.val().length === 0) {
+        $textarea.val($rawContent.text());
+        $simplemde.value($rawContent.text());
+      }
+      requestAnimationFrame(() => {
+        $textarea.focus();
+        $simplemde.codemirror.focus();
+      });
+      event.preventDefault();
+    });
+
+    initRepoIssueCommentDelete();
+    initRepoIssueDependencyDelete();
+    initRepoIssueCodeCommentCancel();
+    initRepoIssueStatusButton();
+    initRepoPullRequestMerge();
+    initRepoPullRequestUpdate();
+    initCompReactionSelector();
+  }
+
+  initRepoClone();
+
+  // Compare or pull request
+  const $repoDiff = $('.repository.diff');
+  if ($repoDiff.length) {
+    initRepoCommonBranchOrTagDropdown('.choose.branch .dropdown');
+    initRepoCommonFilterSearchDropdown('.choose.branch .dropdown');
+  }
+
+  // Pull request
+  const $repoComparePull = $('.repository.compare.pull');
+  if ($repoComparePull.length > 0) {
+    // show pull request form
+    $repoComparePull.find('button.show-form').on('click', function (e) {
+      e.preventDefault();
+      $repoComparePull.find('.pullrequest-form').show();
+      autoSimpleMDE.codemirror.refresh();
+      $(this).parent().hide();
+    });
+  }
+
+  initRepoSettingBranches();
+  initRepoCommonLanguageStats();
+}
+
+function initRepoIssueQuoteReply() {
+  // Quote reply
+  $(document).on('click', '.quote-reply', function (event) {
+    $(this).closest('.dropdown').find('.menu').toggle('visible');
+    const target = $(this).data('target');
+    const quote = $(`#comment-${target}`).text().replace(/\n/g, '\n> ');
+    const content = `> ${quote}\n\n`;
+    let $simplemde = autoSimpleMDE;
+    if ($(this).hasClass('quote-reply-diff')) {
+      const $parent = $(this).closest('.comment-code-cloud');
+      $parent.find('button.comment-form-reply').trigger('click');
+      $simplemde = $parent.find('[name="content"]').data('simplemde');
+    }
+    if ($simplemde !== null) {
+      if ($simplemde.value() !== '') {
+        $simplemde.value(`${$simplemde.value()}\n\n${content}`);
+      } else {
+        $simplemde.value(`${content}`);
+      }
+    }
+    requestAnimationFrame(() => {
+      $simplemde.codemirror.focus();
+      $simplemde.codemirror.setCursor($simplemde.codemirror.lineCount(), 0);
+    });
+    event.preventDefault();
+  });
+}
diff --git a/web_src/js/features/repo-migrate.js b/web_src/js/features/repo-migrate.js
new file mode 100644 (file)
index 0000000..872dbc3
--- /dev/null
@@ -0,0 +1,48 @@
+const {AppSubUrl, csrf} = window.config;
+
+export function initRepoMigrationStatusChecker() {
+  const migrating = $('#repo_migrating');
+  $('#repo_migrating_failed').hide();
+  $('#repo_migrating_failed_image').hide();
+  $('#repo_migrating_progress_message').hide();
+  if (migrating) {
+    const task = migrating.attr('task');
+    if (typeof task === 'undefined') {
+      return;
+    }
+    $.ajax({
+      type: 'GET',
+      url: `${AppSubUrl}/user/task/${task}`,
+      data: {
+        _csrf: csrf,
+      },
+      complete(xhr) {
+        if (xhr.status === 200 && xhr.responseJSON) {
+          if (xhr.responseJSON.status === 4) {
+            window.location.reload();
+            return;
+          } else if (xhr.responseJSON.status === 3) {
+            $('#repo_migrating_progress').hide();
+            $('#repo_migrating').hide();
+            $('#repo_migrating_failed').show();
+            $('#repo_migrating_failed_image').show();
+            $('#repo_migrating_failed_error').text(xhr.responseJSON.message);
+            return;
+          }
+          if (xhr.responseJSON.message) {
+            $('#repo_migrating_progress_message').show();
+            $('#repo_migrating_progress_message').text(xhr.responseJSON.message);
+          }
+          setTimeout(() => {
+            initRepoMigrationStatusChecker();
+          }, 2000);
+          return;
+        }
+        $('#repo_migrating_progress').hide();
+        $('#repo_migrating').hide();
+        $('#repo_migrating_failed').show();
+        $('#repo_migrating_failed_image').show();
+      }
+    });
+  }
+}
diff --git a/web_src/js/features/repo-release.js b/web_src/js/features/repo-release.js
new file mode 100644 (file)
index 0000000..08e3e9e
--- /dev/null
@@ -0,0 +1,29 @@
+import attachTribute from './tribute.js';
+import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js';
+import {initSimpleMDEImagePaste} from './comp/ImagePaste.js';
+import {createCommentSimpleMDE} from './comp/CommentSimpleMDE.js';
+
+export function initRepoRelease() {
+  $(document).on('click', '.remove-rel-attach', function() {
+    const uuid = $(this).data('uuid');
+    const id = $(this).data('id');
+    $(`input[name='attachment-del-${uuid}']`).attr('value', true);
+    $(`#attachment-${id}`).hide();
+  });
+}
+
+
+export function initRepoReleaseEditor() {
+  const $editor = $('.repository.new.release .content-editor');
+  if ($editor.length === 0) {
+    return false;
+  }
+
+  const $textarea = $editor.find('textarea');
+  attachTribute($textarea.get(), {mentions: false, emoji: true});
+  const $files = $editor.parent().find('.files');
+  const $simplemde = createCommentSimpleMDE($textarea);
+  initCompMarkupContentPreviewTab($editor);
+  const dropzone = $editor.parent().find('.dropzone')[0];
+  initSimpleMDEImagePaste($simplemde, dropzone, $files);
+}
diff --git a/web_src/js/features/repo-settings.js b/web_src/js/features/repo-settings.js
new file mode 100644 (file)
index 0000000..e063448
--- /dev/null
@@ -0,0 +1,66 @@
+import {createMonaco} from './codeeditor.js';
+import {initRepoCommonFilterSearchDropdown} from './repo-common.js';
+
+const {AppSubUrl, csrf} = window.config;
+
+export function initRepoSettingsCollaboration() {
+  // Change collaborator access mode
+  $('.access-mode.menu .item').on('click', function () {
+    const $menu = $(this).parent();
+    $.post($menu.data('url'), {
+      _csrf: csrf,
+      uid: $menu.data('uid'),
+      mode: $(this).data('value')
+    });
+  });
+}
+
+export function initRepoSettingSearchTeamBox() {
+  const $searchTeamBox = $('#search-team-box');
+  $searchTeamBox.search({
+    minCharacters: 2,
+    apiSettings: {
+      url: `${AppSubUrl}/api/v1/orgs/${$searchTeamBox.data('org')}/teams/search?q={query}`,
+      headers: {'X-Csrf-Token': csrf},
+      onResponse(response) {
+        const items = [];
+        $.each(response.data, (_i, item) => {
+          const title = `${item.name} (${item.permission} access)`;
+          items.push({
+            title,
+          });
+        });
+
+        return {results: items};
+      }
+    },
+    searchFields: ['name', 'description'],
+    showNoResults: false
+  });
+}
+
+
+export async function initRepoSettingGitHook() {
+  if ($('.edit.githook').length === 0) return;
+  const filename = document.querySelector('.hook-filename').textContent;
+  await createMonaco($('#content')[0], filename, {language: 'shell'});
+}
+
+export function initRepoSettingBranches() {
+  // Branches
+  if ($('.repository.settings.branches').length > 0) {
+    initRepoCommonFilterSearchDropdown('.protected-branches .dropdown');
+    $('.enable-protection, .enable-whitelist, .enable-statuscheck').on('change', function () {
+      if (this.checked) {
+        $($(this).data('target')).removeClass('disabled');
+      } else {
+        $($(this).data('target')).addClass('disabled');
+      }
+    });
+    $('.disable-whitelist').on('change', function () {
+      if (this.checked) {
+        $($(this).data('target')).addClass('disabled');
+      }
+    });
+  }
+}
diff --git a/web_src/js/features/repo-template.js b/web_src/js/features/repo-template.js
new file mode 100644 (file)
index 0000000..9385e2a
--- /dev/null
@@ -0,0 +1,49 @@
+import {htmlEscape} from 'escape-goat';
+
+const {AppSubUrl} = window.config;
+
+export function initRepoTemplateSearch() {
+  const $repoTemplate = $('#repo_template');
+  const checkTemplate = function () {
+    const $templateUnits = $('#template_units');
+    const $nonTemplate = $('#non_template');
+    if ($repoTemplate.val() !== '' && $repoTemplate.val() !== '0') {
+      $templateUnits.show();
+      $nonTemplate.hide();
+    } else {
+      $templateUnits.hide();
+      $nonTemplate.show();
+    }
+  };
+  $repoTemplate.on('change', checkTemplate);
+  checkTemplate();
+
+  const changeOwner = function () {
+    $('#repo_template_search')
+      .dropdown({
+        apiSettings: {
+          url: `${AppSubUrl}/api/v1/repos/search?q={query}&template=true&priority_owner_id=${$('#uid').val()}`,
+          onResponse(response) {
+            const filteredResponse = {success: true, results: []};
+            filteredResponse.results.push({
+              name: '',
+              value: ''
+            });
+            // Parse the response from the api to work with our dropdown
+            $.each(response.data, (_r, repo) => {
+              filteredResponse.results.push({
+                name: htmlEscape(repo.full_name),
+                value: repo.id
+              });
+            });
+            return filteredResponse;
+          },
+          cache: false,
+        },
+
+        fullTextSearch: true
+      });
+  };
+  $('#uid').on('change', changeOwner);
+  changeOwner();
+}
diff --git a/web_src/js/features/repo-wiki.js b/web_src/js/features/repo-wiki.js
new file mode 100644 (file)
index 0000000..aad3161
--- /dev/null
@@ -0,0 +1,174 @@
+import {initMarkupContent} from '../markup/content.js';
+import {initCompMarkupContentPreviewTab} from './comp/MarkupContentPreview.js';
+
+const {csrf} = window.config;
+
+export function initRepoWikiForm() {
+  const $editArea = $('.repository.wiki textarea#edit_area');
+  let sideBySideChanges = 0;
+  let sideBySideTimeout = null;
+  let hasSimpleMDE = true;
+  if ($editArea.length > 0) {
+    const simplemde = new SimpleMDE({
+      autoDownloadFontAwesome: false,
+      element: $editArea[0],
+      forceSync: true,
+      previewRender(plainText, preview) { // Async method
+        // FIXME: still send render request when return back to edit mode
+        const render = function () {
+          sideBySideChanges = 0;
+          if (sideBySideTimeout !== null) {
+            clearTimeout(sideBySideTimeout);
+            sideBySideTimeout = null;
+          }
+          $.post($editArea.data('url'), {
+            _csrf: csrf,
+            mode: 'gfm',
+            context: $editArea.data('context'),
+            text: plainText,
+            wiki: true
+          }, (data) => {
+            preview.innerHTML = `<div class="markup ui segment">${data}</div>`;
+            initMarkupContent();
+          });
+        };
+
+        setTimeout(() => {
+          if (!simplemde.isSideBySideActive()) {
+            render();
+          } else {
+            // delay preview by keystroke counting
+            sideBySideChanges++;
+            if (sideBySideChanges > 10) {
+              render();
+            }
+            // or delay preview by timeout
+            if (sideBySideTimeout !== null) {
+              clearTimeout(sideBySideTimeout);
+              sideBySideTimeout = null;
+            }
+            sideBySideTimeout = setTimeout(render, 600);
+          }
+        }, 0);
+        if (!simplemde.isSideBySideActive()) {
+          return 'Loading...';
+        }
+        return preview.innerHTML;
+      },
+      renderingConfig: {
+        singleLineBreaks: false
+      },
+      indentWithTabs: false,
+      tabSize: 4,
+      spellChecker: false,
+      toolbar: ['bold', 'italic', 'strikethrough', '|',
+        'heading-1', 'heading-2', 'heading-3', 'heading-bigger', 'heading-smaller', '|',
+        {
+          name: 'code-inline',
+          action(e) {
+            const cm = e.codemirror;
+            const selection = cm.getSelection();
+            cm.replaceSelection(`\`${selection}\``);
+            if (!selection) {
+              const cursorPos = cm.getCursor();
+              cm.setCursor(cursorPos.line, cursorPos.ch - 1);
+            }
+            cm.focus();
+          },
+          className: 'fa fa-angle-right',
+          title: 'Add Inline Code',
+        }, 'code', 'quote', '|', {
+          name: 'checkbox-empty',
+          action(e) {
+            const cm = e.codemirror;
+            cm.replaceSelection(`\n- [ ] ${cm.getSelection()}`);
+            cm.focus();
+          },
+          className: 'fa fa-square-o',
+          title: 'Add Checkbox (empty)',
+        },
+        {
+          name: 'checkbox-checked',
+          action(e) {
+            const cm = e.codemirror;
+            cm.replaceSelection(`\n- [x] ${cm.getSelection()}`);
+            cm.focus();
+          },
+          className: 'fa fa-check-square-o',
+          title: 'Add Checkbox (checked)',
+        }, '|',
+        'unordered-list', 'ordered-list', '|',
+        'link', 'image', 'table', 'horizontal-rule', '|',
+        'clean-block', 'preview', 'fullscreen', 'side-by-side', '|',
+        {
+          name: 'revert-to-textarea',
+          action(e) {
+            e.toTextArea();
+            hasSimpleMDE = false;
+            const $form = $('.repository.wiki.new .ui.form');
+            const $root = $form.find('.field.content');
+            const loading = $root.data('loading');
+            $root.append(`<div class="ui bottom tab markup" data-tab="preview">${loading}</div>`);
+            initCompMarkupContentPreviewTab($form);
+          },
+          className: 'fa fa-file',
+          title: 'Revert to simple textarea',
+        },
+      ]
+    });
+    $(simplemde.codemirror.getInputField()).addClass('js-quick-submit');
+
+    setTimeout(() => {
+      const $bEdit = $('.repository.wiki.new .previewtabs a[data-tab="write"]');
+      const $bPrev = $('.repository.wiki.new .previewtabs a[data-tab="preview"]');
+      const $toolbar = $('.editor-toolbar');
+      const $bPreview = $('.editor-toolbar button.preview');
+      const $bSideBySide = $('.editor-toolbar a.fa-columns');
+      $bEdit.on('click', (e) => {
+        if (!hasSimpleMDE) {
+          return false;
+        }
+        e.stopImmediatePropagation();
+        if ($toolbar.hasClass('disabled-for-preview')) {
+          $bPreview.trigger('click');
+        }
+
+        return false;
+      });
+      $bPrev.on('click', (e) => {
+        if (!hasSimpleMDE) {
+          return false;
+        }
+        e.stopImmediatePropagation();
+        if (!$toolbar.hasClass('disabled-for-preview')) {
+          $bPreview.trigger('click');
+        }
+        return false;
+      });
+      $bPreview.on('click', () => {
+        setTimeout(() => {
+          if ($toolbar.hasClass('disabled-for-preview')) {
+            if ($bEdit.hasClass('active')) {
+              $bEdit.removeClass('active');
+            }
+            if (!$bPrev.hasClass('active')) {
+              $bPrev.addClass('active');
+            }
+          } else {
+            if (!$bEdit.hasClass('active')) {
+              $bEdit.addClass('active');
+            }
+            if ($bPrev.hasClass('active')) {
+              $bPrev.removeClass('active');
+            }
+          }
+        }, 0);
+
+        return false;
+      });
+      $bSideBySide.on('click', () => {
+        sideBySideChanges = 10;
+      });
+    }, 0);
+  }
+}
diff --git a/web_src/js/features/sshkey-helper.js b/web_src/js/features/sshkey-helper.js
new file mode 100644 (file)
index 0000000..bb3c8ac
--- /dev/null
@@ -0,0 +1,10 @@
+export function initSshKeyFormParser() {
+// Parse SSH Key
+  $('#ssh-key-content').on('change paste keyup', function () {
+    const arrays = $(this).val().split(' ');
+    const $title = $('#ssh-key-title');
+    if ($title.val() === '' && arrays.length === 3 && arrays[2] !== '') {
+      $title.val(arrays[2]);
+    }
+  });
+}
diff --git a/web_src/js/features/user-auth-u2f.js b/web_src/js/features/user-auth-u2f.js
new file mode 100644 (file)
index 0000000..2525521
--- /dev/null
@@ -0,0 +1,125 @@
+const {AppSubUrl, csrf} = window.config;
+
+export function initUserAuthU2fAuth() {
+  if ($('#wait-for-key').length === 0) {
+    return;
+  }
+  u2fApi.ensureSupport().then(() => {
+    $.getJSON(`${AppSubUrl}/user/u2f/challenge`).done((req) => {
+      u2fApi.sign(req.appId, req.challenge, req.registeredKeys, 30)
+        .then(u2fSigned)
+        .catch((err) => {
+          if (err === undefined) {
+            u2fError(1);
+            return;
+          }
+          u2fError(err.metaData.code);
+        });
+    });
+  }).catch(() => {
+    // Fallback in case browser do not support U2F
+    window.location.href = `${AppSubUrl}/user/two_factor`;
+  });
+}
+
+function u2fSigned(resp) {
+  $.ajax({
+    url: `${AppSubUrl}/user/u2f/sign`,
+    type: 'POST',
+    headers: {'X-Csrf-Token': csrf},
+    data: JSON.stringify(resp),
+    contentType: 'application/json; charset=utf-8',
+  }).done((res) => {
+    window.location.replace(res);
+  }).fail(() => {
+    u2fError(1);
+  });
+}
+
+function u2fRegistered(resp) {
+  if (checkError(resp)) {
+    return;
+  }
+  $.ajax({
+    url: `${AppSubUrl}/user/settings/security/u2f/register`,
+    type: 'POST',
+    headers: {'X-Csrf-Token': csrf},
+    data: JSON.stringify(resp),
+    contentType: 'application/json; charset=utf-8',
+    success() {
+      window.location.reload();
+    },
+    fail() {
+      u2fError(1);
+    }
+  });
+}
+
+function checkError(resp) {
+  if (!('errorCode' in resp)) {
+    return false;
+  }
+  if (resp.errorCode === 0) {
+    return false;
+  }
+  u2fError(resp.errorCode);
+  return true;
+}
+
+function u2fError(errorType) {
+  const u2fErrors = {
+    browser: $('#unsupported-browser'),
+    1: $('#u2f-error-1'),
+    2: $('#u2f-error-2'),
+    3: $('#u2f-error-3'),
+    4: $('#u2f-error-4'),
+    5: $('.u2f_error_5')
+  };
+  u2fErrors[errorType].removeClass('hide');
+
+  Object.keys(u2fErrors).forEach((type) => {
+    if (type !== `${errorType}`) {
+      u2fErrors[type].addClass('hide');
+    }
+  });
+  $('#u2f-error').modal('show');
+}
+
+export function initUserAuthU2fRegister() {
+  $('#register-device').modal({allowMultiple: false});
+  $('#u2f-error').modal({allowMultiple: false});
+  $('#register-security-key').on('click', (e) => {
+    e.preventDefault();
+    u2fApi.ensureSupport()
+      .then(u2fRegisterRequest)
+      .catch(() => {
+        u2fError('browser');
+      });
+  });
+}
+
+function u2fRegisterRequest() {
+  $.post(`${AppSubUrl}/user/settings/security/u2f/request_register`, {
+    _csrf: csrf,
+    name: $('#nickname').val()
+  }).done((req) => {
+    $('#nickname').closest('div.field').removeClass('error');
+    $('#register-device').modal('show');
+    if (req.registeredKeys === null) {
+      req.registeredKeys = [];
+    }
+    u2fApi.register(req.appId, req.registerRequests, req.registeredKeys, 30)
+      .then(u2fRegistered)
+      .catch((reason) => {
+        if (reason === undefined) {
+          u2fError(1);
+          return;
+        }
+        u2fError(reason.metaData.code);
+      });
+  }).fail((xhr) => {
+    if (xhr.status === 409) {
+      $('#nickname').closest('div.field').addClass('error');
+    }
+  });
+}
diff --git a/web_src/js/features/user-auth.js b/web_src/js/features/user-auth.js
new file mode 100644 (file)
index 0000000..bc29480
--- /dev/null
@@ -0,0 +1,46 @@
+export function initUserAuthOauth2() {
+  const $oauth2LoginNav = $('#oauth2-login-navigator');
+  if ($oauth2LoginNav.length === 0) return;
+
+  $oauth2LoginNav.find('.oauth-login-image').click(() => {
+    const oauthLoader = $('#oauth2-login-loader');
+    const oauthNav = $('#oauth2-login-navigator');
+
+    oauthNav.hide();
+    oauthLoader.removeClass('disabled');
+
+    setTimeout(() => {
+      // recover previous content to let user try again
+      // usually redirection will be performed before this action
+      oauthLoader.addClass('disabled');
+      oauthNav.show();
+    }, 5000);
+  });
+}
+
+export function initUserAuthLinkAccountView() {
+  const $lnkUserPage = $('.page-content.user.link-account');
+  if ($lnkUserPage.length === 0) {
+    return false;
+  }
+
+  const $signinTab = $lnkUserPage.find('.item[data-tab="auth-link-signin-tab"]');
+  const $signUpTab = $lnkUserPage.find('.item[data-tab="auth-link-signup-tab"]');
+  const $signInView = $lnkUserPage.find('.tab[data-tab="auth-link-signin-tab"]');
+  const $signUpView = $lnkUserPage.find('.tab[data-tab="auth-link-signup-tab"]');
+
+  $signUpTab.on('click', () => {
+    $signinTab.removeClass('active');
+    $signInView.removeClass('active');
+    $signUpTab.addClass('active');
+    $signUpView.addClass('active');
+    return false;
+  });
+
+  $signinTab.on('click', () => {
+    $signUpTab.removeClass('active');
+    $signUpView.removeClass('active');
+    $signinTab.addClass('active');
+    $signInView.addClass('active');
+  });
+}
diff --git a/web_src/js/features/user-settings.js b/web_src/js/features/user-settings.js
new file mode 100644 (file)
index 0000000..c2f00dc
--- /dev/null
@@ -0,0 +1,15 @@
+export function initUserSettings() {
+  if ($('.user.settings.profile').length > 0) {
+    $('#username').on('keyup', function () {
+      const $prompt = $('#name-change-prompt');
+      const $prompt_redirect = $('#name-change-redirect-prompt');
+      if ($(this).val().toString().toLowerCase() !== $(this).data('name').toString().toLowerCase()) {
+        $prompt.show();
+        $prompt_redirect.show();
+      } else {
+        $prompt.hide();
+        $prompt_redirect.hide();
+      }
+    });
+  }
+}
index 74aca6913b4536d74ed1aaa9bb8f85e9801462e9..a3bd35175e5a811a3f0f15fb5e9064a373fe35f7 100644 (file)
@@ -1,17 +1,11 @@
 import './publicpath.js';
 
-import {htmlEscape} from 'escape-goat';
-import 'jquery.are-you-sure';
-
 import {initVueEnv} from './components/VueComponentLoader.js';
 import {initRepoActivityTopAuthorsChart} from './components/RepoActivityTopAuthors.vue';
 import {initDashboardRepoList} from './components/DashboardRepoList.js';
-import {initRepoBranchTagDropdown} from './components/RepoBranchTagDropdown.js';
 
 import attachTribute from './features/tribute.js';
-import createColorPicker from './features/colorpicker.js';
-import createDropzone from './features/dropzone.js';
-import initClipboard from './features/clipboard.js';
+import initGlobalCopyToClipboardListener from './features/clipboard.js';
 import initContextPopups from './features/contextpopup.js';
 import initGitGraph from './features/gitgraph.js';
 import initHeatmap from './features/heatmap.js';
@@ -21,3487 +15,152 @@ import initProject from './features/projects.js';
 import initServiceWorker from './features/serviceworker.js';
 import initTableSort from './features/tablesort.js';
 import {initAdminUserListSearchForm} from './features/admin-users.js';
-import {createCodeEditor, createMonaco} from './features/codeeditor.js';
 import {initMarkupAnchors} from './markup/anchors.js';
-import {initNotificationsTable, initNotificationCount} from './features/notification.js';
+import {initNotificationCount, initNotificationsTable} from './features/notification.js';
 import {initLastCommitLoader} from './features/lastcommitloader.js';
 import {initIssueContentHistory} from './features/issue-content-history.js';
 import {initStopwatch} from './features/stopwatch.js';
 import {initDiffShowMore} from './features/diff.js';
-import {showLineButton} from './code/linebutton.js';
-import {initMarkupContent, initCommentContent} from './markup/content.js';
-import {stripTags, mqBinarySearch} from './utils.js';
-import {svg} from './svg.js';
-
-const {AppSubUrl, csrf} = window.config;
-
-let previewFileModes;
-const commentMDEditors = {};
+import {initCommentContent, initMarkupContent} from './markup/content.js';
+
+import {initUserAuthLinkAccountView, initUserAuthOauth2} from './features/user-auth.js';
+import {
+  initRepoDiffConversationForm,
+  initRepoDiffFileViewToggle,
+  initRepoDiffReviewButton,
+} from './features/repo-diff.js';
+import {
+  initRepoIssueDue,
+  initRepoIssueList,
+  initRepoIssueReferenceRepositorySearch,
+  initRepoIssueTimeTracking,
+  initRepoIssueWipTitle,
+  initRepoPullRequestMergeInstruction,
+  initRepoPullRequestReview,
+} from './features/repo-issue.js';
+import {initRepoCommitButton} from './features/repo-commit.js';
+import {
+  initFootLanguageMenu,
+  initGlobalButtonClickOnEnter,
+  initGlobalButtons,
+  initGlobalCommon,
+  initGlobalDropzone,
+  initGlobalEnterQuickSubmit,
+  initGlobalFormDirtyLeaveConfirm,
+  initGlobalLinkActions,
+  initHeadNavbarContentToggle,
+} from './features/common-global.js';
+import {initRepoTopicBar} from './features/repo-home.js';
+import {initAdminEmails} from './features/admin-emails.js';
+import {initAdminCommon} from './features/admin-common.js';
+import {initRepoTemplateSearch} from './features/repo-template.js';
+import {initRepoCodeView} from './features/repo-code.js';
+import {initSshKeyFormParser} from './features/sshkey-helper.js';
+import {initUserSettings} from './features/user-settings.js';
+import {initRepoArchiveLinks} from './features/repo-common.js';
+import {initRepoMigrationStatusChecker} from './features/repo-migrate.js';
+import {
+  initRepoSettingGitHook,
+  initRepoSettingsCollaboration,
+  initRepoSettingSearchTeamBox,
+} from './features/repo-settings.js';
+import {initOrgTeamSearchRepoBox, initOrgTeamSettings} from './features/org-team.js';
+import {initUserAuthU2fAuth, initUserAuthU2fRegister} from './features/user-auth-u2f.js';
+import {initRepoRelease, initRepoReleaseEditor} from './features/repo-release.js';
+import {initRepoEditor} from './features/repo-editor.js';
+import {initSearchUserBox} from './features/comp/SearchUserBox.js';
+import {initInstall} from './features/install.js';
+import {initWebHookEditor} from './features/comp/WebHookEditor.js';
+import {initCommonIssue} from './features/common-issue.js';
+import {initRepoBranchButton} from './features/repo-branch.js';
+import {initCommonOrganization} from './features/common-organization.js';
+import {initRepoWikiForm} from './features/repo-wiki.js';
+import {initRepoCommentForm, initRepository} from './features/repo-legacy.js';
 
 // Silence fomantic's error logging when tabs are used without a target content element
 $.fn.tab.settings.silent = true;
-initVueEnv();
-
-function initCommentPreviewTab($form) {
-  const $tabMenu = $form.find('.tabular.menu');
-  $tabMenu.find('.item').tab();
-  $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`).on('click', function () {
-    const $this = $(this);
-    $.post($this.data('url'), {
-      _csrf: csrf,
-      mode: 'comment',
-      context: $this.data('context'),
-      text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val()
-    }, (data) => {
-      const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`);
-      $previewPanel.html(data);
-      initMarkupContent();
-    });
-  });
-
-  buttonsClickOnEnter();
-}
-
-function initEditPreviewTab($form) {
-  const $tabMenu = $form.find('.tabular.menu');
-  $tabMenu.find('.item').tab();
-  const $previewTab = $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`);
-  if ($previewTab.length) {
-    previewFileModes = $previewTab.data('preview-file-modes').split(',');
-    $previewTab.on('click', function () {
-      const $this = $(this);
-      let context = `${$this.data('context')}/`;
-      const mode = $this.data('markdown-mode') || 'comment';
-      const treePathEl = $form.find('input#tree_path');
-      if (treePathEl.length > 0) {
-        context += treePathEl.val();
-      }
-      context = context.substring(0, context.lastIndexOf('/'));
-      $.post($this.data('url'), {
-        _csrf: csrf,
-        mode,
-        context,
-        text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val()
-      }, (data) => {
-        const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`);
-        $previewPanel.html(data);
-        initMarkupContent();
-      });
-    });
-  }
-}
-
-function initEditDiffTab($form) {
-  const $tabMenu = $form.find('.tabular.menu');
-  $tabMenu.find('.item').tab();
-  $tabMenu.find(`.item[data-tab="${$tabMenu.data('diff')}"]`).on('click', function () {
-    const $this = $(this);
-    $.post($this.data('url'), {
-      _csrf: csrf,
-      context: $this.data('context'),
-      content: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val()
-    }, (data) => {
-      const $diffPreviewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('diff')}"]`);
-      $diffPreviewPanel.html(data);
-    });
-  });
-}
-
-function initEditForm() {
-  if ($('.edit.form').length === 0) {
-    return;
-  }
-
-  initEditPreviewTab($('.edit.form'));
-  initEditDiffTab($('.edit.form'));
-}
-
-function initBranchSelector() {
-  const $selectBranch = $('.ui.select-branch');
-  const $branchMenu = $selectBranch.find('.reference-list-menu');
-  const $isNewIssue = $branchMenu.hasClass('new-issue');
-  $branchMenu.find('.item:not(.no-select)').click(function () {
-    const selectedValue = $(this).data('id');
-    const editMode = $('#editing_mode').val();
-    $($(this).data('id-selector')).val(selectedValue);
-    if ($isNewIssue) {
-      $selectBranch.find('.ui .branch-name').text($(this).data('name'));
-      return;
-    }
-
-    if (editMode === 'true') {
-      const form = $('#update_issueref_form');
-
-      $.post(form.attr('action'), {
-        _csrf: csrf,
-        ref: selectedValue
-      },
-      () => {
-        window.location.reload();
-      });
-    } else if (editMode === '') {
-      $selectBranch.find('.ui .branch-name').text(selectedValue);
-    }
-  });
-  $selectBranch.find('.reference.column').on('click', function () {
-    $selectBranch.find('.scrolling.reference-list-menu').css('display', 'none');
-    $selectBranch.find('.reference .text').removeClass('black');
-    $($(this).data('target')).css('display', 'block');
-    $(this).find('.text').addClass('black');
-    return false;
-  });
-}
-
-function initLabelEdit() {
-// Create label
-  const $newLabelPanel = $('.new-label.segment');
-  $('.new-label.button').on('click', () => {
-    $newLabelPanel.show();
-  });
-  $('.new-label.segment .cancel').on('click', () => {
-    $newLabelPanel.hide();
-  });
-
-  initColorPicker();
-
-  $('.edit-label-button').on('click', function () {
-    $('.edit-label .color-picker').minicolors('value', $(this).data('color'));
-    $('#label-modal-id').val($(this).data('id'));
-    $('.edit-label .new-label-input').val($(this).data('title'));
-    $('.edit-label .new-label-desc-input').val($(this).data('description'));
-    $('.edit-label .color-picker').val($(this).data('color'));
-    $('.edit-label .minicolors-swatch-color').css('background-color', $(this).data('color'));
-    $('.edit-label.modal').modal({
-      onApprove() {
-        $('.edit-label.form').trigger('submit');
-      }
-    }).modal('show');
-    return false;
-  });
-}
-
-function initColorPicker() {
-  createColorPicker($('.color-picker'));
-
-  $('.precolors .color').on('click', function () {
-    const color_hex = $(this).data('color-hex');
-    $('.color-picker').val(color_hex);
-    $('.minicolors-swatch-color').css('background-color', color_hex);
-  });
-}
-
-function updateIssuesMeta(url, action, issueIds, elementId) {
-  return new Promise(((resolve) => {
-    $.ajax({
-      type: 'POST',
-      url,
-      data: {
-        _csrf: csrf,
-        action,
-        issue_ids: issueIds,
-        id: elementId,
-      },
-      success: resolve
-    });
-  }));
-}
-
-function initRepoStatusChecker() {
-  const migrating = $('#repo_migrating');
-  $('#repo_migrating_failed').hide();
-  $('#repo_migrating_failed_image').hide();
-  $('#repo_migrating_progress_message').hide();
-  if (migrating) {
-    const task = migrating.attr('task');
-    if (typeof task === 'undefined') {
-      return;
-    }
-    $.ajax({
-      type: 'GET',
-      url: `${AppSubUrl}/user/task/${task}`,
-      data: {
-        _csrf: csrf,
-      },
-      complete(xhr) {
-        if (xhr.status === 200 && xhr.responseJSON) {
-          if (xhr.responseJSON.status === 4) {
-            window.location.reload();
-            return;
-          } else if (xhr.responseJSON.status === 3) {
-            $('#repo_migrating_progress').hide();
-            $('#repo_migrating').hide();
-            $('#repo_migrating_failed').show();
-            $('#repo_migrating_failed_image').show();
-            $('#repo_migrating_failed_error').text(xhr.responseJSON.message);
-            return;
-          }
-          if (xhr.responseJSON.message) {
-            $('#repo_migrating_progress_message').show();
-            $('#repo_migrating_progress_message').text(xhr.responseJSON.message);
-          }
-          setTimeout(() => {
-            initRepoStatusChecker();
-          }, 2000);
-          return;
-        }
-        $('#repo_migrating_progress').hide();
-        $('#repo_migrating').hide();
-        $('#repo_migrating_failed').show();
-        $('#repo_migrating_failed_image').show();
-      }
-    });
-  }
-}
-
-function initReactionSelector(parent) {
-  let reactions = '';
-  if (!parent) {
-    parent = $(document);
-    reactions = '.reactions > ';
-  }
-
-  parent.find(`${reactions}a.label`).popup({position: 'bottom left', metadata: {content: 'title', title: 'none'}});
-
-  parent.find(`.select-reaction > .menu > .item, ${reactions}a.label`).on('click', function (e) {
-    e.preventDefault();
-
-    if ($(this).hasClass('disabled')) return;
-
-    const actionURL = $(this).hasClass('item') ? $(this).closest('.select-reaction').data('action-url') : $(this).data('action-url');
-    const url = `${actionURL}/${$(this).hasClass('blue') ? 'unreact' : 'react'}`;
-    $.ajax({
-      type: 'POST',
-      url,
-      data: {
-        _csrf: csrf,
-        content: $(this).data('content')
-      }
-    }).done((resp) => {
-      if (resp && (resp.html || resp.empty)) {
-        const content = $(this).closest('.content');
-        let react = content.find('.segment.reactions');
-        if ((!resp.empty || resp.html === '') && react.length > 0) {
-          react.remove();
-        }
-        if (!resp.empty) {
-          react = $('<div class="ui attached segment reactions"></div>');
-          const attachments = content.find('.segment.bottom:first');
-          if (attachments.length > 0) {
-            react.insertBefore(attachments);
-          } else {
-            react.appendTo(content);
-          }
-          react.html(resp.html);
-          react.find('.dropdown').dropdown();
-          initReactionSelector(react);
-        }
-      }
-    });
-  });
-}
-
-function insertAtCursor(field, value) {
-  if (field.selectionStart || field.selectionStart === 0) {
-    const startPos = field.selectionStart;
-    const endPos = field.selectionEnd;
-    field.value = field.value.substring(0, startPos) + value + field.value.substring(endPos, field.value.length);
-    field.selectionStart = startPos + value.length;
-    field.selectionEnd = startPos + value.length;
-  } else {
-    field.value += value;
-  }
-}
-
-function replaceAndKeepCursor(field, oldval, newval) {
-  if (field.selectionStart || field.selectionStart === 0) {
-    const startPos = field.selectionStart;
-    const endPos = field.selectionEnd;
-    field.value = field.value.replace(oldval, newval);
-    field.selectionStart = startPos + newval.length - oldval.length;
-    field.selectionEnd = endPos + newval.length - oldval.length;
-  } else {
-    field.value = field.value.replace(oldval, newval);
-  }
-}
-
-function getPastedImages(e) {
-  if (!e.clipboardData) return [];
-
-  const files = [];
-  for (const item of e.clipboardData.items || []) {
-    if (!item.type || !item.type.startsWith('image/')) continue;
-    files.push(item.getAsFile());
-  }
-
-  if (files.length) {
-    e.preventDefault();
-    e.stopPropagation();
-  }
-  return files;
-}
-
-async function uploadFile(file, uploadUrl) {
-  const formData = new FormData();
-  formData.append('file', file, file.name);
-
-  const res = await fetch(uploadUrl, {
-    method: 'POST',
-    headers: {'X-Csrf-Token': csrf},
-    body: formData,
-  });
-  return await res.json();
-}
-
-function reload() {
-  window.location.reload();
-}
-
-function initImagePaste(target) {
-  target.each(function () {
-    const dropzone = this.querySelector('.dropzone');
-    if (!dropzone) {
-      return;
-    }
-    const uploadUrl = dropzone.dataset.uploadUrl;
-    const dropzoneFiles = dropzone.querySelector('.files');
-    for (const textarea of this.querySelectorAll('textarea')) {
-      textarea.addEventListener('paste', async (e) => {
-        for (const img of getPastedImages(e)) {
-          const name = img.name.substr(0, img.name.lastIndexOf('.'));
-          insertAtCursor(textarea, `![${name}]()`);
-          const data = await uploadFile(img, uploadUrl);
-          replaceAndKeepCursor(textarea, `![${name}]()`, `![${name}](${AppSubUrl}/attachments/${data.uuid})`);
-          const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
-          dropzoneFiles.appendChild(input[0]);
-        }
-      }, false);
-    }
-  });
-}
-
-function initSimpleMDEImagePaste(simplemde, dropzone, files) {
-  const uploadUrl = dropzone.dataset.uploadUrl;
-  simplemde.codemirror.on('paste', async (_, e) => {
-    for (const img of getPastedImages(e)) {
-      const name = img.name.substr(0, img.name.lastIndexOf('.'));
-      const data = await uploadFile(img, uploadUrl);
-      const pos = simplemde.codemirror.getCursor();
-      simplemde.codemirror.replaceRange(`![${name}](${AppSubUrl}/attachments/${data.uuid})`, pos);
-      const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
-      files.append(input);
-    }
-  });
-}
-
-let autoSimpleMDE;
-
-function initCommentForm() {
-  if ($('.comment.form').length === 0) {
-    return;
-  }
-
-  autoSimpleMDE = setCommentSimpleMDE($('.comment.form textarea:not(.review-textarea)'));
-  initBranchSelector();
-  initCommentPreviewTab($('.comment.form'));
-  initImagePaste($('.comment.form'));
-
-  // Listsubmit
-  function initListSubmits(selector, outerSelector) {
-    const $list = $(`.ui.${outerSelector}.list`);
-    const $noSelect = $list.find('.no-select');
-    const $listMenu = $(`.${selector} .menu`);
-    let hasUpdateAction = $listMenu.data('action') === 'update';
-    const items = {};
-
-    $(`.${selector}`).dropdown('setting', 'onHide', () => {
-      hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
-      if (hasUpdateAction) {
-        const promises = [];
-        Object.keys(items).forEach((elementId) => {
-          const item = items[elementId];
-          const promise = updateIssuesMeta(
-            item['update-url'],
-            item.action,
-            item['issue-id'],
-            elementId,
-          );
-          promises.push(promise);
-        });
-        Promise.all(promises).then(reload);
-      }
-    });
-
-    $listMenu.find('.item:not(.no-select)').on('click', function (e) {
-      e.preventDefault();
-      if ($(this).hasClass('ban-change')) {
-        return false;
-      }
-
-      hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
-      if ($(this).hasClass('checked')) {
-        $(this).removeClass('checked');
-        $(this).find('.octicon-check').addClass('invisible');
-        if (hasUpdateAction) {
-          if (!($(this).data('id') in items)) {
-            items[$(this).data('id')] = {
-              'update-url': $listMenu.data('update-url'),
-              action: 'detach',
-              'issue-id': $listMenu.data('issue-id'),
-            };
-          } else {
-            delete items[$(this).data('id')];
-          }
-        }
-      } else {
-        $(this).addClass('checked');
-        $(this).find('.octicon-check').removeClass('invisible');
-        if (hasUpdateAction) {
-          if (!($(this).data('id') in items)) {
-            items[$(this).data('id')] = {
-              'update-url': $listMenu.data('update-url'),
-              action: 'attach',
-              'issue-id': $listMenu.data('issue-id'),
-            };
-          } else {
-            delete items[$(this).data('id')];
-          }
-        }
-      }
-
-      // TODO: Which thing should be done for choosing review requests
-      // to make chosen items be shown on time here?
-      if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
-        return false;
-      }
-
-      const listIds = [];
-      $(this).parent().find('.item').each(function () {
-        if ($(this).hasClass('checked')) {
-          listIds.push($(this).data('id'));
-          $($(this).data('id-selector')).removeClass('hide');
-        } else {
-          $($(this).data('id-selector')).addClass('hide');
-        }
-      });
-      if (listIds.length === 0) {
-        $noSelect.removeClass('hide');
-      } else {
-        $noSelect.addClass('hide');
-      }
-      $($(this).parent().data('id')).val(listIds.join(','));
-      return false;
-    });
-    $listMenu.find('.no-select.item').on('click', function (e) {
-      e.preventDefault();
-      if (hasUpdateAction) {
-        updateIssuesMeta(
-          $listMenu.data('update-url'),
-          'clear',
-          $listMenu.data('issue-id'),
-          '',
-        ).then(reload);
-      }
-
-      $(this).parent().find('.item').each(function () {
-        $(this).removeClass('checked');
-        $(this).find('.octicon').addClass('invisible');
-      });
-
-      if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
-        return false;
-      }
-
-      $list.find('.item').each(function () {
-        $(this).addClass('hide');
-      });
-      $noSelect.removeClass('hide');
-      $($(this).parent().data('id')).val('');
-    });
-  }
-
-  // Init labels and assignees
-  initListSubmits('select-label', 'labels');
-  initListSubmits('select-assignees', 'assignees');
-  initListSubmits('select-assignees-modify', 'assignees');
-  initListSubmits('select-reviewers-modify', 'assignees');
-
-  function selectItem(select_id, input_id) {
-    const $menu = $(`${select_id} .menu`);
-    const $list = $(`.ui${select_id}.list`);
-    const hasUpdateAction = $menu.data('action') === 'update';
-
-    $menu.find('.item:not(.no-select)').on('click', function () {
-      $(this).parent().find('.item').each(function () {
-        $(this).removeClass('selected active');
-      });
-
-      $(this).addClass('selected active');
-      if (hasUpdateAction) {
-        updateIssuesMeta(
-          $menu.data('update-url'),
-          '',
-          $menu.data('issue-id'),
-          $(this).data('id'),
-        ).then(reload);
-      }
-
-      let icon = '';
-      if (input_id === '#milestone_id') {
-        icon = svg('octicon-milestone', 18, 'mr-3');
-      } else if (input_id === '#project_id') {
-        icon = svg('octicon-project', 18, 'mr-3');
-      } else if (input_id === '#assignee_id') {
-        icon = `<img class="ui avatar image mr-3" src=${$(this).data('avatar')}>`;
-      }
-
-      $list.find('.selected').html(`
-        <a class="item muted sidebar-item-link" href=${$(this).data('href')}>
-          ${icon}
-          ${htmlEscape($(this).text())}
-        </a>
-      `);
-
-      $(`.ui${select_id}.list .no-select`).addClass('hide');
-      $(input_id).val($(this).data('id'));
-    });
-    $menu.find('.no-select.item').on('click', function () {
-      $(this).parent().find('.item:not(.no-select)').each(function () {
-        $(this).removeClass('selected active');
-      });
-
-      if (hasUpdateAction) {
-        updateIssuesMeta(
-          $menu.data('update-url'),
-          '',
-          $menu.data('issue-id'),
-          $(this).data('id'),
-        ).then(reload);
-      }
-
-      $list.find('.selected').html('');
-      $list.find('.no-select').removeClass('hide');
-      $(input_id).val('');
-    });
-  }
-
-  // Milestone, Assignee, Project
-  selectItem('.select-project', '#project_id');
-  selectItem('.select-milestone', '#milestone_id');
-  selectItem('.select-assignee', '#assignee_id');
-}
-
-function initInstall() {
-  if ($('.install').length === 0) {
-    return;
-  }
-
-  if ($('#db_host').val() === '') {
-    $('#db_host').val('127.0.0.1:3306');
-    $('#db_user').val('gitea');
-    $('#db_name').val('gitea');
-  }
-
-  // Database type change detection.
-  $('#db_type').on('change', function () {
-    const sqliteDefault = 'data/gitea.db';
-    const tidbDefault = 'data/gitea_tidb';
-
-    const dbType = $(this).val();
-    if (dbType === 'SQLite3') {
-      $('#sql_settings').hide();
-      $('#pgsql_settings').hide();
-      $('#mysql_settings').hide();
-      $('#sqlite_settings').show();
-
-      if (dbType === 'SQLite3' && $('#db_path').val() === tidbDefault) {
-        $('#db_path').val(sqliteDefault);
-      }
-      return;
-    }
-
-    const dbDefaults = {
-      MySQL: '127.0.0.1:3306',
-      PostgreSQL: '127.0.0.1:5432',
-      MSSQL: '127.0.0.1:1433'
-    };
-
-    $('#sqlite_settings').hide();
-    $('#sql_settings').show();
-
-    $('#pgsql_settings').toggle(dbType === 'PostgreSQL');
-    $('#mysql_settings').toggle(dbType === 'MySQL');
-    $.each(dbDefaults, (_type, defaultHost) => {
-      if ($('#db_host').val() === defaultHost) {
-        $('#db_host').val(dbDefaults[dbType]);
-        return false;
-      }
-    });
-  });
-
-  // TODO: better handling of exclusive relations.
-  $('#offline-mode input').on('change', function () {
-    if ($(this).is(':checked')) {
-      $('#disable-gravatar').checkbox('check');
-      $('#federated-avatar-lookup').checkbox('uncheck');
-    }
-  });
-  $('#disable-gravatar input').on('change', function () {
-    if ($(this).is(':checked')) {
-      $('#federated-avatar-lookup').checkbox('uncheck');
-    } else {
-      $('#offline-mode').checkbox('uncheck');
-    }
-  });
-  $('#federated-avatar-lookup input').on('change', function () {
-    if ($(this).is(':checked')) {
-      $('#disable-gravatar').checkbox('uncheck');
-      $('#offline-mode').checkbox('uncheck');
-    }
-  });
-  $('#enable-openid-signin input').on('change', function () {
-    if ($(this).is(':checked')) {
-      if (!$('#disable-registration input').is(':checked')) {
-        $('#enable-openid-signup').checkbox('check');
-      }
-    } else {
-      $('#enable-openid-signup').checkbox('uncheck');
-    }
-  });
-  $('#disable-registration input').on('change', function () {
-    if ($(this).is(':checked')) {
-      $('#enable-captcha').checkbox('uncheck');
-      $('#enable-openid-signup').checkbox('uncheck');
-    } else {
-      $('#enable-openid-signup').checkbox('check');
-    }
-  });
-  $('#enable-captcha input').on('change', function () {
-    if ($(this).is(':checked')) {
-      $('#disable-registration').checkbox('uncheck');
-    }
-  });
-}
-
-function initIssueComments() {
-  if ($('.repository.view.issue .timeline').length === 0) return;
-
-  $('.re-request-review').on('click', function (event) {
-    const url = $(this).data('update-url');
-    const issueId = $(this).data('issue-id');
-    const id = $(this).data('id');
-    const isChecked = $(this).hasClass('checked');
-
-    event.preventDefault();
-    updateIssuesMeta(
-      url,
-      isChecked ? 'detach' : 'attach',
-      issueId,
-      id,
-    ).then(reload);
-    return false;
-  });
-
-  $('.dismiss-review-btn').on('click', function (e) {
-    e.preventDefault();
-    const $this = $(this);
-    const $dismissReviewModal = $this.next();
-    $dismissReviewModal.modal('show');
-  });
-
-  $(document).on('click', (event) => {
-    const urlTarget = $(':target');
-    if (urlTarget.length === 0) return;
-
-    const urlTargetId = urlTarget.attr('id');
-    if (!urlTargetId) return;
-    if (!/^(issue|pull)(comment)?-\d+$/.test(urlTargetId)) return;
-
-    const $target = $(event.target);
-
-    if ($target.closest(`#${urlTargetId}`).length === 0) {
-      const scrollPosition = $(window).scrollTop();
-      window.location.hash = '';
-      $(window).scrollTop(scrollPosition);
-      window.history.pushState(null, null, ' ');
-    }
-  });
-}
-
-function getArchive($target, url, first) {
-  $.ajax({
-    url,
-    type: 'POST',
-    data: {
-      _csrf: csrf,
-    },
-    complete(xhr) {
-      if (xhr.status === 200) {
-        if (!xhr.responseJSON) {
-          // XXX Shouldn't happen?
-          $target.closest('.dropdown').children('i').removeClass('loading');
-          return;
-        }
-
-        if (!xhr.responseJSON.complete) {
-          $target.closest('.dropdown').children('i').addClass('loading');
-          // Wait for only three quarters of a second initially, in case it's
-          // quickly archived.
-          setTimeout(() => {
-            getArchive($target, url, false);
-          }, first ? 750 : 2000);
-        } else {
-          // We don't need to continue checking.
-          $target.closest('.dropdown').children('i').removeClass('loading');
-          window.location.href = url;
-        }
-      }
-    }
-  });
-}
-
-function initArchiveLinks() {
-  if ($('.archive-link').length === 0) {
-    return;
-  }
-
-  $('.archive-link').on('click', function (event) {
-    const url = $(this).data('url');
-    if (typeof url === 'undefined') {
-      return;
-    }
-
-    event.preventDefault();
-    getArchive($(event.target), url, true);
-  });
-}
-
-async function initRepository() {
-  if ($('.repository').length === 0) {
-    return;
-  }
-
-  function initFilterSearchDropdown(selector) {
-    const $dropdown = $(selector);
-    $dropdown.dropdown({
-      fullTextSearch: true,
-      selectOnKeydown: false,
-      onChange(_text, _value, $choice) {
-        if ($choice.data('url')) {
-          window.location.href = $choice.data('url');
-        }
-      },
-      message: {noResults: $dropdown.data('no-results')}
-    });
-  }
-
-  // Commit statuses
-  $('.commit-statuses-trigger').each(function () {
-    $(this)
-      .popup({
-        on: 'click',
-        position: ($('.repository.file.list').length > 0 ? 'right center' : 'left center'),
-      });
-  });
-
-  // File list and commits
-  if ($('.repository.file.list').length > 0 ||
-    $('.repository.commits').length > 0 || $('.repository.release').length > 0) {
-    initRepoBranchTagDropdown('.choose.reference .dropdown');
-  }
-
-  // Wiki
-  if ($('.repository.wiki.view').length > 0) {
-    initFilterSearchDropdown('.choose.page .dropdown');
-  }
-
-  // Options
-  if ($('.repository.settings.options').length > 0) {
-    // Enable or select internal/external wiki system and issue tracker.
-    $('.enable-system').on('change', function () {
-      if (this.checked) {
-        $($(this).data('target')).removeClass('disabled');
-        if (!$(this).data('context')) $($(this).data('context')).addClass('disabled');
-      } else {
-        $($(this).data('target')).addClass('disabled');
-        if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled');
-      }
-    });
-    $('.enable-system-radio').on('change', function () {
-      if (this.value === 'false') {
-        $($(this).data('target')).addClass('disabled');
-        if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).removeClass('disabled');
-      } else if (this.value === 'true') {
-        $($(this).data('target')).removeClass('disabled');
-        if (typeof $(this).data('context') !== 'undefined') $($(this).data('context')).addClass('disabled');
-      }
-    });
-  }
-
-  // Labels
-  if ($('.repository.labels').length > 0) {
-    initLabelEdit();
-  }
-
-  // Milestones
-  if ($('.repository.new.milestone').length > 0) {
-    $('#clear-date').on('click', () => {
-      $('#deadline').val('');
-      return false;
-    });
-  }
-
-  // Repo Creation
-  if ($('.repository.new.repo').length > 0) {
-    $('input[name="gitignores"], input[name="license"]').on('change', () => {
-      const gitignores = $('input[name="gitignores"]').val();
-      const license = $('input[name="license"]').val();
-      if (gitignores || license) {
-        $('input[name="auto_init"]').prop('checked', true);
-      }
-    });
-  }
-
-  // Issues
-  if ($('.repository.view.issue').length > 0) {
-    // Edit issue title
-    const $issueTitle = $('#issue-title');
-    const $editInput = $('#edit-title-input input');
-    const editTitleToggle = function () {
-      $issueTitle.toggle();
-      $('.not-in-edit').toggle();
-      $('#edit-title-input').toggle();
-      $('#pull-desc').toggle();
-      $('#pull-desc-edit').toggle();
-      $('.in-edit').toggle();
-      $('#issue-title-wrapper').toggleClass('edit-active');
-      $editInput.focus();
-      return false;
-    };
-
-    const changeBranchSelect = function () {
-      const selectionTextField = $('#pull-target-branch');
-
-      const baseName = selectionTextField.data('basename');
-      const branchNameNew = $(this).data('branch');
-      const branchNameOld = selectionTextField.data('branch');
-
-      // Replace branch name to keep translation from HTML template
-      selectionTextField.html(selectionTextField.html().replace(
-        `${baseName}:${branchNameOld}`,
-        `${baseName}:${branchNameNew}`
-      ));
-      selectionTextField.data('branch', branchNameNew); // update branch name in setting
-    };
-    $('#branch-select > .item').on('click', changeBranchSelect);
-
-    $('#edit-title').on('click', editTitleToggle);
-    $('#cancel-edit-title').on('click', editTitleToggle);
-    $('#save-edit-title').on('click', editTitleToggle).on('click', function () {
-      const pullrequest_targetbranch_change = function (update_url) {
-        const targetBranch = $('#pull-target-branch').data('branch');
-        const $branchTarget = $('#branch_target');
-        if (targetBranch === $branchTarget.text()) {
-          return false;
-        }
-        $.post(update_url, {
-          _csrf: csrf,
-          target_branch: targetBranch
-        }).done((data) => {
-          $branchTarget.text(data.base_branch);
-        }).always(() => {
-          reload();
-        });
-      };
-
-      const pullrequest_target_update_url = $(this).data('target-update-url');
-      if ($editInput.val().length === 0 || $editInput.val() === $issueTitle.text()) {
-        $editInput.val($issueTitle.text());
-        pullrequest_targetbranch_change(pullrequest_target_update_url);
-      } else {
-        $.post($(this).data('update-url'), {
-          _csrf: csrf,
-          title: $editInput.val()
-        }, (data) => {
-          $editInput.val(data.title);
-          $issueTitle.text(data.title);
-          pullrequest_targetbranch_change(pullrequest_target_update_url);
-          reload();
-        });
-      }
-      return false;
-    });
-
-    // Toggle WIP
-    $('.toggle-wip a, .toggle-wip button').on('click', async (e) => {
-      e.preventDefault();
-      const {title, wipPrefix, updateUrl} = e.currentTarget.closest('.toggle-wip').dataset;
-      await $.post(updateUrl, {
-        _csrf: csrf,
-        title: title?.startsWith(wipPrefix) ? title.substr(wipPrefix.length).trim() : `${wipPrefix.trim()} ${title}`,
-      });
-      reload();
-    });
-
-    // Issue Comments
-    initIssueComments();
-
-    // Issue/PR Context Menus
-    $('.context-dropdown').dropdown({
-      action: 'hide'
-    });
-
-    // Previous/Next code review conversation
-    $(document).on('click', '.previous-conversation', (e) => {
-      const $conversation = $(e.currentTarget).closest('.comment-code-cloud');
-      const $conversations = $('.comment-code-cloud:not(.hide)');
-      const index = $conversations.index($conversation);
-      const previousIndex = index > 0 ? index - 1 : $conversations.length - 1;
-      const $previousConversation = $conversations.eq(previousIndex);
-      const anchor = $previousConversation.find('.comment').first().attr('id');
-      window.location.href = `#${anchor}`;
-    });
-    $(document).on('click', '.next-conversation', (e) => {
-      const $conversation = $(e.currentTarget).closest('.comment-code-cloud');
-      const $conversations = $('.comment-code-cloud:not(.hide)');
-      const index = $conversations.index($conversation);
-      const nextIndex = index < $conversations.length - 1 ? index + 1 : 0;
-      const $nextConversation = $conversations.eq(nextIndex);
-      const anchor = $nextConversation.find('.comment').first().attr('id');
-      window.location.href = `#${anchor}`;
-    });
-
-    // Quote reply
-    $(document).on('click', '.quote-reply', function (event) {
-      $(this).closest('.dropdown').find('.menu').toggle('visible');
-      const target = $(this).data('target');
-      const quote = $(`#comment-${target}`).text().replace(/\n/g, '\n> ');
-      const content = `> ${quote}\n\n`;
-      let $simplemde = autoSimpleMDE;
-      if ($(this).hasClass('quote-reply-diff')) {
-        const $parent = $(this).closest('.comment-code-cloud');
-        $parent.find('button.comment-form-reply').trigger('click');
-        $simplemde = $parent.find('[name="content"]').data('simplemde');
-      }
-      if ($simplemde !== null) {
-        if ($simplemde.value() !== '') {
-          $simplemde.value(`${$simplemde.value()}\n\n${content}`);
-        } else {
-          $simplemde.value(`${content}`);
-        }
-      }
-      requestAnimationFrame(() => {
-        $simplemde.codemirror.focus();
-        $simplemde.codemirror.setCursor($simplemde.codemirror.lineCount(), 0);
-      });
-      event.preventDefault();
-    });
-
-    // Reference issue
-    $(document).on('click', '.reference-issue', function (event) {
-      const $this = $(this);
-
-      $this.closest('.dropdown').find('.menu').toggle('visible');
-
-      const content = $(`#comment-${$this.data('target')}`).text();
-
-      const poster = $this.data('poster-username');
-      const reference = $this.data('reference');
-
-      const $modal = $($this.data('modal'));
-      $modal.find('textarea[name="content"]').val(`${content}\n\n_Originally posted by @${poster} in ${reference}_`);
-
-      $modal.modal('show');
-
-      event.preventDefault();
-    });
-
-    // Edit issue or comment content
-    $(document).on('click', '.edit-content', async function (event) {
-      $(this).closest('.dropdown').find('.menu').toggle('visible');
-      const $segment = $(this).closest('.header').next();
-      const $editContentZone = $segment.find('.edit-content-zone');
-      const $renderContent = $segment.find('.render-content');
-      const $rawContent = $segment.find('.raw-content');
-      let $textarea;
-      let $simplemde;
-
-      // Setup new form
-      if ($editContentZone.html().length === 0) {
-        $editContentZone.html($('#edit-content-form').html());
-        $textarea = $editContentZone.find('textarea');
-        attachTribute($textarea.get(), {mentions: true, emoji: true});
-
-        let dz;
-        const $dropzone = $editContentZone.find('.dropzone');
-        if ($dropzone.length === 1) {
-          $dropzone.data('saved', false);
-
-          const fileUuidDict = {};
-          dz = await createDropzone($dropzone[0], {
-            url: $dropzone.data('upload-url'),
-            headers: {'X-Csrf-Token': csrf},
-            maxFiles: $dropzone.data('max-file'),
-            maxFilesize: $dropzone.data('max-size'),
-            acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'),
-            addRemoveLinks: true,
-            dictDefaultMessage: $dropzone.data('default-message'),
-            dictInvalidFileType: $dropzone.data('invalid-input-type'),
-            dictFileTooBig: $dropzone.data('file-too-big'),
-            dictRemoveFile: $dropzone.data('remove-file'),
-            timeout: 0,
-            thumbnailMethod: 'contain',
-            thumbnailWidth: 480,
-            thumbnailHeight: 480,
-            init() {
-              this.on('success', (file, data) => {
-                fileUuidDict[file.uuid] = {
-                  submitted: false
-                };
-                const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
-                $dropzone.find('.files').append(input);
-              });
-              this.on('removedfile', (file) => {
-                $(`#${file.uuid}`).remove();
-                if ($dropzone.data('remove-url') && !fileUuidDict[file.uuid].submitted) {
-                  $.post($dropzone.data('remove-url'), {
-                    file: file.uuid,
-                    _csrf: csrf,
-                  });
-                }
-              });
-              this.on('submit', () => {
-                $.each(fileUuidDict, (fileUuid) => {
-                  fileUuidDict[fileUuid].submitted = true;
-                });
-              });
-              this.on('reload', () => {
-                $.getJSON($editContentZone.data('attachment-url'), (data) => {
-                  dz.removeAllFiles(true);
-                  $dropzone.find('.files').empty();
-                  $.each(data, function () {
-                    const imgSrc = `${$dropzone.data('link-url')}/${this.uuid}`;
-                    dz.emit('addedfile', this);
-                    dz.emit('thumbnail', this, imgSrc);
-                    dz.emit('complete', this);
-                    dz.files.push(this);
-                    fileUuidDict[this.uuid] = {
-                      submitted: true,
-                    };
-                    $dropzone.find(`img[src='${imgSrc}']`).css('max-width', '100%');
-                    const input = $(`<input id="${this.uuid}" name="files" type="hidden">`).val(this.uuid);
-                    $dropzone.find('.files').append(input);
-                  });
-                });
-              });
-            }
-          });
-          dz.emit('reload');
-        }
-        // Give new write/preview data-tab name to distinguish from others
-        const $editContentForm = $editContentZone.find('.ui.comment.form');
-        const $tabMenu = $editContentForm.find('.tabular.menu');
-        $tabMenu.attr('data-write', $editContentZone.data('write'));
-        $tabMenu.attr('data-preview', $editContentZone.data('preview'));
-        $tabMenu.find('.write.item').attr('data-tab', $editContentZone.data('write'));
-        $tabMenu.find('.preview.item').attr('data-tab', $editContentZone.data('preview'));
-        $editContentForm.find('.write').attr('data-tab', $editContentZone.data('write'));
-        $editContentForm.find('.preview').attr('data-tab', $editContentZone.data('preview'));
-        $simplemde = setCommentSimpleMDE($textarea);
-        commentMDEditors[$editContentZone.data('write')] = $simplemde;
-        initCommentPreviewTab($editContentForm);
-        if ($dropzone.length === 1) {
-          initSimpleMDEImagePaste($simplemde, $dropzone[0], $dropzone.find('.files'));
-        }
-
-        $editContentZone.find('.cancel.button').on('click', () => {
-          $renderContent.show();
-          $editContentZone.hide();
-          if (dz) {
-            dz.emit('reload');
-          }
-        });
-        $editContentZone.find('.save.button').on('click', () => {
-          $renderContent.show();
-          $editContentZone.hide();
-          const $attachments = $dropzone.find('.files').find('[name=files]').map(function () {
-            return $(this).val();
-          }).get();
-          $.post($editContentZone.data('update-url'), {
-            _csrf: csrf,
-            content: $textarea.val(),
-            context: $editContentZone.data('context'),
-            files: $attachments
-          }, (data) => {
-            if (data.length === 0 || data.content.length === 0) {
-              $renderContent.html($('#no-content').html());
-              $rawContent.text('');
-            } else {
-              $renderContent.html(data.content);
-              $rawContent.text($textarea.val());
-            }
-            const $content = $segment;
-            if (!$content.find('.dropzone-attachments').length) {
-              if (data.attachments !== '') {
-                $content.append(`
-                  <div class="dropzone-attachments">
-                  </div>
-                `);
-                $content.find('.dropzone-attachments').replaceWith(data.attachments);
-              }
-            } else if (data.attachments === '') {
-              $content.find('.dropzone-attachments').remove();
-            } else {
-              $content.find('.dropzone-attachments').replaceWith(data.attachments);
-            }
-            if (dz) {
-              dz.emit('submit');
-              dz.emit('reload');
-            }
-            initMarkupContent();
-            initCommentContent();
-          });
-        });
-      } else {
-        $textarea = $segment.find('textarea');
-        $simplemde = commentMDEditors[$editContentZone.data('write')];
-      }
-
-      // Show write/preview tab and copy raw content as needed
-      $editContentZone.show();
-      $renderContent.hide();
-      if ($textarea.val().length === 0) {
-        $textarea.val($rawContent.text());
-        $simplemde.value($rawContent.text());
-      }
-      requestAnimationFrame(() => {
-        $textarea.focus();
-        $simplemde.codemirror.focus();
-      });
-      event.preventDefault();
-    });
-
-    // Delete comment
-    $(document).on('click', '.delete-comment', function () {
-      const $this = $(this);
-      if (window.confirm($this.data('locale'))) {
-        $.post($this.data('url'), {
-          _csrf: csrf
-        }).done(() => {
-          const $conversationHolder = $this.closest('.conversation-holder');
-          $(`#${$this.data('comment-id')}`).remove();
-          if ($conversationHolder.length && !$conversationHolder.find('.comment').length) {
-            const path = $conversationHolder.data('path');
-            const side = $conversationHolder.data('side');
-            const idx = $conversationHolder.data('idx');
-            const lineType = $conversationHolder.closest('tr').data('line-type');
-            if (lineType === 'same') {
-              $(`a.add-code-comment[data-path="${path}"][data-idx="${idx}"]`).removeClass('invisible');
-            } else {
-              $(`a.add-code-comment[data-path="${path}"][data-side="${side}"][data-idx="${idx}"]`).removeClass('invisible');
-            }
-            $conversationHolder.remove();
-          }
-        });
-      }
-      return false;
-    });
-
-    // Delete Issue dependency
-    $(document).on('click', '.delete-dependency-button', (e) => {
-      const {id, type} = e.currentTarget.dataset;
-
-      $('.remove-dependency').modal({
-        closable: false,
-        duration: 200,
-        onApprove: () => {
-          $('#removeDependencyID').val(id);
-          $('#dependencyType').val(type);
-          $('#removeDependencyForm').trigger('submit');
-        }
-      }).modal('show');
-    });
-
-    // Cancel inline code comment
-    $(document).on('click', '.cancel-code-comment', (e) => {
-      const form = $(e.currentTarget).closest('form');
-      if (form.length > 0 && form.hasClass('comment-form')) {
-        form.addClass('hide');
-        form.closest('.comment-code-cloud').find('button.comment-form-reply').show();
-      } else {
-        form.closest('.comment-code-cloud').remove();
-      }
-    });
-
-    // Change status
-    const $statusButton = $('#status-button');
-    $('#comment-form textarea').on('keyup', function () {
-      const $simplemde = $(this).data('simplemde');
-      const value = ($simplemde && $simplemde.value()) ? $simplemde.value() : $(this).val();
-      $statusButton.text($statusButton.data(value.length === 0 ? 'status' : 'status-and-comment'));
-    });
-    $statusButton.on('click', () => {
-      $('#status').val($statusButton.data('status-val'));
-      $('#comment-form').trigger('submit');
-    });
-
-    // Pull Request merge button
-    const $mergeButton = $('.merge-button > button');
-    $mergeButton.on('click', function (e) {
-      e.preventDefault();
-      $(`.${$(this).data('do')}-fields`).show();
-      $(this).parent().hide();
-      $('.instruct-toggle').hide();
-      $('.instruct-content').hide();
-    });
-    $('.merge-button > .dropdown').dropdown({
-      onChange(_text, _value, $choice) {
-        if ($choice.data('do')) {
-          $mergeButton.find('.button-text').text($choice.text());
-          $mergeButton.data('do', $choice.data('do'));
-        }
-      }
-    });
-    $('.merge-cancel').on('click', function (e) {
-      e.preventDefault();
-      $(this).closest('.form').hide();
-      $mergeButton.parent().show();
-      $('.instruct-toggle').show();
-    });
-
-    // Pull Request update button
-    const $pullUpdateButton = $('.update-button > button');
-    $pullUpdateButton.on('click', function (e) {
-      e.preventDefault();
-      const $this = $(this);
-      const redirect = $this.data('redirect');
-      $this.addClass('loading');
-      $.post($this.data('do'), {
-        _csrf: csrf
-      }).done((data) => {
-        if (data.redirect) {
-          window.location.href = data.redirect;
-        } else if (redirect) {
-          window.location.href = redirect;
-        } else {
-          window.location.reload();
-        }
-      });
-    });
-
-    $('.update-button > .dropdown').dropdown({
-      onChange(_text, _value, $choice) {
-        const $url = $choice.data('do');
-        if ($url) {
-          $pullUpdateButton.find('.button-text').text($choice.text());
-          $pullUpdateButton.data('do', $url);
-        }
-      }
-    });
-
-    initReactionSelector();
-  }
-
-  // Quick start and repository home
-  $('#repo-clone-ssh').on('click', function () {
-    $('.clone-url').text($(this).data('link'));
-    $('#repo-clone-url').val($(this).data('link'));
-    $(this).addClass('primary');
-    $('#repo-clone-https').removeClass('primary');
-    localStorage.setItem('repo-clone-protocol', 'ssh');
-  });
-  $('#repo-clone-https').on('click', function () {
-    $('.clone-url').text($(this).data('link'));
-    $('#repo-clone-url').val($(this).data('link'));
-    $(this).addClass('primary');
-    if ($('#repo-clone-ssh').length > 0) {
-      $('#repo-clone-ssh').removeClass('primary');
-      localStorage.setItem('repo-clone-protocol', 'https');
-    }
-  });
-  $('#repo-clone-url').on('click', function () {
-    $(this).select();
-  });
-
-  // Compare or pull request
-  const $repoDiff = $('.repository.diff');
-  if ($repoDiff.length) {
-    initBranchOrTagDropdown('.choose.branch .dropdown');
-    initFilterSearchDropdown('.choose.branch .dropdown');
-  }
-
-  // Pull request
-  const $repoComparePull = $('.repository.compare.pull');
-  if ($repoComparePull.length > 0) {
-    // show pull request form
-    $repoComparePull.find('button.show-form').on('click', function (e) {
-      e.preventDefault();
-      $repoComparePull.find('.pullrequest-form').show();
-      autoSimpleMDE.codemirror.refresh();
-      $(this).parent().hide();
-    });
-  }
-
-  // Branches
-  if ($('.repository.settings.branches').length > 0) {
-    initFilterSearchDropdown('.protected-branches .dropdown');
-    $('.enable-protection, .enable-whitelist, .enable-statuscheck').on('change', function () {
-      if (this.checked) {
-        $($(this).data('target')).removeClass('disabled');
-      } else {
-        $($(this).data('target')).addClass('disabled');
-      }
-    });
-    $('.disable-whitelist').on('change', function () {
-      if (this.checked) {
-        $($(this).data('target')).addClass('disabled');
-      }
-    });
-  }
-
-  // Language stats
-  if ($('.language-stats').length > 0) {
-    $('.language-stats').on('click', (e) => {
-      e.preventDefault();
-      $('.language-stats-details, .repository-menu').slideToggle();
-    });
-  }
-}
-
-function initPullRequestMergeInstruction() {
-  $('.show-instruction').on('click', () => {
-    $('.instruct-content').toggle();
-  });
-}
-
-function initRelease() {
-  $(document).on('click', '.remove-rel-attach', function() {
-    const uuid = $(this).data('uuid');
-    const id = $(this).data('id');
-    $(`input[name='attachment-del-${uuid}']`).attr('value', true);
-    $(`#attachment-${id}`).hide();
-  });
-}
-
-function initPullRequestReview() {
-  if (window.location.hash && window.location.hash.startsWith('#issuecomment-')) {
-    const commentDiv = $(window.location.hash);
-    if (commentDiv) {
-      // get the name of the parent id
-      const groupID = commentDiv.closest('div[id^="code-comments-"]').attr('id');
-      if (groupID && groupID.startsWith('code-comments-')) {
-        const id = groupID.substr(14);
-        $(`#show-outdated-${id}`).addClass('hide');
-        $(`#code-comments-${id}`).removeClass('hide');
-        $(`#code-preview-${id}`).removeClass('hide');
-        $(`#hide-outdated-${id}`).removeClass('hide');
-        commentDiv[0].scrollIntoView();
-      }
-    }
-  }
-
-  $(document).on('click', '.show-outdated', function (e) {
-    e.preventDefault();
-    const id = $(this).data('comment');
-    $(this).addClass('hide');
-    $(`#code-comments-${id}`).removeClass('hide');
-    $(`#code-preview-${id}`).removeClass('hide');
-    $(`#hide-outdated-${id}`).removeClass('hide');
-  });
-
-  $(document).on('click', '.hide-outdated', function (e) {
-    e.preventDefault();
-    const id = $(this).data('comment');
-    $(this).addClass('hide');
-    $(`#code-comments-${id}`).addClass('hide');
-    $(`#code-preview-${id}`).addClass('hide');
-    $(`#show-outdated-${id}`).removeClass('hide');
-  });
-
-  $(document).on('click', 'button.comment-form-reply', function (e) {
-    e.preventDefault();
-    $(this).hide();
-    const form = $(this).closest('.comment-code-cloud').find('.comment-form');
-    form.removeClass('hide');
-    const $textarea = form.find('textarea');
-    let $simplemde;
-    if ($textarea.data('simplemde')) {
-      $simplemde = $textarea.data('simplemde');
-    } else {
-      attachTribute($textarea.get(), {mentions: true, emoji: true});
-      $simplemde = setCommentSimpleMDE($textarea);
-      $textarea.data('simplemde', $simplemde);
-    }
-    $textarea.focus();
-    $simplemde.codemirror.focus();
-    assignMenuAttributes(form.find('.menu'));
-  });
-
-  const $reviewBox = $('.review-box');
-  if ($reviewBox.length === 1) {
-    setCommentSimpleMDE($reviewBox.find('textarea'));
-    initImagePaste($reviewBox);
-  }
-
-  // The following part is only for diff views
-  if ($('.repository.pull.diff').length === 0) {
-    return;
-  }
-
-  $('.btn-review').on('click', function (e) {
-    e.preventDefault();
-    $(this).closest('.dropdown').find('.menu').toggle('visible');
-  }).closest('.dropdown').find('.close').on('click', function (e) {
-    e.preventDefault();
-    $(this).closest('.menu').toggle('visible');
-  });
-
-  $('a.add-code-comment').on('click', async function (e) {
-    if ($(e.target).hasClass('btn-add-single')) return; // https://github.com/go-gitea/gitea/issues/4745
-    e.preventDefault();
-
-    const isSplit = $(this).closest('.code-diff').hasClass('code-diff-split');
-    const side = $(this).data('side');
-    const idx = $(this).data('idx');
-    const path = $(this).data('path');
-    const tr = $(this).closest('tr');
-    const lineType = tr.data('line-type');
-
-    let ntr = tr.next();
-    if (!ntr.hasClass('add-comment')) {
-      ntr = $(`
-        <tr class="add-comment" data-line-type="${lineType}">
-          ${isSplit ? `
-            <td class="lines-num"></td>
-            <td class="lines-type-marker"></td>
-            <td class="add-comment-left"></td>
-            <td class="lines-num"></td>
-            <td class="lines-type-marker"></td>
-            <td class="add-comment-right"></td>
-          ` : `
-            <td colspan="2" class="lines-num"></td>
-            <td class="add-comment-left add-comment-right" colspan="2"></td>
-          `}
-        </tr>`);
-      tr.after(ntr);
-    }
-
-    const td = ntr.find(`.add-comment-${side}`);
-    let commentCloud = td.find('.comment-code-cloud');
-    if (commentCloud.length === 0 && !ntr.find('button[name="is_review"]').length) {
-      const data = await $.get($(this).data('new-comment-url'));
-      td.html(data);
-      commentCloud = td.find('.comment-code-cloud');
-      assignMenuAttributes(commentCloud.find('.menu'));
-      td.find("input[name='line']").val(idx);
-      td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed');
-      td.find("input[name='path']").val(path);
-      const $textarea = commentCloud.find('textarea');
-      attachTribute($textarea.get(), {mentions: true, emoji: true});
-      const $simplemde = setCommentSimpleMDE($textarea);
-      $textarea.focus();
-      $simplemde.codemirror.focus();
-    }
-  });
-}
-
-function assignMenuAttributes(menu) {
-  const id = Math.floor(Math.random() * Math.floor(1000000));
-  menu.attr('data-write', menu.attr('data-write') + id);
-  menu.attr('data-preview', menu.attr('data-preview') + id);
-  menu.find('.item').each(function () {
-    const tab = $(this).attr('data-tab') + id;
-    $(this).attr('data-tab', tab);
-  });
-  menu.parent().find("*[data-tab='write']").attr('data-tab', `write${id}`);
-  menu.parent().find("*[data-tab='preview']").attr('data-tab', `preview${id}`);
-  initCommentPreviewTab(menu.parent('.form'));
-  return id;
-}
-
-function initRepositoryCollaboration() {
-  // Change collaborator access mode
-  $('.access-mode.menu .item').on('click', function () {
-    const $menu = $(this).parent();
-    $.post($menu.data('url'), {
-      _csrf: csrf,
-      uid: $menu.data('uid'),
-      mode: $(this).data('value')
-    });
-  });
-}
-
-function initTeamSettings() {
-  // Change team access mode
-  $('.organization.new.team input[name=permission]').on('change', () => {
-    const val = $('input[name=permission]:checked', '.organization.new.team').val();
-    if (val === 'admin') {
-      $('.organization.new.team .team-units').hide();
-    } else {
-      $('.organization.new.team .team-units').show();
-    }
-  });
-}
-
-function initWikiForm() {
-  const $editArea = $('.repository.wiki textarea#edit_area');
-  let sideBySideChanges = 0;
-  let sideBySideTimeout = null;
-  let hasSimpleMDE = true;
-  if ($editArea.length > 0) {
-    const simplemde = new SimpleMDE({
-      autoDownloadFontAwesome: false,
-      element: $editArea[0],
-      forceSync: true,
-      previewRender(plainText, preview) { // Async method
-        // FIXME: still send render request when return back to edit mode
-        const render = function () {
-          sideBySideChanges = 0;
-          if (sideBySideTimeout !== null) {
-            clearTimeout(sideBySideTimeout);
-            sideBySideTimeout = null;
-          }
-          $.post($editArea.data('url'), {
-            _csrf: csrf,
-            mode: 'gfm',
-            context: $editArea.data('context'),
-            text: plainText,
-            wiki: true
-          }, (data) => {
-            preview.innerHTML = `<div class="markup ui segment">${data}</div>`;
-            initMarkupContent();
-          });
-        };
-
-        setTimeout(() => {
-          if (!simplemde.isSideBySideActive()) {
-            render();
-          } else {
-            // delay preview by keystroke counting
-            sideBySideChanges++;
-            if (sideBySideChanges > 10) {
-              render();
-            }
-            // or delay preview by timeout
-            if (sideBySideTimeout !== null) {
-              clearTimeout(sideBySideTimeout);
-              sideBySideTimeout = null;
-            }
-            sideBySideTimeout = setTimeout(render, 600);
-          }
-        }, 0);
-        if (!simplemde.isSideBySideActive()) {
-          return 'Loading...';
-        }
-        return preview.innerHTML;
-      },
-      renderingConfig: {
-        singleLineBreaks: false
-      },
-      indentWithTabs: false,
-      tabSize: 4,
-      spellChecker: false,
-      toolbar: ['bold', 'italic', 'strikethrough', '|',
-        'heading-1', 'heading-2', 'heading-3', 'heading-bigger', 'heading-smaller', '|',
-        {
-          name: 'code-inline',
-          action(e) {
-            const cm = e.codemirror;
-            const selection = cm.getSelection();
-            cm.replaceSelection(`\`${selection}\``);
-            if (!selection) {
-              const cursorPos = cm.getCursor();
-              cm.setCursor(cursorPos.line, cursorPos.ch - 1);
-            }
-            cm.focus();
-          },
-          className: 'fa fa-angle-right',
-          title: 'Add Inline Code',
-        }, 'code', 'quote', '|', {
-          name: 'checkbox-empty',
-          action(e) {
-            const cm = e.codemirror;
-            cm.replaceSelection(`\n- [ ] ${cm.getSelection()}`);
-            cm.focus();
-          },
-          className: 'fa fa-square-o',
-          title: 'Add Checkbox (empty)',
-        },
-        {
-          name: 'checkbox-checked',
-          action(e) {
-            const cm = e.codemirror;
-            cm.replaceSelection(`\n- [x] ${cm.getSelection()}`);
-            cm.focus();
-          },
-          className: 'fa fa-check-square-o',
-          title: 'Add Checkbox (checked)',
-        }, '|',
-        'unordered-list', 'ordered-list', '|',
-        'link', 'image', 'table', 'horizontal-rule', '|',
-        'clean-block', 'preview', 'fullscreen', 'side-by-side', '|',
-        {
-          name: 'revert-to-textarea',
-          action(e) {
-            e.toTextArea();
-            hasSimpleMDE = false;
-            const $form = $('.repository.wiki.new .ui.form');
-            const $root = $form.find('.field.content');
-            const loading = $root.data('loading');
-            $root.append(`<div class="ui bottom tab markup" data-tab="preview">${loading}</div>`);
-            initCommentPreviewTab($form);
-          },
-          className: 'fa fa-file',
-          title: 'Revert to simple textarea',
-        },
-      ]
-    });
-    $(simplemde.codemirror.getInputField()).addClass('js-quick-submit');
-
-    setTimeout(() => {
-      const $bEdit = $('.repository.wiki.new .previewtabs a[data-tab="write"]');
-      const $bPrev = $('.repository.wiki.new .previewtabs a[data-tab="preview"]');
-      const $toolbar = $('.editor-toolbar');
-      const $bPreview = $('.editor-toolbar button.preview');
-      const $bSideBySide = $('.editor-toolbar a.fa-columns');
-      $bEdit.on('click', (e) => {
-        if (!hasSimpleMDE) {
-          return false;
-        }
-        e.stopImmediatePropagation();
-        if ($toolbar.hasClass('disabled-for-preview')) {
-          $bPreview.trigger('click');
-        }
-
-        return false;
-      });
-      $bPrev.on('click', (e) => {
-        if (!hasSimpleMDE) {
-          return false;
-        }
-        e.stopImmediatePropagation();
-        if (!$toolbar.hasClass('disabled-for-preview')) {
-          $bPreview.trigger('click');
-        }
-        return false;
-      });
-      $bPreview.on('click', () => {
-        setTimeout(() => {
-          if ($toolbar.hasClass('disabled-for-preview')) {
-            if ($bEdit.hasClass('active')) {
-              $bEdit.removeClass('active');
-            }
-            if (!$bPrev.hasClass('active')) {
-              $bPrev.addClass('active');
-            }
-          } else {
-            if (!$bEdit.hasClass('active')) {
-              $bEdit.addClass('active');
-            }
-            if ($bPrev.hasClass('active')) {
-              $bPrev.removeClass('active');
-            }
-          }
-        }, 0);
-
-        return false;
-      });
-      $bSideBySide.on('click', () => {
-        sideBySideChanges = 10;
-      });
-    }, 0);
-  }
-}
-
-// Adding function to get the cursor position in a text field to jQuery object.
-$.fn.getCursorPosition = function () {
-  const el = $(this).get(0);
-  let pos = 0;
-  if ('selectionStart' in el) {
-    pos = el.selectionStart;
-  } else if ('selection' in document) {
-    el.focus();
-    const Sel = document.selection.createRange();
-    const SelLength = document.selection.createRange().text.length;
-    Sel.moveStart('character', -el.value.length);
-    pos = Sel.text.length - SelLength;
-  }
-  return pos;
-};
-
-function setCommentSimpleMDE($editArea) {
-  if ($editArea.length === 0) {
-    return null;
-  }
-
-  const simplemde = new SimpleMDE({
-    autoDownloadFontAwesome: false,
-    element: $editArea[0],
-    forceSync: true,
-    renderingConfig: {
-      singleLineBreaks: false
-    },
-    indentWithTabs: false,
-    tabSize: 4,
-    spellChecker: false,
-    toolbar: ['bold', 'italic', 'strikethrough', '|',
-      'heading-1', 'heading-2', 'heading-3', 'heading-bigger', 'heading-smaller', '|',
-      'code', 'quote', '|', {
-        name: 'checkbox-empty',
-        action(e) {
-          const cm = e.codemirror;
-          cm.replaceSelection(`\n- [ ] ${cm.getSelection()}`);
-          cm.focus();
-        },
-        className: 'fa fa-square-o',
-        title: 'Add Checkbox (empty)',
-      },
-      {
-        name: 'checkbox-checked',
-        action(e) {
-          const cm = e.codemirror;
-          cm.replaceSelection(`\n- [x] ${cm.getSelection()}`);
-          cm.focus();
-        },
-        className: 'fa fa-check-square-o',
-        title: 'Add Checkbox (checked)',
-      }, '|',
-      'unordered-list', 'ordered-list', '|',
-      'link', 'image', 'table', 'horizontal-rule', '|',
-      'clean-block', '|',
-      {
-        name: 'revert-to-textarea',
-        action(e) {
-          e.toTextArea();
-        },
-        className: 'fa fa-file',
-        title: 'Revert to simple textarea',
-      },
-    ]
-  });
-  $(simplemde.codemirror.getInputField()).addClass('js-quick-submit');
-  simplemde.codemirror.setOption('extraKeys', {
-    Enter: () => {
-      const tributeContainer = document.querySelector('.tribute-container');
-      if (!tributeContainer || tributeContainer.style.display === 'none') {
-        return CodeMirror.Pass;
-      }
-    },
-    Backspace: (cm) => {
-      if (cm.getInputField().trigger) {
-        cm.getInputField().trigger('input');
-      }
-      cm.execCommand('delCharBefore');
-    }
-  });
-  attachTribute(simplemde.codemirror.getInputField(), {mentions: true, emoji: true});
-  $editArea.data('simplemde', simplemde);
-  $(simplemde.codemirror.getInputField()).data('simplemde', simplemde);
-  return simplemde;
-}
-
-async function initEditor() {
-  $('.js-quick-pull-choice-option').on('change', function () {
-    if ($(this).val() === 'commit-to-new-branch') {
-      $('.quick-pull-branch-name').show();
-      $('.quick-pull-branch-name input').prop('required', true);
-    } else {
-      $('.quick-pull-branch-name').hide();
-      $('.quick-pull-branch-name input').prop('required', false);
-    }
-    $('#commit-button').text($(this).attr('button_text'));
-  });
-
-  const $editFilename = $('#file-name');
-  $editFilename.on('keyup', function (e) {
-    const $section = $('.breadcrumb span.section');
-    const $divider = $('.breadcrumb div.divider');
-    let value;
-    let parts;
-
-    if (e.keyCode === 8 && $(this).getCursorPosition() === 0 && $section.length > 0) {
-      value = $section.last().find('a').text();
-      $(this).val(value + $(this).val());
-      $(this)[0].setSelectionRange(value.length, value.length);
-      $section.last().remove();
-      $divider.last().remove();
-    }
-    if (e.keyCode === 191) {
-      parts = $(this).val().split('/');
-      for (let i = 0; i < parts.length; ++i) {
-        value = parts[i];
-        if (i < parts.length - 1) {
-          if (value.length) {
-            $(`<span class="section"><a href="#">${value}</a></span>`).insertBefore($(this));
-            $('<div class="divider"> / </div>').insertBefore($(this));
-          }
-        } else {
-          $(this).val(value);
-        }
-        $(this)[0].setSelectionRange(0, 0);
-      }
-    }
-    parts = [];
-    $('.breadcrumb span.section').each(function () {
-      const element = $(this);
-      if (element.find('a').length) {
-        parts.push(element.find('a').text());
-      } else {
-        parts.push(element.text());
-      }
-    });
-    if ($(this).val()) parts.push($(this).val());
-    $('#tree_path').val(parts.join('/'));
-  }).trigger('keyup');
-
-  const $editArea = $('.repository.editor textarea#edit_area');
-  if (!$editArea.length) return;
 
-  const editor = await createCodeEditor($editArea[0], $editFilename[0], previewFileModes);
-
-  // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
-  // to enable or disable the commit button
-  const $commitButton = $('#commit-button');
-  const $editForm = $('.ui.edit.form');
-  const dirtyFileClass = 'dirty-file';
-
-  // Disabling the button at the start
-  if ($('input[name="page_has_posted"]').val() !== 'true') {
-    $commitButton.prop('disabled', true);
-  }
-
-  // Registering a custom listener for the file path and the file content
-  $editForm.areYouSure({
-    silent: true,
-    dirtyClass: dirtyFileClass,
-    fieldSelector: ':input:not(.commit-form-wrapper :input)',
-    change() {
-      const dirty = $(this).hasClass(dirtyFileClass);
-      $commitButton.prop('disabled', !dirty);
-    }
-  });
-
-  // Update the editor from query params, if available,
-  // only after the dirtyFileClass initialization
-  const params = new URLSearchParams(window.location.search);
-  const value = params.get('value');
-  if (value) {
-    editor.setValue(value);
-  }
-
-  $commitButton.on('click', (event) => {
-    // A modal which asks if an empty file should be committed
-    if ($editArea.val().length === 0) {
-      $('#edit-empty-content-modal').modal({
-        onApprove() {
-          $('.edit.form').trigger('submit');
-        }
-      }).modal('show');
-      event.preventDefault();
-    }
-  });
-}
-
-function initReleaseEditor() {
-  const $editor = $('.repository.new.release .content-editor');
-  if ($editor.length === 0) {
-    return false;
-  }
-
-  const $textarea = $editor.find('textarea');
-  attachTribute($textarea.get(), {mentions: false, emoji: true});
-  const $files = $editor.parent().find('.files');
-  const $simplemde = setCommentSimpleMDE($textarea);
-  initCommentPreviewTab($editor);
-  const dropzone = $editor.parent().find('.dropzone')[0];
-  initSimpleMDEImagePaste($simplemde, dropzone, $files);
-}
-
-function initOrganization() {
-  if ($('.organization').length === 0) {
-    return;
-  }
-
-  // Options
-  if ($('.organization.settings.options').length > 0) {
-    $('#org_name').on('keyup', function () {
-      const $prompt = $('#org-name-change-prompt');
-      const $prompt_redirect = $('#org-name-change-redirect-prompt');
-      if ($(this).val().toString().toLowerCase() !== $(this).data('org-name').toString().toLowerCase()) {
-        $prompt.show();
-        $prompt_redirect.show();
-      } else {
-        $prompt.hide();
-        $prompt_redirect.hide();
-      }
-    });
-  }
-
-  // Labels
-  if ($('.organization.settings.labels').length > 0) {
-    initLabelEdit();
-  }
-}
-
-function initUserSettings() {
-  // Options
-  if ($('.user.settings.profile').length > 0) {
-    $('#username').on('keyup', function () {
-      const $prompt = $('#name-change-prompt');
-      const $prompt_redirect = $('#name-change-redirect-prompt');
-      if ($(this).val().toString().toLowerCase() !== $(this).data('name').toString().toLowerCase()) {
-        $prompt.show();
-        $prompt_redirect.show();
-      } else {
-        $prompt.hide();
-        $prompt_redirect.hide();
-      }
-    });
-  }
-}
-
-async function initGithook() {
-  if ($('.edit.githook').length === 0) return;
-  const filename = document.querySelector('.hook-filename').textContent;
-  await createMonaco($('#content')[0], filename, {language: 'shell'});
-}
-
-function initWebhook() {
-  if ($('.new.webhook').length === 0) {
-    return;
-  }
-
-  $('.events.checkbox input').on('change', function () {
-    if ($(this).is(':checked')) {
-      $('.events.fields').show();
-    }
-  });
-  $('.non-events.checkbox input').on('change', function () {
-    if ($(this).is(':checked')) {
-      $('.events.fields').hide();
-    }
-  });
-
-  const updateContentType = function () {
-    const visible = $('#http_method').val() === 'POST';
-    $('#content_type').parent().parent()[visible ? 'show' : 'hide']();
-  };
-  updateContentType();
-  $('#http_method').on('change', () => {
-    updateContentType();
-  });
-
-  // Test delivery
-  $('#test-delivery').on('click', function () {
-    const $this = $(this);
-    $this.addClass('loading disabled');
-    $.post($this.data('link'), {
-      _csrf: csrf
-    }).done(
-      setTimeout(() => {
-        window.location.href = $this.data('redirect');
-      }, 5000)
-    );
-  });
-}
-
-function initAdmin() {
-  if ($('.admin').length === 0) {
-    return;
-  }
-
-  // New user
-  if ($('.admin.new.user').length > 0 || $('.admin.edit.user').length > 0) {
-    $('#login_type').on('change', function () {
-      if ($(this).val().substring(0, 1) === '0') {
-        $('#user_name').removeAttr('disabled');
-        $('#login_name').removeAttr('required');
-        $('.non-local').hide();
-        $('.local').show();
-        $('#user_name').focus();
-
-        if ($(this).data('password') === 'required') {
-          $('#password').attr('required', 'required');
-        }
-      } else {
-        if ($('.admin.edit.user').length > 0) {
-          $('#user_name').attr('disabled', 'disabled');
-        }
-        $('#login_name').attr('required', 'required');
-        $('.non-local').show();
-        $('.local').hide();
-        $('#login_name').focus();
-
-        $('#password').removeAttr('required');
-      }
-    });
-  }
-
-  function onSecurityProtocolChange() {
-    if ($('#security_protocol').val() > 0) {
-      $('.has-tls').show();
-    } else {
-      $('.has-tls').hide();
-    }
-  }
-
-  function onUsePagedSearchChange() {
-    if ($('#use_paged_search').prop('checked')) {
-      $('.search-page-size').show()
-        .find('input').attr('required', 'required');
-    } else {
-      $('.search-page-size').hide()
-        .find('input').removeAttr('required');
-    }
-  }
-
-  function onOAuth2Change(applyDefaultValues) {
-    $('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url').hide();
-    $('.open_id_connect_auto_discovery_url input[required]').removeAttr('required');
-
-    const provider = $('#oauth2_provider').val();
-    switch (provider) {
-      case 'openidConnect':
-        $('.open_id_connect_auto_discovery_url input').attr('required', 'required');
-        $('.open_id_connect_auto_discovery_url').show();
-        break;
-      default:
-        if ($(`#${provider}_customURLSettings`).data('required')) {
-          $('#oauth2_use_custom_url').attr('checked', 'checked');
-        }
-        if ($(`#${provider}_customURLSettings`).data('available')) {
-          $('.oauth2_use_custom_url').show();
-        }
-    }
-    onOAuth2UseCustomURLChange(applyDefaultValues);
-  }
-
-  function onOAuth2UseCustomURLChange(applyDefaultValues) {
-    const provider = $('#oauth2_provider').val();
-    $('.oauth2_use_custom_url_field').hide();
-    $('.oauth2_use_custom_url_field input[required]').removeAttr('required');
-
-    if ($('#oauth2_use_custom_url').is(':checked')) {
-      for (const custom of ['token_url', 'auth_url', 'profile_url', 'email_url', 'tenant']) {
-        if (applyDefaultValues) {
-          $(`#oauth2_${custom}`).val($(`#${provider}_${custom}`).val());
-        }
-        if ($(`#${provider}_${custom}`).data('available')) {
-          $(`.oauth2_${custom} input`).attr('required', 'required');
-          $(`.oauth2_${custom}`).show();
-        }
-      }
-    }
-  }
-
-  function onVerifyGroupMembershipChange() {
-    if ($('#groups_enabled').is(':checked')) {
-      $('#groups_enabled_change').show();
-    } else {
-      $('#groups_enabled_change').hide();
-    }
-  }
-
-  // New authentication
-  if ($('.admin.new.authentication').length > 0) {
-    $('#auth_type').on('change', function () {
-      $('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size, .sspi').hide();
-
-      $('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required], .sspi input[required]').removeAttr('required');
-      $('.binddnrequired').removeClass('required');
-
-      const authType = $(this).val();
-      switch (authType) {
-        case '2': // LDAP
-          $('.ldap').show();
-          $('.binddnrequired input, .ldap div.required:not(.dldap) input').attr('required', 'required');
-          $('.binddnrequired').addClass('required');
-          break;
-        case '3': // SMTP
-          $('.smtp').show();
-          $('.has-tls').show();
-          $('.smtp div.required input, .has-tls').attr('required', 'required');
-          break;
-        case '4': // PAM
-          $('.pam').show();
-          $('.pam input').attr('required', 'required');
-          break;
-        case '5': // LDAP
-          $('.dldap').show();
-          $('.dldap div.required:not(.ldap) input').attr('required', 'required');
-          break;
-        case '6': // OAuth2
-          $('.oauth2').show();
-          $('.oauth2 div.required:not(.oauth2_use_custom_url,.oauth2_use_custom_url_field,.open_id_connect_auto_discovery_url) input').attr('required', 'required');
-          onOAuth2Change(true);
-          break;
-        case '7': // SSPI
-          $('.sspi').show();
-          $('.sspi div.required input').attr('required', 'required');
-          break;
-      }
-      if (authType === '2' || authType === '5') {
-        onSecurityProtocolChange();
-        onVerifyGroupMembershipChange();
-      }
-      if (authType === '2') {
-        onUsePagedSearchChange();
-      }
-    });
-    $('#auth_type').trigger('change');
-    $('#security_protocol').on('change', onSecurityProtocolChange);
-    $('#use_paged_search').on('change', onUsePagedSearchChange);
-    $('#oauth2_provider').on('change', () => onOAuth2Change(true));
-    $('#oauth2_use_custom_url').on('change', () => onOAuth2UseCustomURLChange(true));
-    $('#groups_enabled').on('change', onVerifyGroupMembershipChange);
-  }
-  // Edit authentication
-  if ($('.admin.edit.authentication').length > 0) {
-    const authType = $('#auth_type').val();
-    if (authType === '2' || authType === '5') {
-      $('#security_protocol').on('change', onSecurityProtocolChange);
-      $('#groups_enabled').on('change', onVerifyGroupMembershipChange);
-      onVerifyGroupMembershipChange();
-      if (authType === '2') {
-        $('#use_paged_search').on('change', onUsePagedSearchChange);
-      }
-    } else if (authType === '6') {
-      $('#oauth2_provider').on('change', () => onOAuth2Change(true));
-      $('#oauth2_use_custom_url').on('change', () => onOAuth2UseCustomURLChange(false));
-      onOAuth2Change(false);
-    }
-  }
-
-  // Notice
-  if ($('.admin.notice')) {
-    const $detailModal = $('#detail-modal');
-
-    // Attach view detail modals
-    $('.view-detail').on('click', function () {
-      $detailModal.find('.content pre').text($(this).parents('tr').find('.notice-description').text());
-      $detailModal.find('.sub.header').text($(this).parents('tr').find('.notice-created-time').text());
-      $detailModal.modal('show');
-      return false;
-    });
-
-    // Select actions
-    const $checkboxes = $('.select.table .ui.checkbox');
-    $('.select.action').on('click', function () {
-      switch ($(this).data('action')) {
-        case 'select-all':
-          $checkboxes.checkbox('check');
-          break;
-        case 'deselect-all':
-          $checkboxes.checkbox('uncheck');
-          break;
-        case 'inverse':
-          $checkboxes.checkbox('toggle');
-          break;
-      }
-    });
-    $('#delete-selection').on('click', function () {
-      const $this = $(this);
-      $this.addClass('loading disabled');
-      const ids = [];
-      $checkboxes.each(function () {
-        if ($(this).checkbox('is checked')) {
-          ids.push($(this).data('id'));
-        }
-      });
-      $.post($this.data('link'), {
-        _csrf: csrf,
-        ids
-      }).done(() => {
-        window.location.href = $this.data('redirect');
-      });
-    });
-  }
-}
-
-function buttonsClickOnEnter() {
-  $('.ui.button').on('keypress', function (e) {
-    if (e.keyCode === 13 || e.keyCode === 32) { // enter key or space bar
-      $(this).trigger('click');
-    }
-  });
-}
-
-function searchUsers() {
-  const $searchUserBox = $('#search-user-box');
-  $searchUserBox.search({
-    minCharacters: 2,
-    apiSettings: {
-      url: `${AppSubUrl}/api/v1/users/search?q={query}`,
-      onResponse(response) {
-        const items = [];
-        const searchQueryUppercase = $searchUserBox.find('input').val().toUpperCase();
-        $.each(response.data, (_i, item) => {
-          let title = item.login;
-          if (item.full_name && item.full_name.length > 0) {
-            title += ` (${htmlEscape(item.full_name)})`;
-          }
-          const resultItem = {
-            title,
-            image: item.avatar_url
-          };
-          if (searchQueryUppercase === item.login.toUpperCase()) {
-            items.unshift(resultItem);
-          } else {
-            items.push(resultItem);
-          }
-        });
-
-        return {results: items};
-      }
-    },
-    searchFields: ['login', 'full_name'],
-    showNoResults: false
-  });
-}
-
-function searchTeams() {
-  const $searchTeamBox = $('#search-team-box');
-  $searchTeamBox.search({
-    minCharacters: 2,
-    apiSettings: {
-      url: `${AppSubUrl}/api/v1/orgs/${$searchTeamBox.data('org')}/teams/search?q={query}`,
-      headers: {'X-Csrf-Token': csrf},
-      onResponse(response) {
-        const items = [];
-        $.each(response.data, (_i, item) => {
-          const title = `${item.name} (${item.permission} access)`;
-          items.push({
-            title,
-          });
-        });
-
-        return {results: items};
-      }
-    },
-    searchFields: ['name', 'description'],
-    showNoResults: false
-  });
-}
-
-function searchRepositories() {
-  const $searchRepoBox = $('#search-repo-box');
-  $searchRepoBox.search({
-    minCharacters: 2,
-    apiSettings: {
-      url: `${AppSubUrl}/api/v1/repos/search?q={query}&uid=${$searchRepoBox.data('uid')}`,
-      onResponse(response) {
-        const items = [];
-        $.each(response.data, (_i, item) => {
-          items.push({
-            title: item.full_name.split('/')[1],
-            description: item.full_name
-          });
-        });
-
-        return {results: items};
-      }
-    },
-    searchFields: ['full_name'],
-    showNoResults: false
-  });
-}
-
-function initCodeView() {
-  if ($('.code-view .lines-num').length > 0) {
-    $(document).on('click', '.lines-num span', function (e) {
-      const $select = $(this);
-      let $list;
-      if ($('div.blame').length) {
-        $list = $('.code-view td.lines-code.blame-code');
-      } else {
-        $list = $('.code-view td.lines-code');
-      }
-      selectRange($list, $list.filter(`[rel=${$select.attr('id')}]`), (e.shiftKey ? $list.filter('.active').eq(0) : null));
-      deSelect();
-
-      // show code view menu marker (don't show in blame page)
-      if ($('div.blame').length === 0) {
-        showLineButton();
-      }
-    });
-
-    $(window).on('hashchange', () => {
-      let m = window.location.hash.match(/^#(L\d+)-(L\d+)$/);
-      let $list;
-      if ($('div.blame').length) {
-        $list = $('.code-view td.lines-code.blame-code');
-      } else {
-        $list = $('.code-view td.lines-code');
-      }
-      let $first;
-      if (m) {
-        $first = $list.filter(`[rel=${m[1]}]`);
-        selectRange($list, $first, $list.filter(`[rel=${m[2]}]`));
-
-        // show code view menu marker (don't show in blame page)
-        if ($('div.blame').length === 0) {
-          showLineButton();
-        }
-
-        $('html, body').scrollTop($first.offset().top - 200);
-        return;
-      }
-      m = window.location.hash.match(/^#(L|n)(\d+)$/);
-      if (m) {
-        $first = $list.filter(`[rel=L${m[2]}]`);
-        selectRange($list, $first);
-
-        // show code view menu marker (don't show in blame page)
-        if ($('div.blame').length === 0) {
-          showLineButton();
-        }
-
-        $('html, body').scrollTop($first.offset().top - 200);
-      }
-    }).trigger('hashchange');
-  }
-  $(document).on('click', '.fold-file', ({currentTarget}) => {
-    const box = currentTarget.closest('.file-content');
-    const chevron = currentTarget.querySelector('a.chevron');
-    const folded = box.dataset.folded !== 'true';
-    chevron.innerHTML = svg(`octicon-chevron-${folded ? 'right' : 'down'}`, 18);
-    box.dataset.folded = String(folded);
-  });
-  $(document).on('click', '.blob-excerpt', async ({currentTarget}) => {
-    const {url, query, anchor} = currentTarget.dataset;
-    if (!url) return;
-    const blob = await $.get(`${url}?${query}&anchor=${anchor}`);
-    currentTarget.closest('tr').outerHTML = blob;
-  });
-}
-
-function initU2FAuth() {
-  if ($('#wait-for-key').length === 0) {
-    return;
-  }
-  u2fApi.ensureSupport()
-    .then(() => {
-      $.getJSON(`${AppSubUrl}/user/u2f/challenge`).done((req) => {
-        u2fApi.sign(req.appId, req.challenge, req.registeredKeys, 30)
-          .then(u2fSigned)
-          .catch((err) => {
-            if (err === undefined) {
-              u2fError(1);
-              return;
-            }
-            u2fError(err.metaData.code);
-          });
-      });
-    }).catch(() => {
-      // Fallback in case browser do not support U2F
-      window.location.href = `${AppSubUrl}/user/two_factor`;
-    });
-}
-function u2fSigned(resp) {
-  $.ajax({
-    url: `${AppSubUrl}/user/u2f/sign`,
-    type: 'POST',
-    headers: {'X-Csrf-Token': csrf},
-    data: JSON.stringify(resp),
-    contentType: 'application/json; charset=utf-8',
-  }).done((res) => {
-    window.location.replace(res);
-  }).fail(() => {
-    u2fError(1);
-  });
-}
-
-function u2fRegistered(resp) {
-  if (checkError(resp)) {
-    return;
-  }
-  $.ajax({
-    url: `${AppSubUrl}/user/settings/security/u2f/register`,
-    type: 'POST',
-    headers: {'X-Csrf-Token': csrf},
-    data: JSON.stringify(resp),
-    contentType: 'application/json; charset=utf-8',
-    success() {
-      reload();
-    },
-    fail() {
-      u2fError(1);
-    }
-  });
-}
-
-function checkError(resp) {
-  if (!('errorCode' in resp)) {
-    return false;
-  }
-  if (resp.errorCode === 0) {
-    return false;
-  }
-  u2fError(resp.errorCode);
-  return true;
-}
-
-function u2fError(errorType) {
-  const u2fErrors = {
-    browser: $('#unsupported-browser'),
-    1: $('#u2f-error-1'),
-    2: $('#u2f-error-2'),
-    3: $('#u2f-error-3'),
-    4: $('#u2f-error-4'),
-    5: $('.u2f_error_5')
-  };
-  u2fErrors[errorType].removeClass('hide');
-
-  Object.keys(u2fErrors).forEach((type) => {
-    if (type !== `${errorType}`) {
-      u2fErrors[type].addClass('hide');
-    }
-  });
-  $('#u2f-error').modal('show');
-}
-
-function initU2FRegister() {
-  $('#register-device').modal({allowMultiple: false});
-  $('#u2f-error').modal({allowMultiple: false});
-  $('#register-security-key').on('click', (e) => {
-    e.preventDefault();
-    u2fApi.ensureSupport()
-      .then(u2fRegisterRequest)
-      .catch(() => {
-        u2fError('browser');
-      });
-  });
-}
-
-function u2fRegisterRequest() {
-  $.post(`${AppSubUrl}/user/settings/security/u2f/request_register`, {
-    _csrf: csrf,
-    name: $('#nickname').val()
-  }).done((req) => {
-    $('#nickname').closest('div.field').removeClass('error');
-    $('#register-device').modal('show');
-    if (req.registeredKeys === null) {
-      req.registeredKeys = [];
-    }
-    u2fApi.register(req.appId, req.registerRequests, req.registeredKeys, 30)
-      .then(u2fRegistered)
-      .catch((reason) => {
-        if (reason === undefined) {
-          u2fError(1);
-          return;
-        }
-        u2fError(reason.metaData.code);
-      });
-  }).fail((xhr) => {
-    if (xhr.status === 409) {
-      $('#nickname').closest('div.field').addClass('error');
-    }
-  });
-}
-
-function initWipTitle() {
-  $('.title_wip_desc > a').on('click', (e) => {
-    e.preventDefault();
-
-    const $issueTitle = $('#issue_title');
-    $issueTitle.focus();
-    const value = $issueTitle.val().trim().toUpperCase();
-
-    const wipPrefixes = $('.title_wip_desc').data('wip-prefixes');
-    for (const prefix of wipPrefixes) {
-      if (value.startsWith(prefix.toUpperCase())) {
-        return;
-      }
-    }
-
-    $issueTitle.val(`${wipPrefixes[0]} ${$issueTitle.val()}`);
-  });
-}
-
-function initTemplateSearch() {
-  const $repoTemplate = $('#repo_template');
-  const checkTemplate = function () {
-    const $templateUnits = $('#template_units');
-    const $nonTemplate = $('#non_template');
-    if ($repoTemplate.val() !== '' && $repoTemplate.val() !== '0') {
-      $templateUnits.show();
-      $nonTemplate.hide();
-    } else {
-      $templateUnits.hide();
-      $nonTemplate.show();
-    }
-  };
-  $repoTemplate.on('change', checkTemplate);
-  checkTemplate();
-
-  const changeOwner = function () {
-    $('#repo_template_search')
-      .dropdown({
-        apiSettings: {
-          url: `${AppSubUrl}/api/v1/repos/search?q={query}&template=true&priority_owner_id=${$('#uid').val()}`,
-          onResponse(response) {
-            const filteredResponse = {success: true, results: []};
-            filteredResponse.results.push({
-              name: '',
-              value: ''
-            });
-            // Parse the response from the api to work with our dropdown
-            $.each(response.data, (_r, repo) => {
-              filteredResponse.results.push({
-                name: htmlEscape(repo.full_name),
-                value: repo.id
-              });
-            });
-            return filteredResponse;
-          },
-          cache: false,
-        },
-
-        fullTextSearch: true
-      });
-  };
-  $('#uid').on('change', changeOwner);
-  changeOwner();
-}
-
-function initIssueReferenceRepositorySearch() {
-  $('.issue_reference_repository_search')
-    .dropdown({
-      apiSettings: {
-        url: `${AppSubUrl}/api/v1/repos/search?q={query}&limit=20`,
-        onResponse(response) {
-          const filteredResponse = {success: true, results: []};
-          $.each(response.data, (_r, repo) => {
-            filteredResponse.results.push({
-              name: htmlEscape(repo.full_name),
-              value: repo.full_name
-            });
-          });
-          return filteredResponse;
-        },
-        cache: false,
-      },
-      onChange(_value, _text, $choice) {
-        const $form = $choice.closest('form');
-        $form.attr('action', `${AppSubUrl}/${_text}/issues/new`);
-      },
-      fullTextSearch: true
-    });
-}
-
-function initFileViewToggle() {
-  $('.file-view-toggle').on('click', function() {
-    const $this = $(this);
-    $this.parent().children().removeClass('active');
-    $this.addClass('active');
-
-    const $target = $($this.data('toggle-selector'));
-    $target.parent().children().addClass('hide');
-    $target.removeClass('hide');
-  });
-}
-
-function initLinkAccountView() {
-  const $lnkUserPage = $('.page-content.user.link-account');
-  if ($lnkUserPage.length === 0) {
-    return false;
-  }
-
-  const $signinTab = $lnkUserPage.find('.item[data-tab="auth-link-signin-tab"]');
-  const $signUpTab = $lnkUserPage.find('.item[data-tab="auth-link-signup-tab"]');
-  const $signInView = $lnkUserPage.find('.tab[data-tab="auth-link-signin-tab"]');
-  const $signUpView = $lnkUserPage.find('.tab[data-tab="auth-link-signup-tab"]');
-
-  $signUpTab.on('click', () => {
-    $signinTab.removeClass('active');
-    $signInView.removeClass('active');
-    $signUpTab.addClass('active');
-    $signUpView.addClass('active');
-    return false;
-  });
-
-  $signinTab.on('click', () => {
-    $signUpTab.removeClass('active');
-    $signUpView.removeClass('active');
-    $signinTab.addClass('active');
-    $signInView.addClass('active');
-  });
-}
+initVueEnv();
 
 $(document).ready(async () => {
-  // Show exact time
-  $('.time-since').each(function () {
-    $(this)
-      .addClass('poping up')
-      .attr('data-content', $(this).attr('title'))
-      .attr('data-variation', 'inverted tiny')
-      .attr('title', '');
-  });
-
-  // Undo Safari emoji glitch fix at high enough zoom levels
-  if (navigator.userAgent.match('Safari')) {
-    $(window).resize(() => {
-      const px = mqBinarySearch('width', 0, 4096, 1, 'px');
-      const em = mqBinarySearch('width', 0, 1024, 0.01, 'em');
-      if (em * 16 * 1.25 - px <= -1) {
-        $('body').addClass('safari-above125');
-      } else {
-        $('body').removeClass('safari-above125');
-      }
-    });
-  }
-
-  // Semantic UI modules.
-  $('.dropdown:not(.custom)').dropdown({
-    fullTextSearch: 'exact'
-  });
-  $('.jump.dropdown').dropdown({
-    action: 'hide',
-    onShow() {
-      $('.poping.up').popup('hide');
-    },
-    fullTextSearch: 'exact'
-  });
-  $('.slide.up.dropdown').dropdown({
-    transition: 'slide up',
-    fullTextSearch: 'exact'
-  });
-  $('.upward.dropdown').dropdown({
-    direction: 'upward',
-    fullTextSearch: 'exact'
-  });
-  $('.ui.checkbox').checkbox();
-  $('.ui.progress').progress({
-    showActivity: false
-  });
-  $('.poping.up').popup();
-  $('.top.menu .poping.up').popup({
-    onShow() {
-      if ($('.top.menu .menu.transition').hasClass('visible')) {
-        return false;
-      }
-    }
-  });
-  $('.tabular.menu .item').tab();
-  $('.tabable.menu .item').tab();
-
-  $('.toggle.button').on('click', function () {
-    $($(this).data('target')).slideToggle(100);
-  });
-
-  // make table <tr> element clickable like a link
-  $('tr[data-href]').on('click', function () {
-    window.location = $(this).data('href');
-  });
-
-  // make table <td> element clickable like a link
-  $('td[data-href]').click(function () {
-    window.location = $(this).data('href');
-  });
-
-  // link-account tab handle
-  initLinkAccountView();
-
-  // Dropzone
-  for (const el of document.querySelectorAll('.dropzone')) {
-    const $dropzone = $(el);
-    await createDropzone(el, {
-      url: $dropzone.data('upload-url'),
-      headers: {'X-Csrf-Token': csrf},
-      maxFiles: $dropzone.data('max-file'),
-      maxFilesize: $dropzone.data('max-size'),
-      acceptedFiles: (['*/*', ''].includes($dropzone.data('accepts'))) ? null : $dropzone.data('accepts'),
-      addRemoveLinks: true,
-      dictDefaultMessage: $dropzone.data('default-message'),
-      dictInvalidFileType: $dropzone.data('invalid-input-type'),
-      dictFileTooBig: $dropzone.data('file-too-big'),
-      dictRemoveFile: $dropzone.data('remove-file'),
-      timeout: 0,
-      thumbnailMethod: 'contain',
-      thumbnailWidth: 480,
-      thumbnailHeight: 480,
-      init() {
-        this.on('success', (_file, data) => {
-          const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
-          $dropzone.find('.files').append(input);
-        });
-        this.on('removedfile', (file) => {
-          $(`#${file.uuid}`).remove();
-          if ($dropzone.data('remove-url')) {
-            $.post($dropzone.data('remove-url'), {
-              file: file.uuid,
-              _csrf: csrf
-            });
-          }
-        });
-      },
-    });
-  }
-
-  // Helpers.
-  $('.delete-button').on('click', showDeletePopup);
-  $('.add-all-button').on('click', showAddAllPopup);
-  $('.link-action').on('click', linkAction);
-  $('.language-menu a[lang]').on('click', linkLanguageAction);
-  $('.link-email-action').on('click', linkEmailAction);
+  initGlobalCommon();
+  initGlobalDropzone();
+  initGlobalLinkActions();
+  initGlobalButtons();
+  initRepoBranchButton();
 
-  $('.delete-branch-button').on('click', showDeletePopup);
+  initCommonIssue();
 
-  $('.undo-button').on('click', function () {
-    const $this = $(this);
-    $.post($this.data('url'), {
-      _csrf: csrf,
-      id: $this.data('id')
-    }).done((data) => {
-      window.location.href = data.redirect;
-    });
-  });
-  $('.show-panel.button').on('click', function () {
-    $($(this).data('panel')).show();
-  });
-  $('.hide-panel.button').on('click', function () {
-    $($(this).data('panel')).hide();
-  });
-  $('.show-create-branch-modal.button').on('click', function () {
-    $('#create-branch-form')[0].action = $('#create-branch-form').data('base-action') + $(this).data('branch-from');
-    $('#modal-create-branch-from-span').text($(this).data('branch-from'));
-    $($(this).data('modal')).modal('show');
-  });
-  $('.show-modal.button').on('click', function () {
-    $($(this).data('modal')).modal('show');
-    const colorPickers = $($(this).data('modal')).find('.color-picker');
-    if (colorPickers.length > 0) {
-      initColorPicker();
-    }
-  });
-  $('.delete-post.button').on('click', function () {
-    const $this = $(this);
-    $.post($this.data('request-url'), {
-      _csrf: csrf
-    }).done(() => {
-      window.location.href = $this.data('done-url');
-    });
-  });
-
-  $('.issue-checkbox').on('click', () => {
-    const numChecked = $('.issue-checkbox').children('input:checked').length;
-    if (numChecked > 0) {
-      $('#issue-filters').addClass('hide');
-      $('#issue-actions').removeClass('hide');
-    } else {
-      $('#issue-filters').removeClass('hide');
-      $('#issue-actions').addClass('hide');
-    }
-  });
-
-  $('.issue-action').on('click', function () {
-    let {action, elementId, url} = this.dataset;
-    const issueIDs = $('.issue-checkbox').children('input:checked').map((_, el) => {
-      return el.dataset.issueId;
-    }).get().join(',');
-    if (elementId === '0' && url.substr(-9) === '/assignee') {
-      elementId = '';
-      action = 'clear';
-    }
-    updateIssuesMeta(url, action, issueIDs, elementId, '').then(() => {
-      // NOTICE: This reset of checkbox state targets Firefox caching behaviour, as the checkboxes stay checked after reload
-      if (action === 'close' || action === 'open') {
-        // uncheck all checkboxes
-        $('.issue-checkbox input[type="checkbox"]').each((_, e) => { e.checked = false });
-      }
-      reload();
-    });
-  });
-
-  // NOTICE: This event trigger targets Firefox caching behaviour, as the checkboxes stay checked after reload
-  // trigger ckecked event, if checkboxes are checked on load
-  $('.issue-checkbox input[type="checkbox"]:checked').first().each((_, e) => {
-    e.checked = false;
-    $(e).trigger('click');
-  });
-
-  $(document).on('click', '.resolve-conversation', async function (e) {
-    e.preventDefault();
-    const comment_id = $(this).data('comment-id');
-    const origin = $(this).data('origin');
-    const action = $(this).data('action');
-    const url = $(this).data('update-url');
-
-    const data = await $.post(url, {_csrf: csrf, origin, action, comment_id});
-
-    if ($(this).closest('.conversation-holder').length) {
-      const conversation = $(data);
-      $(this).closest('.conversation-holder').replaceWith(conversation);
-      conversation.find('.dropdown').dropdown();
-      initReactionSelector(conversation);
-      initClipboard();
-    } else {
-      reload();
-    }
-  });
-
-  buttonsClickOnEnter();
-  searchUsers();
-  searchTeams();
-  searchRepositories();
+  initSearchUserBox();
+  initRepoSettingSearchTeamBox();
+  initOrgTeamSearchRepoBox();
 
+  initGlobalButtonClickOnEnter();
   initMarkupAnchors();
   initCommentContent();
-  initCommentForm();
+  initRepoCommentForm();
   initInstall();
-  initArchiveLinks();
+  initRepoArchiveLinks();
   initRepository();
   initMigration();
-  initWikiForm();
-  initEditForm();
-  initEditor();
-  initOrganization();
-  initWebhook();
-  initAdmin();
-  initCodeView();
+  initRepoWikiForm();
+  initRepoEditor();
+  initCommonOrganization();
+  initWebHookEditor();
+  initAdminCommon();
+  initRepoCodeView();
   initRepoActivityTopAuthorsChart();
   initDashboardRepoList();
-  initTeamSettings();
-  initCtrlEnterSubmit();
-  initNavbarContentToggle();
-  initTopicbar();
-  initU2FAuth();
-  initU2FRegister();
-  initIssueList();
-  initIssueTimetracking();
-  initIssueDue();
-  initWipTitle();
-  initPullRequestReview();
-  initRepoStatusChecker();
-  initTemplateSearch();
-  initIssueReferenceRepositorySearch();
+  initOrgTeamSettings();
+  initGlobalEnterQuickSubmit();
+  initHeadNavbarContentToggle();
+  initFootLanguageMenu();
+  initRepoTopicBar();
+  initUserAuthU2fAuth();
+  initUserAuthU2fRegister();
+  initRepoIssueList();
+  initRepoIssueTimeTracking();
+  initRepoIssueDue();
+  initRepoIssueWipTitle();
+  initRepoPullRequestReview();
+  initRepoMigrationStatusChecker();
+  initRepoTemplateSearch();
+  initRepoIssueReferenceRepositorySearch();
   initContextPopups();
   initTableSort();
   initNotificationsTable();
   initLastCommitLoader();
-  initPullRequestMergeInstruction();
-  initFileViewToggle();
-  initReleaseEditor();
-  initRelease();
+  initRepoPullRequestMergeInstruction();
+  initRepoDiffFileViewToggle();
+  initRepoReleaseEditor();
+  initRepoRelease();
   initDiffShowMore();
   initIssueContentHistory();
   initAdminUserListSearchForm();
-
-  const routes = {
-    'div.user.settings': initUserSettings,
-    'div.repository.settings.collaboration': initRepositoryCollaboration
-  };
-
-  for (const [selector, fn] of Object.entries(routes)) {
-    if ($(selector).length > 0) {
-      fn();
-      break;
-    }
-  }
+  initGlobalCopyToClipboardListener();
+  initUserAuthOauth2();
+  initRepoDiffReviewButton();
+  initRepoCommitButton();
+  initAdminEmails();
+  initGlobalEnterQuickSubmit();
+  initSshKeyFormParser();
+  initGlobalFormDirtyLeaveConfirm();
+  initUserSettings();
+  initRepoSettingsCollaboration();
+  initUserAuthLinkAccountView();
+  initRepoDiffConversationForm();
 
   // parallel init of async loaded features
   await Promise.all([
     attachTribute(document.querySelectorAll('#content, .emoji-input')),
     initGitGraph(),
-    initClipboard(),
     initHeatmap(),
     initProject(),
     initServiceWorker(),
     initNotificationCount(),
     initStopwatch(),
     initMarkupContent(),
-    initGithook(),
+    initRepoSettingGitHook(),
     initImageDiff(),
   ]);
 });
-
-function changeHash(hash) {
-  if (window.history.pushState) {
-    window.history.pushState(null, null, hash);
-  } else {
-    window.location.hash = hash;
-  }
-}
-
-function deSelect() {
-  if (window.getSelection) {
-    window.getSelection().removeAllRanges();
-  } else {
-    document.selection.empty();
-  }
-}
-
-function selectRange($list, $select, $from) {
-  $list.removeClass('active');
-
-  // add hashchange to permalink
-  const $issue = $('a.ref-in-new-issue');
-  const $copyPermalink = $('a.copy-line-permalink');
-
-  if ($issue.length === 0 || $copyPermalink.length === 0) {
-    return;
-  }
-
-  const updateIssueHref = function(anchor) {
-    let href = $issue.attr('href');
-    href = `${href.replace(/%23L\d+$|%23L\d+-L\d+$/, '')}%23${anchor}`;
-    $issue.attr('href', href);
-  };
-
-  const updateCopyPermalinkHref = function(anchor) {
-    let link = $copyPermalink.attr('data-clipboard-text');
-    link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`;
-    $copyPermalink.attr('data-clipboard-text', link);
-  };
-
-  if ($from) {
-    let a = parseInt($select.attr('rel').substr(1));
-    let b = parseInt($from.attr('rel').substr(1));
-    let c;
-    if (a !== b) {
-      if (a > b) {
-        c = a;
-        a = b;
-        b = c;
-      }
-      const classes = [];
-      for (let i = a; i <= b; i++) {
-        classes.push(`[rel=L${i}]`);
-      }
-      $list.filter(classes.join(',')).addClass('active');
-      changeHash(`#L${a}-L${b}`);
-
-      updateIssueHref(`L${a}-L${b}`);
-      updateCopyPermalinkHref(`L${a}-L${b}`);
-      return;
-    }
-  }
-  $select.addClass('active');
-  changeHash(`#${$select.attr('rel')}`);
-
-  updateIssueHref($select.attr('rel'));
-  updateCopyPermalinkHref($select.attr('rel'));
-}
-
-$(() => {
-  // Warn users that try to leave a page after entering data into a form.
-  // Except on sign-in pages, and for forms marked as 'ignore-dirty'.
-  if ($('.user.signin').length === 0) {
-    $('form:not(.ignore-dirty)').areYouSure();
-  }
-
-  // Parse SSH Key
-  $('#ssh-key-content').on('change paste keyup', function () {
-    const arrays = $(this).val().split(' ');
-    const $title = $('#ssh-key-title');
-    if ($title.val() === '' && arrays.length === 3 && arrays[2] !== '') {
-      $title.val(arrays[2]);
-    }
-  });
-});
-
-function showDeletePopup() {
-  const $this = $(this);
-  const dataArray = $this.data();
-  let filter = '';
-  if ($this.data('modal-id')) {
-    filter += `#${$this.data('modal-id')}`;
-  }
-
-  const dialog = $(`.delete.modal${filter}`);
-  dialog.find('.name').text($this.data('name'));
-  for (const [key, value] of Object.entries(dataArray)) {
-    if (key && key.startsWith('data')) {
-      dialog.find(`.${key}`).text(value);
-    }
-  }
-
-  dialog.modal({
-    closable: false,
-    onApprove() {
-      if ($this.data('type') === 'form') {
-        $($this.data('form')).trigger('submit');
-        return;
-      }
-
-      const postData = {
-        _csrf: csrf,
-      };
-      for (const [key, value] of Object.entries(dataArray)) {
-        if (key && key.startsWith('data')) {
-          postData[key.substr(4)] = value;
-        }
-        if (key === 'id') {
-          postData['id'] = value;
-        }
-      }
-
-      $.post($this.data('url'), postData).done((data) => {
-        window.location.href = data.redirect;
-      });
-    }
-  }).modal('show');
-  return false;
-}
-
-function showAddAllPopup() {
-  const $this = $(this);
-  let filter = '';
-  if ($this.attr('id')) {
-    filter += `#${$this.attr('id')}`;
-  }
-
-  const dialog = $(`.addall.modal${filter}`);
-  dialog.find('.name').text($this.data('name'));
-
-  dialog.modal({
-    closable: false,
-    onApprove() {
-      if ($this.data('type') === 'form') {
-        $($this.data('form')).trigger('submit');
-        return;
-      }
-
-      $.post($this.data('url'), {
-        _csrf: csrf,
-        id: $this.data('id')
-      }).done((data) => {
-        window.location.href = data.redirect;
-      });
-    }
-  }).modal('show');
-  return false;
-}
-
-function linkAction(e) {
-  e.preventDefault();
-  const $this = $(this);
-  const redirect = $this.data('redirect');
-  $.post($this.data('url'), {
-    _csrf: csrf
-  }).done((data) => {
-    if (data.redirect) {
-      window.location.href = data.redirect;
-    } else if (redirect) {
-      window.location.href = redirect;
-    } else {
-      window.location.reload();
-    }
-  });
-}
-
-function linkLanguageAction() {
-  const $this = $(this);
-  $.post($this.data('url')).always(() => {
-    window.location.reload();
-  });
-}
-
-function linkEmailAction(e) {
-  const $this = $(this);
-  $('#form-uid').val($this.data('uid'));
-  $('#form-email').val($this.data('email'));
-  $('#form-primary').val($this.data('primary'));
-  $('#form-activate').val($this.data('activate'));
-  $('#form-uid').val($this.data('uid'));
-  $('#change-email-modal').modal('show');
-  e.preventDefault();
-}
-
-function initCtrlEnterSubmit() {
-  $('.js-quick-submit').on('keydown', function (e) {
-    if (((e.ctrlKey && !e.altKey) || e.metaKey) && (e.keyCode === 13 || e.keyCode === 10)) {
-      $(this).closest('form').trigger('submit');
-    }
-  });
-}
-
-function initIssueTimetracking() {
-  $(document).on('click', '.issue-add-time', () => {
-    $('.issue-start-time-modal').modal({
-      duration: 200,
-      onApprove() {
-        $('#add_time_manual_form').trigger('submit');
-      }
-    }).modal('show');
-    $('.issue-start-time-modal input').on('keydown', (e) => {
-      if ((e.keyCode || e.key) === 13) {
-        $('#add_time_manual_form').trigger('submit');
-      }
-    });
-  });
-  $(document).on('click', '.issue-start-time, .issue-stop-time', () => {
-    $('#toggle_stopwatch_form').trigger('submit');
-  });
-  $(document).on('click', '.issue-cancel-time', () => {
-    $('#cancel_stopwatch_form').trigger('submit');
-  });
-  $(document).on('click', 'button.issue-delete-time', function () {
-    const sel = `.issue-delete-time-modal[data-id="${$(this).data('id')}"]`;
-    $(sel).modal({
-      duration: 200,
-      onApprove() {
-        $(`${sel} form`).trigger('submit');
-      }
-    }).modal('show');
-  });
-}
-
-function initBranchOrTagDropdown(selector) {
-  $(selector).each(function() {
-    const $dropdown = $(this);
-    $dropdown.find('.reference.column').on('click', function () {
-      $dropdown.find('.scrolling.reference-list-menu').hide();
-      $($(this).data('target')).show();
-      return false;
-    });
-  });
-}
-
-
-$('.commit-button').on('click', function (e) {
-  e.preventDefault();
-  $(this).parent().find('.commit-body').toggle();
-});
-
-function initNavbarContentToggle() {
-  const content = $('#navbar');
-  const toggle = $('#navbar-expand-toggle');
-  let isExpanded = false;
-  toggle.on('click', () => {
-    isExpanded = !isExpanded;
-    if (isExpanded) {
-      content.addClass('shown');
-      toggle.addClass('active');
-    } else {
-      content.removeClass('shown');
-      toggle.removeClass('active');
-    }
-  });
-}
-
-function initTopicbar() {
-  const mgrBtn = $('#manage_topic');
-  const editDiv = $('#topic_edit');
-  const viewDiv = $('#repo-topics');
-  const saveBtn = $('#save_topic');
-  const topicDropdown = $('#topic_edit .dropdown');
-  const topicForm = $('#topic_edit.ui.form');
-  const topicPrompts = getPrompts();
-
-  mgrBtn.on('click', () => {
-    viewDiv.hide();
-    editDiv.css('display', ''); // show Semantic UI Grid
-  });
-
-  function getPrompts() {
-    const hidePrompt = $('div.hide#validate_prompt');
-    const prompts = {
-      countPrompt: hidePrompt.children('#count_prompt').text(),
-      formatPrompt: hidePrompt.children('#format_prompt').text()
-    };
-    hidePrompt.remove();
-    return prompts;
-  }
-
-  saveBtn.on('click', () => {
-    const topics = $('input[name=topics]').val();
-
-    $.post(saveBtn.data('link'), {
-      _csrf: csrf,
-      topics
-    }, (_data, _textStatus, xhr) => {
-      if (xhr.responseJSON.status === 'ok') {
-        viewDiv.children('.topic').remove();
-        if (topics.length) {
-          const topicArray = topics.split(',');
-
-          const last = viewDiv.children('a').last();
-          for (let i = 0; i < topicArray.length; i++) {
-            const link = $('<a class="ui repo-topic large label topic"></a>');
-            link.attr('href', `${AppSubUrl}/explore/repos?q=${encodeURIComponent(topicArray[i])}&topic=1`);
-            link.text(topicArray[i]);
-            link.insertBefore(last);
-          }
-        }
-        editDiv.css('display', 'none');
-        viewDiv.show();
-      }
-    }).fail((xhr) => {
-      if (xhr.status === 422) {
-        if (xhr.responseJSON.invalidTopics.length > 0) {
-          topicPrompts.formatPrompt = xhr.responseJSON.message;
-
-          const {invalidTopics} = xhr.responseJSON;
-          const topicLables = topicDropdown.children('a.ui.label');
-
-          topics.split(',').forEach((value, index) => {
-            for (let i = 0; i < invalidTopics.length; i++) {
-              if (invalidTopics[i] === value) {
-                topicLables.eq(index).removeClass('green').addClass('red');
-              }
-            }
-          });
-        } else {
-          topicPrompts.countPrompt = xhr.responseJSON.message;
-        }
-      }
-    }).always(() => {
-      topicForm.form('validate form');
-    });
-  });
-
-  topicDropdown.dropdown({
-    allowAdditions: true,
-    forceSelection: false,
-    fullTextSearch: 'exact',
-    fields: {name: 'description', value: 'data-value'},
-    saveRemoteData: false,
-    label: {
-      transition: 'horizontal flip',
-      duration: 200,
-      variation: false,
-      blue: true,
-      basic: true,
-    },
-    className: {
-      label: 'ui small label'
-    },
-    apiSettings: {
-      url: `${AppSubUrl}/api/v1/topics/search?q={query}`,
-      throttle: 500,
-      cache: false,
-      onResponse(res) {
-        const formattedResponse = {
-          success: false,
-          results: [],
-        };
-        const query = stripTags(this.urlData.query.trim());
-        let found_query = false;
-        const current_topics = [];
-        topicDropdown.find('div.label.visible.topic,a.label.visible').each((_, e) => { current_topics.push(e.dataset.value) });
-
-        if (res.topics) {
-          let found = false;
-          for (let i = 0; i < res.topics.length; i++) {
-            // skip currently added tags
-            if (current_topics.includes(res.topics[i].topic_name)) {
-              continue;
-            }
-
-            if (res.topics[i].topic_name.toLowerCase() === query.toLowerCase()) {
-              found_query = true;
-            }
-            formattedResponse.results.push({description: res.topics[i].topic_name, 'data-value': res.topics[i].topic_name});
-            found = true;
-          }
-          formattedResponse.success = found;
-        }
-
-        if (query.length > 0 && !found_query) {
-          formattedResponse.success = true;
-          formattedResponse.results.unshift({description: query, 'data-value': query});
-        } else if (query.length > 0 && found_query) {
-          formattedResponse.results.sort((a, b) => {
-            if (a.description.toLowerCase() === query.toLowerCase()) return -1;
-            if (b.description.toLowerCase() === query.toLowerCase()) return 1;
-            if (a.description > b.description) return -1;
-            if (a.description < b.description) return 1;
-            return 0;
-          });
-        }
-
-        return formattedResponse;
-      },
-    },
-    onLabelCreate(value) {
-      value = value.toLowerCase().trim();
-      this.attr('data-value', value).contents().first().replaceWith(value);
-      return $(this);
-    },
-    onAdd(addedValue, _addedText, $addedChoice) {
-      addedValue = addedValue.toLowerCase().trim();
-      $($addedChoice).attr('data-value', addedValue);
-      $($addedChoice).attr('data-text', addedValue);
-    }
-  });
-
-  $.fn.form.settings.rules.validateTopic = function (_values, regExp) {
-    const topics = topicDropdown.children('a.ui.label');
-    const status = topics.length === 0 || topics.last().attr('data-value').match(regExp);
-    if (!status) {
-      topics.last().removeClass('green').addClass('red');
-    }
-    return status && topicDropdown.children('a.ui.label.red').length === 0;
-  };
-
-  topicForm.form({
-    on: 'change',
-    inline: true,
-    fields: {
-      topics: {
-        identifier: 'topics',
-        rules: [
-          {
-            type: 'validateTopic',
-            value: /^[a-z0-9][a-z0-9-]{0,35}$/,
-            prompt: topicPrompts.formatPrompt
-          },
-          {
-            type: 'maxCount[25]',
-            prompt: topicPrompts.countPrompt
-          }
-        ]
-      },
-    }
-  });
-}
-
-function updateDeadline(deadlineString) {
-  $('#deadline-err-invalid-date').hide();
-  $('#deadline-loader').addClass('loading');
-
-  let realDeadline = null;
-  if (deadlineString !== '') {
-    const newDate = Date.parse(deadlineString);
-
-    if (Number.isNaN(newDate)) {
-      $('#deadline-loader').removeClass('loading');
-      $('#deadline-err-invalid-date').show();
-      return false;
-    }
-    realDeadline = new Date(newDate);
-  }
-
-  $.ajax(`${$('#update-issue-deadline-form').attr('action')}/deadline`, {
-    data: JSON.stringify({
-      due_date: realDeadline,
-    }),
-    headers: {
-      'X-Csrf-Token': csrf,
-      'X-Remote': true,
-    },
-    contentType: 'application/json',
-    type: 'POST',
-    success() {
-      reload();
-    },
-    error() {
-      $('#deadline-loader').removeClass('loading');
-      $('#deadline-err-invalid-date').show();
-    }
-  });
-}
-
-function initIssueDue() {
-  $(document).on('click', '.issue-due-edit', () => {
-    $('#deadlineForm').fadeToggle(150);
-  });
-  $(document).on('click', '.issue-due-remove', () => {
-    updateDeadline('');
-  });
-  $(document).on('submit', '.issue-due-form', () => {
-    updateDeadline($('#deadlineDate').val());
-    return false;
-  });
-}
-
-function initIssueList() {
-  const repolink = $('#repolink').val();
-  const repoId = $('#repoId').val();
-  const crossRepoSearch = $('#crossRepoSearch').val();
-  const tp = $('#type').val();
-  let issueSearchUrl = `${AppSubUrl}/api/v1/repos/${repolink}/issues?q={query}&type=${tp}`;
-  if (crossRepoSearch === 'true') {
-    issueSearchUrl = `${AppSubUrl}/api/v1/repos/issues/search?q={query}&priority_repo_id=${repoId}&type=${tp}`;
-  }
-  $('#new-dependency-drop-list')
-    .dropdown({
-      apiSettings: {
-        url: issueSearchUrl,
-        onResponse(response) {
-          const filteredResponse = {success: true, results: []};
-          const currIssueId = $('#new-dependency-drop-list').data('issue-id');
-          // Parse the response from the api to work with our dropdown
-          $.each(response, (_i, issue) => {
-            // Don't list current issue in the dependency list.
-            if (issue.id === currIssueId) {
-              return;
-            }
-            filteredResponse.results.push({
-              name: `#${issue.number} ${htmlEscape(issue.title)
-              }<div class="text small dont-break-out">${htmlEscape(issue.repository.full_name)}</div>`,
-              value: issue.id
-            });
-          });
-          return filteredResponse;
-        },
-        cache: false,
-      },
-
-      fullTextSearch: true
-    });
-
-  function excludeLabel (item) {
-    const href = $(item).attr('href');
-    const id = $(item).data('label-id');
-
-    const regStr = `labels=((?:-?[0-9]+%2c)*)(${id})((?:%2c-?[0-9]+)*)&`;
-    const newStr = 'labels=$1-$2$3&';
-
-    window.location = href.replace(new RegExp(regStr), newStr);
-  }
-
-  $('.menu a.label-filter-item').each(function () {
-    $(this).on('click', function (e) {
-      if (e.altKey) {
-        e.preventDefault();
-        excludeLabel(this);
-      }
-    });
-  });
-
-  $('.menu .ui.dropdown.label-filter').on('keydown', (e) => {
-    if (e.altKey && e.keyCode === 13) {
-      const selectedItems = $('.menu .ui.dropdown.label-filter .menu .item.selected');
-      if (selectedItems.length > 0) {
-        excludeLabel($(selectedItems[0]));
-      }
-    }
-  });
-}
-
-$(document).on('click', 'button[name="is_review"]', (e) => {
-  $(e.target).closest('form').append('<input type="hidden" name="is_review" value="true">');
-});
-
-$(document).on('submit', '.conversation-holder form', async (e) => {
-  e.preventDefault();
-  const form = $(e.target);
-  const newConversationHolder = $(await $.post(form.attr('action'), form.serialize()));
-  const {path, side, idx} = newConversationHolder.data();
-
-  form.closest('.conversation-holder').replaceWith(newConversationHolder);
-  if (form.closest('tr').data('line-type') === 'same') {
-    $(`a.add-code-comment[data-path="${path}"][data-idx="${idx}"]`).addClass('invisible');
-  } else {
-    $(`a.add-code-comment[data-path="${path}"][data-side="${side}"][data-idx="${idx}"]`).addClass('invisible');
-  }
-  newConversationHolder.find('.dropdown').dropdown();
-  initReactionSelector(newConversationHolder);
-  initClipboard();
-});
-
-$(document).on('click', '.oauth-login-image', () => {
-  const oauthLoader = $('#oauth2-login-loader');
-  const oauthNav = $('#oauth2-login-navigator');
-
-  oauthNav.hide();
-  oauthLoader.removeClass('disabled');
-
-  setTimeout(() => {
-    // recover previous content to let user try again
-    // usually redirection will be performed before this action
-    oauthLoader.addClass('disabled');
-    oauthNav.show();
-  }, 5000);
-});