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