Split index.js
to separate files (#17315)
* split `index.js` to separate files * tune clipboard * fix promise * fix document * remove intermediate empty file * fix async event listener * use `export function` instead of `export {}`, add more comments Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
@ -1,11 +0,0 @@
|
||||
import {svg} from '../svg.js';
|
||||
|
||||
export function showLineButton() {
|
||||
if ($('.code-line-menu').length === 0) return;
|
||||
$('.code-line-button').remove();
|
||||
$('.code-view td.lines-code.active').closest('tr').find('td:eq(0)').first().prepend(
|
||||
$(`<button class="code-line-button">${svg('octicon-kebab-horizontal')}</button>`)
|
||||
);
|
||||
$('.code-line-menu').appendTo($('.code-view'));
|
||||
$('.code-line-button').popup({popup: $('.code-line-menu'), on: 'click'});
|
||||
}
|
@ -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};
|
||||
|
@ -101,10 +101,9 @@ const sfc = {
|
||||
}
|
||||
};
|
||||
|
||||
function initRepoActivityTopAuthorsChart() {
|
||||
export function initRepoActivityTopAuthorsChart() {
|
||||
initVueApp('#repo-activity-top-authors-chart', sfc);
|
||||
}
|
||||
|
||||
export default sfc;
|
||||
export {initRepoActivityTopAuthorsChart};
|
||||
export default sfc; // this line is necessary to activate the IDE's Vue plugin
|
||||
</script>
|
||||
|
@ -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};
|
||||
|
@ -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};
|
||||
|
214
web_src/js/features/admin-common.js
Normal file
214
web_src/js/features/admin-common.js
Normal file
@ -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');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
12
web_src/js/features/admin-emails.js
Normal file
12
web_src/js/features/admin-emails.js
Normal file
@ -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);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
const selector = '[data-clipboard-target], [data-clipboard-text]';
|
||||
// For all DOM elements with [data-clipboard-target] or [data-clipboard-text], this copy-to-clipboard will work for them
|
||||
|
||||
// TODO: replace these with toast-style notifications
|
||||
function onSuccess(btn) {
|
||||
@ -16,23 +16,28 @@ function onError(btn) {
|
||||
btn.dataset.content = btn.dataset.original;
|
||||
}
|
||||
|
||||
export default async function initClipboard() {
|
||||
for (const btn of document.querySelectorAll(selector) || []) {
|
||||
btn.addEventListener('click', async () => {
|
||||
export default function initGlobalCopyToClipboardListener() {
|
||||
document.addEventListener('click', async (e) => {
|
||||
let target = e.target;
|
||||
// in case <button data-clipboard-text><svg></button>, so we just search up to 3 levels for performance.
|
||||
for (let i = 0; i < 3 && target; i++) {
|
||||
let text;
|
||||
if (btn.dataset.clipboardText) {
|
||||
text = btn.dataset.clipboardText;
|
||||
} else if (btn.dataset.clipboardTarget) {
|
||||
text = document.querySelector(btn.dataset.clipboardTarget)?.value;
|
||||
if (target.dataset.clipboardText) {
|
||||
text = target.dataset.clipboardText;
|
||||
} else if (target.dataset.clipboardTarget) {
|
||||
text = document.querySelector(target.dataset.clipboardTarget)?.value;
|
||||
}
|
||||
if (!text) return;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
onSuccess(btn);
|
||||
} catch {
|
||||
onError(btn);
|
||||
if (text) {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
onSuccess(target);
|
||||
} catch {
|
||||
onError(target);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
target = target.parentElement;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
306
web_src/js/features/common-global.js
Normal file
306
web_src/js/features/common-global.js
Normal file
File diff suppressed because it is too large
Load Diff
40
web_src/js/features/common-issue.js
Normal file
40
web_src/js/features/common-issue.js
Normal file
@ -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');
|
||||
});
|
||||
}
|
24
web_src/js/features/common-organization.js
Normal file
24
web_src/js/features/common-organization.js
Normal file
@ -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');
|
||||
}
|
11
web_src/js/features/comp/ColorPicker.js
Normal file
11
web_src/js/features/comp/ColorPicker.js
Normal file
@ -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);
|
||||
});
|
||||
}
|
72
web_src/js/features/comp/CommentSimpleMDE.js
Normal file
72
web_src/js/features/comp/CommentSimpleMDE.js
Normal file
@ -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;
|
||||
}
|
91
web_src/js/features/comp/ImagePaste.js
Normal file
91
web_src/js/features/comp/ImagePaste.js
Normal file
@ -0,0 +1,91 @@
|
||||
const {AppSubUrl, csrf} = window.config;
|
||||
|
||||
async function uploadFile(file, uploadUrl) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file, file.name);
|
||||
|
||||
const res = await fetch(uploadUrl, {
|
||||
method: 'POST',
|
||||
headers: {'X-Csrf-Token': csrf},
|
||||
body: formData,
|
||||
});
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
function clipboardPastedImages(e) {
|
||||
if (!e.clipboardData) return [];
|
||||
|
||||
const files = [];
|
||||
for (const item of e.clipboardData.items || []) {
|
||||
if (!item.type || !item.type.startsWith('image/')) continue;
|
||||
files.push(item.getAsFile());
|
||||
}
|
||||
|
||||
if (files.length) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
|
||||
function insertAtCursor(field, value) {
|
||||
if (field.selectionStart || field.selectionStart === 0) {
|
||||
const startPos = field.selectionStart;
|
||||
const endPos = field.selectionEnd;
|
||||
field.value = field.value.substring(0, startPos) + value + field.value.substring(endPos, field.value.length);
|
||||
field.selectionStart = startPos + value.length;
|
||||
field.selectionEnd = startPos + value.length;
|
||||
} else {
|
||||
field.value += value;
|
||||
}
|
||||
}
|
||||
|
||||
function replaceAndKeepCursor(field, oldval, newval) {
|
||||
if (field.selectionStart || field.selectionStart === 0) {
|
||||
const startPos = field.selectionStart;
|
||||
const endPos = field.selectionEnd;
|
||||
field.value = field.value.replace(oldval, newval);
|
||||
field.selectionStart = startPos + newval.length - oldval.length;
|
||||
field.selectionEnd = endPos + newval.length - oldval.length;
|
||||
} else {
|
||||
field.value = field.value.replace(oldval, newval);
|
||||
}
|
||||
}
|
||||
|
||||
export function initCompImagePaste($target) {
|
||||
$target.each(function () {
|
||||
const dropzone = this.querySelector('.dropzone');
|
||||
if (!dropzone) {
|
||||
return;
|
||||
}
|
||||
const uploadUrl = dropzone.dataset.uploadUrl;
|
||||
const dropzoneFiles = dropzone.querySelector('.files');
|
||||
for (const textarea of this.querySelectorAll('textarea')) {
|
||||
textarea.addEventListener('paste', async (e) => {
|
||||
for (const img of clipboardPastedImages(e)) {
|
||||
const name = img.name.substr(0, img.name.lastIndexOf('.'));
|
||||
insertAtCursor(textarea, `![${name}]()`);
|
||||
const data = await uploadFile(img, uploadUrl);
|
||||
replaceAndKeepCursor(textarea, `![${name}]()`, ``);
|
||||
const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
|
||||
dropzoneFiles.appendChild(input[0]);
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function initSimpleMDEImagePaste(simplemde, dropzone, files) {
|
||||
const uploadUrl = dropzone.dataset.uploadUrl;
|
||||
simplemde.codemirror.on('paste', async (_, e) => {
|
||||
for (const img of clipboardPastedImages(e)) {
|
||||
const name = img.name.substr(0, img.name.lastIndexOf('.'));
|
||||
const data = await uploadFile(img, uploadUrl);
|
||||
const pos = simplemde.codemirror.getCursor();
|
||||
simplemde.codemirror.replaceRange(``, pos);
|
||||
const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
|
||||
files.append(input);
|
||||
}
|
||||
});
|
||||
}
|
30
web_src/js/features/comp/LabelEdit.js
Normal file
30
web_src/js/features/comp/LabelEdit.js
Normal file
@ -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;
|
||||
});
|
||||
}
|
21
web_src/js/features/comp/MarkupContentPreview.js
Normal file
21
web_src/js/features/comp/MarkupContentPreview.js
Normal file
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
48
web_src/js/features/comp/ReactionSelector.js
Normal file
48
web_src/js/features/comp/ReactionSelector.js
Normal file
@ -0,0 +1,48 @@
|
||||
const {csrf} = window.config;
|
||||
|
||||
export function initCompReactionSelector(parent) {
|
||||
let reactions = '';
|
||||
if (!parent) {
|
||||
parent = $(document);
|
||||
reactions = '.reactions > ';
|
||||
}
|
||||
|
||||
parent.find(`${reactions}a.label`).popup({position: 'bottom left', metadata: {content: 'title', title: 'none'}});
|
||||
|
||||
parent.find(`.select-reaction > .menu > .item, ${reactions}a.label`).on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if ($(this).hasClass('disabled')) return;
|
||||
|
||||
const actionURL = $(this).hasClass('item') ? $(this).closest('.select-reaction').data('action-url') : $(this).data('action-url');
|
||||
const url = `${actionURL}/${$(this).hasClass('blue') ? 'unreact' : 'react'}`;
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url,
|
||||
data: {
|
||||
_csrf: csrf,
|
||||
content: $(this).data('content')
|
||||
}
|
||||
}).done((resp) => {
|
||||
if (resp && (resp.html || resp.empty)) {
|
||||
const content = $(this).closest('.content');
|
||||
let react = content.find('.segment.reactions');
|
||||
if ((!resp.empty || resp.html === '') && react.length > 0) {
|
||||
react.remove();
|
||||
}
|
||||
if (!resp.empty) {
|
||||
react = $('<div class="ui attached segment reactions"></div>');
|
||||
const attachments = content.find('.segment.bottom:first');
|
||||
if (attachments.length > 0) {
|
||||
react.insertBefore(attachments);
|
||||
} else {
|
||||
react.appendTo(content);
|
||||
}
|
||||
react.html(resp.html);
|
||||
react.find('.dropdown').dropdown();
|
||||
initCompReactionSelector(react);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
36
web_src/js/features/comp/SearchUserBox.js
Normal file
36
web_src/js/features/comp/SearchUserBox.js
Normal file
@ -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
|
||||
});
|
||||
}
|
40
web_src/js/features/comp/WebHookEditor.js
Normal file
40
web_src/js/features/comp/WebHookEditor.js
Normal file
@ -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)
|
||||
);
|
||||
});
|
||||
}
|
91
web_src/js/features/install.js
Normal file
91
web_src/js/features/install.js
Normal file
@ -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');
|
||||
}
|
||||
});
|
||||
}
|
37
web_src/js/features/org-team.js
Normal file
37
web_src/js/features/org-team.js
Normal file
@ -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
|
||||
});
|
||||
}
|
7
web_src/js/features/repo-branch.js
Normal file
7
web_src/js/features/repo-branch.js
Normal file
@ -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');
|
||||
});
|
||||
}
|
145
web_src/js/features/repo-code.js
Normal file
145
web_src/js/features/repo-code.js
Normal file
@ -0,0 +1,145 @@
|
||||
import {svg} from '../svg.js';
|
||||
|
||||
function changeHash(hash) {
|
||||
if (window.history.pushState) {
|
||||
window.history.pushState(null, null, hash);
|
||||
} else {
|
||||
window.location.hash = hash;
|
||||
}
|
||||
}
|
||||
|
||||
function selectRange($list, $select, $from) {
|
||||
$list.removeClass('active');
|
||||
|
||||
// add hashchange to permalink
|
||||
const $issue = $('a.ref-in-new-issue');
|
||||
const $copyPermalink = $('a.copy-line-permalink');
|
||||
|
||||
if ($issue.length === 0 || $copyPermalink.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updateIssueHref = function(anchor) {
|
||||
let href = $issue.attr('href');
|
||||
href = `${href.replace(/%23L\d+$|%23L\d+-L\d+$/, '')}%23${anchor}`;
|
||||
$issue.attr('href', href);
|
||||
};
|
||||
|
||||
const updateCopyPermalinkHref = function(anchor) {
|
||||
let link = $copyPermalink.attr('data-clipboard-text');
|
||||
link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`;
|
||||
$copyPermalink.attr('data-clipboard-text', link);
|
||||
};
|
||||
|
||||
if ($from) {
|
||||
let a = parseInt($select.attr('rel').substr(1));
|
||||
let b = parseInt($from.attr('rel').substr(1));
|
||||
let c;
|
||||
if (a !== b) {
|
||||
if (a > b) {
|
||||
c = a;
|
||||
a = b;
|
||||
b = c;
|
||||
}
|
||||
const classes = [];
|
||||
for (let i = a; i <= b; i++) {
|
||||
classes.push(`[rel=L${i}]`);
|
||||
}
|
||||
$list.filter(classes.join(',')).addClass('active');
|
||||
changeHash(`#L${a}-L${b}`);
|
||||
|
||||
updateIssueHref(`L${a}-L${b}`);
|
||||
updateCopyPermalinkHref(`L${a}-L${b}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$select.addClass('active');
|
||||
changeHash(`#${$select.attr('rel')}`);
|
||||
|
||||
updateIssueHref($select.attr('rel'));
|
||||
updateCopyPermalinkHref($select.attr('rel'));
|
||||
}
|
||||
|
||||
function showLineButton() {
|
||||
if ($('.code-line-menu').length === 0) return;
|
||||
$('.code-line-button').remove();
|
||||
$('.code-view td.lines-code.active').closest('tr').find('td:eq(0)').first().prepend(
|
||||
$(`<button class="code-line-button">${svg('octicon-kebab-horizontal')}</button>`)
|
||||
);
|
||||
$('.code-line-menu').appendTo($('.code-view'));
|
||||
$('.code-line-button').popup({popup: $('.code-line-menu'), on: 'click'});
|
||||
}
|
||||
|
||||
export function initRepoCodeView() {
|
||||
if ($('.code-view .lines-num').length > 0) {
|
||||
$(document).on('click', '.lines-num span', function (e) {
|
||||
const $select = $(this);
|
||||
let $list;
|
||||
if ($('div.blame').length) {
|
||||
$list = $('.code-view td.lines-code.blame-code');
|
||||
} else {
|
||||
$list = $('.code-view td.lines-code');
|
||||
}
|
||||
selectRange($list, $list.filter(`[rel=${$select.attr('id')}]`), (e.shiftKey ? $list.filter('.active').eq(0) : null));
|
||||
|
||||
if (window.getSelection) {
|
||||
window.getSelection().removeAllRanges();
|
||||
} else {
|
||||
document.selection.empty();
|
||||
}
|
||||
|
||||
// show code view menu marker (don't show in blame page)
|
||||
if ($('div.blame').length === 0) {
|
||||
showLineButton();
|
||||
}
|
||||
});
|
||||
|
||||
$(window).on('hashchange', () => {
|
||||
let m = window.location.hash.match(/^#(L\d+)-(L\d+)$/);
|
||||
let $list;
|
||||
if ($('div.blame').length) {
|
||||
$list = $('.code-view td.lines-code.blame-code');
|
||||
} else {
|
||||
$list = $('.code-view td.lines-code');
|
||||
}
|
||||
let $first;
|
||||
if (m) {
|
||||
$first = $list.filter(`[rel=${m[1]}]`);
|
||||
selectRange($list, $first, $list.filter(`[rel=${m[2]}]`));
|
||||
|
||||
// show code view menu marker (don't show in blame page)
|
||||
if ($('div.blame').length === 0) {
|
||||
showLineButton();
|
||||
}
|
||||
|
||||
$('html, body').scrollTop($first.offset().top - 200);
|
||||
return;
|
||||
}
|
||||
m = window.location.hash.match(/^#(L|n)(\d+)$/);
|
||||
if (m) {
|
||||
$first = $list.filter(`[rel=L${m[2]}]`);
|
||||
selectRange($list, $first);
|
||||
|
||||
// show code view menu marker (don't show in blame page)
|
||||
if ($('div.blame').length === 0) {
|
||||
showLineButton();
|
||||
}
|
||||
|
||||
$('html, body').scrollTop($first.offset().top - 200);
|
||||
}
|
||||
}).trigger('hashchange');
|
||||
}
|
||||
$(document).on('click', '.fold-file', ({currentTarget}) => {
|
||||
const box = currentTarget.closest('.file-content');
|
||||
const chevron = currentTarget.querySelector('a.chevron');
|
||||
const folded = box.dataset.folded !== 'true';
|
||||
chevron.innerHTML = svg(`octicon-chevron-${folded ? 'right' : 'down'}`, 18);
|
||||
box.dataset.folded = String(folded);
|
||||
});
|
||||
$(document).on('click', '.blob-excerpt', async ({currentTarget}) => {
|
||||
const {url, query, anchor} = currentTarget.dataset;
|
||||
if (!url) return;
|
||||
const blob = await $.get(`${url}?${query}&anchor=${anchor}`);
|
||||
currentTarget.closest('tr').outerHTML = blob;
|
||||
});
|
||||
}
|
6
web_src/js/features/repo-commit.js
Normal file
6
web_src/js/features/repo-commit.js
Normal file
@ -0,0 +1,6 @@
|
||||
export function initRepoCommitButton() {
|
||||
$('.commit-button').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
$(this).parent().find('.commit-body').toggle();
|
||||
});
|
||||
}
|
100
web_src/js/features/repo-common.js
Normal file
100
web_src/js/features/repo-common.js
Normal file
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
81
web_src/js/features/repo-diff.js
Normal file
81
web_src/js/features/repo-diff.js
Normal file
@ -0,0 +1,81 @@
|
||||
import {initCompReactionSelector} from './comp/ReactionSelector.js';
|
||||
|
||||
const {csrf} = window.config;
|
||||
|
||||
export function initRepoDiffReviewButton() {
|
||||
$(document).on('click', 'button[name="is_review"]', (e) => {
|
||||
$(e.target).closest('form').append('<input type="hidden" name="is_review" value="true">');
|
||||
});
|
||||
}
|
||||
|
||||
export function initRepoDiffFileViewToggle() {
|
||||
$('.file-view-toggle').on('click', function () {
|
||||
const $this = $(this);
|
||||
$this.parent().children().removeClass('active');
|
||||
$this.addClass('active');
|
||||
|
||||
const $target = $($this.data('toggle-selector'));
|
||||
$target.parent().children().addClass('hide');
|
||||
$target.removeClass('hide');
|
||||
});
|
||||
}
|
||||
|
||||
export function initRepoDiffConversationForm() {
|
||||
$('.conversation-holder form').on('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const form = $(e.target);
|
||||
const newConversationHolder = $(await $.post(form.attr('action'), form.serialize()));
|
||||
const {path, side, idx} = newConversationHolder.data();
|
||||
|
||||
form.closest('.conversation-holder').replaceWith(newConversationHolder);
|
||||
if (form.closest('tr').data('line-type') === 'same') {
|
||||
$(`a.add-code-comment[data-path="${path}"][data-idx="${idx}"]`).addClass('invisible');
|
||||
} else {
|
||||
$(`a.add-code-comment[data-path="${path}"][data-side="${side}"][data-idx="${idx}"]`).addClass('invisible');
|
||||
}
|
||||
newConversationHolder.find('.dropdown').dropdown();
|
||||
initCompReactionSelector(newConversationHolder);
|
||||
});
|
||||
|
||||
|
||||
$('.resolve-conversation').on('click', async function (e) {
|
||||
e.preventDefault();
|
||||
const comment_id = $(this).data('comment-id');
|
||||
const origin = $(this).data('origin');
|
||||
const action = $(this).data('action');
|
||||
const url = $(this).data('update-url');
|
||||
|
||||
const data = await $.post(url, {_csrf: csrf, origin, action, comment_id});
|
||||
|
||||
if ($(this).closest('.conversation-holder').length) {
|
||||
const conversation = $(data);
|
||||
$(this).closest('.conversation-holder').replaceWith(conversation);
|
||||
conversation.find('.dropdown').dropdown();
|
||||
initCompReactionSelector(conversation);
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function initRepoDiffConversationNav() {
|
||||
// Previous/Next code review conversation
|
||||
$(document).on('click', '.previous-conversation', (e) => {
|
||||
const $conversation = $(e.currentTarget).closest('.comment-code-cloud');
|
||||
const $conversations = $('.comment-code-cloud:not(.hide)');
|
||||
const index = $conversations.index($conversation);
|
||||
const previousIndex = index > 0 ? index - 1 : $conversations.length - 1;
|
||||
const $previousConversation = $conversations.eq(previousIndex);
|
||||
const anchor = $previousConversation.find('.comment').first().attr('id');
|
||||
window.location.href = `#${anchor}`;
|
||||
});
|
||||
$(document).on('click', '.next-conversation', (e) => {
|
||||
const $conversation = $(e.currentTarget).closest('.comment-code-cloud');
|
||||
const $conversations = $('.comment-code-cloud:not(.hide)');
|
||||
const index = $conversations.index($conversation);
|
||||
const nextIndex = index < $conversations.length - 1 ? index + 1 : 0;
|
||||
const $nextConversation = $conversations.eq(nextIndex);
|
||||
const anchor = $nextConversation.find('.comment').first().attr('id');
|
||||
window.location.href = `#${anchor}`;
|
||||
});
|
||||
}
|
180
web_src/js/features/repo-editor.js
Normal file
180
web_src/js/features/repo-editor.js
Normal file
@ -0,0 +1,180 @@
|
||||
import {initMarkupContent} from '../markup/content.js';
|
||||
import {createCodeEditor} from './codeeditor.js';
|
||||
|
||||
const {csrf} = window.config;
|
||||
|
||||
let previewFileModes;
|
||||
|
||||
function initEditPreviewTab($form) {
|
||||
const $tabMenu = $form.find('.tabular.menu');
|
||||
$tabMenu.find('.item').tab();
|
||||
const $previewTab = $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`);
|
||||
if ($previewTab.length) {
|
||||
previewFileModes = $previewTab.data('preview-file-modes').split(',');
|
||||
$previewTab.on('click', function () {
|
||||
const $this = $(this);
|
||||
let context = `${$this.data('context')}/`;
|
||||
const mode = $this.data('markdown-mode') || 'comment';
|
||||
const treePathEl = $form.find('input#tree_path');
|
||||
if (treePathEl.length > 0) {
|
||||
context += treePathEl.val();
|
||||
}
|
||||
context = context.substring(0, context.lastIndexOf('/'));
|
||||
$.post($this.data('url'), {
|
||||
_csrf: csrf,
|
||||
mode,
|
||||
context,
|
||||
text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val()
|
||||
}, (data) => {
|
||||
const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`);
|
||||
$previewPanel.html(data);
|
||||
initMarkupContent();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initEditDiffTab($form) {
|
||||
const $tabMenu = $form.find('.tabular.menu');
|
||||
$tabMenu.find('.item').tab();
|
||||
$tabMenu.find(`.item[data-tab="${$tabMenu.data('diff')}"]`).on('click', function () {
|
||||
const $this = $(this);
|
||||
$.post($this.data('url'), {
|
||||
_csrf: csrf,
|
||||
context: $this.data('context'),
|
||||
content: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val()
|
||||
}, (data) => {
|
||||
const $diffPreviewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('diff')}"]`);
|
||||
$diffPreviewPanel.html(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initEditorForm() {
|
||||
if ($('.repository .edit.form').length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
initEditPreviewTab($('.repository .edit.form'));
|
||||
initEditDiffTab($('.repository .edit.form'));
|
||||
}
|
||||
|
||||
|
||||
function getCursorPosition($e) {
|
||||
const el = $e.get(0);
|
||||
let pos = 0;
|
||||
if ('selectionStart' in el) {
|
||||
pos = el.selectionStart;
|
||||
} else if ('selection' in document) {
|
||||
el.focus();
|
||||
const Sel = document.selection.createRange();
|
||||
const SelLength = document.selection.createRange().text.length;
|
||||
Sel.moveStart('character', -el.value.length);
|
||||
pos = Sel.text.length - SelLength;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
export async function initRepoEditor() {
|
||||
initEditorForm();
|
||||
|
||||
$('.js-quick-pull-choice-option').on('change', function () {
|
||||
if ($(this).val() === 'commit-to-new-branch') {
|
||||
$('.quick-pull-branch-name').show();
|
||||
$('.quick-pull-branch-name input').prop('required', true);
|
||||
} else {
|
||||
$('.quick-pull-branch-name').hide();
|
||||
$('.quick-pull-branch-name input').prop('required', false);
|
||||
}
|
||||
$('#commit-button').text($(this).attr('button_text'));
|
||||
});
|
||||
|
||||
const $editFilename = $('#file-name');
|
||||
$editFilename.on('keyup', function (e) {
|
||||
const $section = $('.breadcrumb span.section');
|
||||
const $divider = $('.breadcrumb div.divider');
|
||||
let value;
|
||||
let parts;
|
||||
|
||||
if (e.keyCode === 8 && getCursorPosition($(this)) === 0 && $section.length > 0) {
|
||||
value = $section.last().find('a').text();
|
||||
$(this).val(value + $(this).val());
|
||||
$(this)[0].setSelectionRange(value.length, value.length);
|
||||
$section.last().remove();
|
||||
$divider.last().remove();
|
||||
}
|
||||
if (e.keyCode === 191) {
|
||||
parts = $(this).val().split('/');
|
||||
for (let i = 0; i < parts.length; ++i) {
|
||||
value = parts[i];
|
||||
if (i < parts.length - 1) {
|
||||
if (value.length) {
|
||||
$(`<span class="section"><a href="#">${value}</a></span>`).insertBefore($(this));
|
||||
$('<div class="divider"> / </div>').insertBefore($(this));
|
||||
}
|
||||
} else {
|
||||
$(this).val(value);
|
||||
}
|
||||
$(this)[0].setSelectionRange(0, 0);
|
||||
}
|
||||
}
|
||||
parts = [];
|
||||
$('.breadcrumb span.section').each(function () {
|
||||
const element = $(this);
|
||||
if (element.find('a').length) {
|
||||
parts.push(element.find('a').text());
|
||||
} else {
|
||||
parts.push(element.text());
|
||||
}
|
||||
});
|
||||
if ($(this).val()) parts.push($(this).val());
|
||||
$('#tree_path').val(parts.join('/'));
|
||||
}).trigger('keyup');
|
||||
|
||||
const $editArea = $('.repository.editor textarea#edit_area');
|
||||
if (!$editArea.length) return;
|
||||
|
||||
const editor = await createCodeEditor($editArea[0], $editFilename[0], previewFileModes);
|
||||
|
||||
// Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
|
||||
// to enable or disable the commit button
|
||||
const $commitButton = $('#commit-button');
|
||||
const $editForm = $('.ui.edit.form');
|
||||
const dirtyFileClass = 'dirty-file';
|
||||
|
||||
// Disabling the button at the start
|
||||
if ($('input[name="page_has_posted"]').val() !== 'true') {
|
||||
$commitButton.prop('disabled', true);
|
||||
}
|
||||
|
||||
// Registering a custom listener for the file path and the file content
|
||||
$editForm.areYouSure({
|
||||
silent: true,
|
||||
dirtyClass: dirtyFileClass,
|
||||
fieldSelector: ':input:not(.commit-form-wrapper :input)',
|
||||
change() {
|
||||
const dirty = $(this).hasClass(dirtyFileClass);
|
||||
$commitButton.prop('disabled', !dirty);
|
||||
}
|
||||
});
|
||||
|
||||
// Update the editor from query params, if available,
|
||||
// only after the dirtyFileClass initialization
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const value = params.get('value');
|
||||
if (value) {
|
||||
editor.setValue(value);
|
||||
}
|
||||
|
||||
$commitButton.on('click', (event) => {
|
||||
// A modal which asks if an empty file should be committed
|
||||
if ($editArea.val().length === 0) {
|
||||
$('#edit-empty-content-modal').modal({
|
||||
onApprove() {
|
||||
$('.edit.form').trigger('submit');
|
||||
}
|
||||
}).modal('show');
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
180
web_src/js/features/repo-home.js
Normal file
180
web_src/js/features/repo-home.js
Normal file
@ -0,0 +1,180 @@
|
||||
import {stripTags} from '../utils.js';
|
||||
|
||||
const {AppSubUrl, csrf} = window.config;
|
||||
|
||||
export function initRepoTopicBar() {
|
||||
const mgrBtn = $('#manage_topic');
|
||||
const editDiv = $('#topic_edit');
|
||||
const viewDiv = $('#repo-topics');
|
||||
const saveBtn = $('#save_topic');
|
||||
const topicDropdown = $('#topic_edit .dropdown');
|
||||
const topicForm = $('#topic_edit.ui.form');
|
||||
const topicPrompts = getPrompts();
|
||||
|
||||
mgrBtn.on('click', () => {
|
||||
viewDiv.hide();
|
||||
editDiv.css('display', ''); // show Semantic UI Grid
|
||||
});
|
||||
|
||||
function getPrompts() {
|
||||
const hidePrompt = $('div.hide#validate_prompt');
|
||||
const prompts = {
|
||||
countPrompt: hidePrompt.children('#count_prompt').text(),
|
||||
formatPrompt: hidePrompt.children('#format_prompt').text()
|
||||
};
|
||||
hidePrompt.remove();
|
||||
return prompts;
|
||||
}
|
||||
|
||||
saveBtn.on('click', () => {
|
||||
const topics = $('input[name=topics]').val();
|
||||
|
||||
$.post(saveBtn.data('link'), {
|
||||
_csrf: csrf,
|
||||
topics
|
||||
}, (_data, _textStatus, xhr) => {
|
||||
if (xhr.responseJSON.status === 'ok') {
|
||||
viewDiv.children('.topic').remove();
|
||||
if (topics.length) {
|
||||
const topicArray = topics.split(',');
|
||||
|
||||
const last = viewDiv.children('a').last();
|
||||
for (let i = 0; i < topicArray.length; i++) {
|
||||
const link = $('<a class="ui repo-topic large label topic"></a>');
|
||||
link.attr('href', `${AppSubUrl}/explore/repos?q=${encodeURIComponent(topicArray[i])}&topic=1`);
|
||||
link.text(topicArray[i]);
|
||||
link.insertBefore(last);
|
||||
}
|
||||
}
|
||||
editDiv.css('display', 'none');
|
||||
viewDiv.show();
|
||||
}
|
||||
}).fail((xhr) => {
|
||||
if (xhr.status === 422) {
|
||||
if (xhr.responseJSON.invalidTopics.length > 0) {
|
||||
topicPrompts.formatPrompt = xhr.responseJSON.message;
|
||||
|
||||
const {invalidTopics} = xhr.responseJSON;
|
||||
const topicLables = topicDropdown.children('a.ui.label');
|
||||
|
||||
topics.split(',').forEach((value, index) => {
|
||||
for (let i = 0; i < invalidTopics.length; i++) {
|
||||
if (invalidTopics[i] === value) {
|
||||
topicLables.eq(index).removeClass('green').addClass('red');
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
topicPrompts.countPrompt = xhr.responseJSON.message;
|
||||
}
|
||||
}
|
||||
}).always(() => {
|
||||
topicForm.form('validate form');
|
||||
});
|
||||
});
|
||||
|
||||
topicDropdown.dropdown({
|
||||
allowAdditions: true,
|
||||
forceSelection: false,
|
||||
fullTextSearch: 'exact',
|
||||
fields: {name: 'description', value: 'data-value'},
|
||||
saveRemoteData: false,
|
||||
label: {
|
||||
transition: 'horizontal flip',
|
||||
duration: 200,
|
||||
variation: false,
|
||||
blue: true,
|
||||
basic: true,
|
||||
},
|
||||
className: {
|
||||
label: 'ui small label'
|
||||
},
|
||||
apiSettings: {
|
||||
url: `${AppSubUrl}/api/v1/topics/search?q={query}`,
|
||||
throttle: 500,
|
||||
cache: false,
|
||||
onResponse(res) {
|
||||
const formattedResponse = {
|
||||
success: false,
|
||||
results: [],
|
||||
};
|
||||
const query = stripTags(this.urlData.query.trim());
|
||||
let found_query = false;
|
||||
const current_topics = [];
|
||||
topicDropdown.find('div.label.visible.topic,a.label.visible').each((_, e) => { current_topics.push(e.dataset.value) });
|
||||
|
||||
if (res.topics) {
|
||||
let found = false;
|
||||
for (let i = 0; i < res.topics.length; i++) {
|
||||
// skip currently added tags
|
||||
if (current_topics.includes(res.topics[i].topic_name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (res.topics[i].topic_name.toLowerCase() === query.toLowerCase()) {
|
||||
found_query = true;
|
||||
}
|
||||
formattedResponse.results.push({description: res.topics[i].topic_name, 'data-value': res.topics[i].topic_name});
|
||||
found = true;
|
||||
}
|
||||
formattedResponse.success = found;
|
||||
}
|
||||
|
||||
if (query.length > 0 && !found_query) {
|
||||
formattedResponse.success = true;
|
||||
formattedResponse.results.unshift({description: query, 'data-value': query});
|
||||
} else if (query.length > 0 && found_query) {
|
||||
formattedResponse.results.sort((a, b) => {
|
||||
if (a.description.toLowerCase() === query.toLowerCase()) return -1;
|
||||
if (b.description.toLowerCase() === query.toLowerCase()) return 1;
|
||||
if (a.description > b.description) return -1;
|
||||
if (a.description < b.description) return 1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
return formattedResponse;
|
||||
},
|
||||
},
|
||||
onLabelCreate(value) {
|
||||
value = value.toLowerCase().trim();
|
||||
this.attr('data-value', value).contents().first().replaceWith(value);
|
||||
return $(this);
|
||||
},
|
||||
onAdd(addedValue, _addedText, $addedChoice) {
|
||||
addedValue = addedValue.toLowerCase().trim();
|
||||
$($addedChoice).attr('data-value', addedValue);
|
||||
$($addedChoice).attr('data-text', addedValue);
|
||||
}
|
||||
});
|
||||
|
||||
$.fn.form.settings.rules.validateTopic = function (_values, regExp) {
|
||||
const topics = topicDropdown.children('a.ui.label');
|
||||
const status = topics.length === 0 || topics.last().attr('data-value').match(regExp);
|
||||
if (!status) {
|
||||
topics.last().removeClass('green').addClass('red');
|
||||
}
|
||||
return status && topicDropdown.children('a.ui.label.red').length === 0;
|
||||
};
|
||||
|
||||
topicForm.form({
|
||||
on: 'change',
|
||||
inline: true,
|
||||
fields: {
|
||||
topics: {
|
||||
identifier: 'topics',
|
||||
rules: [
|
||||
{
|
||||
type: 'validateTopic',
|
||||
value: /^[a-z0-9][a-z0-9-]{0,35}$/,
|
||||
prompt: topicPrompts.formatPrompt
|
||||
},
|
||||
{
|
||||
type: 'maxCount[25]',
|
||||
prompt: topicPrompts.countPrompt
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
638
web_src/js/features/repo-issue.js
Normal file
638
web_src/js/features/repo-issue.js
Normal file
File diff suppressed because it is too large
Load Diff
574
web_src/js/features/repo-legacy.js
Normal file
574
web_src/js/features/repo-legacy.js
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user