Split `index.js` to separate files (#17315)
	
		
	
				
					
				
			* 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>
			
			
				tokarchuk/v1.17
			
			
		
							parent
							
								
									3728f1daa0
								
							
						
					
					
						commit
						1a7473ff45
					
				@ -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'}); | 
				
			||||
} | 
				
			||||
@ -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'); | 
				
			||||
      }); | 
				
			||||
    }); | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
@ -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); | 
				
			||||
} | 
				
			||||
@ -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'); | 
				
			||||
    }); | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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'); | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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'); | 
				
			||||
} | 
				
			||||
@ -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); | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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; | 
				
			||||
} | 
				
			||||
@ -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}]()`, ``); | 
				
			||||
          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(``, pos); | 
				
			||||
      const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid); | 
				
			||||
      files.append(input); | 
				
			||||
    } | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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; | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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(); | 
				
			||||
    }); | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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); | 
				
			||||
        } | 
				
			||||
      } | 
				
			||||
    }); | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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 | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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) | 
				
			||||
    ); | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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'); | 
				
			||||
    } | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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 | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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'); | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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; | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -0,0 +1,6 @@ | 
				
			||||
export function initRepoCommitButton() { | 
				
			||||
  $('.commit-button').on('click', function (e) { | 
				
			||||
    e.preventDefault(); | 
				
			||||
    $(this).parent().find('.commit-body').toggle(); | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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(); | 
				
			||||
    }); | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
@ -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}`; | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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(); | 
				
			||||
    } | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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 | 
				
			||||
          } | 
				
			||||
        ] | 
				
			||||
      }, | 
				
			||||
    } | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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); | 
				
			||||
} | 
				
			||||
@ -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(); | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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(); | 
				
			||||
      } | 
				
			||||
    }); | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
@ -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); | 
				
			||||
} | 
				
			||||
@ -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'); | 
				
			||||
      } | 
				
			||||
    }); | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
@ -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(); | 
				
			||||
} | 
				
			||||
@ -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); | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
@ -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]); | 
				
			||||
    } | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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'); | 
				
			||||
    } | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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'); | 
				
			||||
  }); | 
				
			||||
} | 
				
			||||
@ -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(); | 
				
			||||
      } | 
				
			||||
    }); | 
				
			||||
  } | 
				
			||||
} | 
				
			||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						
					Loading…
					
					
				
		Reference in new issue