debian-mirror-gitlab/app/assets/javascripts/projects/project_new.js

392 lines
13 KiB
JavaScript
Raw Normal View History

2018-05-09 12:01:36 +05:30
import $ from 'jquery';
2021-11-18 22:05:49 +05:30
import { debounce } from 'lodash';
2022-07-16 23:28:13 +05:30
import DEFAULT_PROJECT_TEMPLATES from 'any_else_ce/projects/default_project_templates';
2022-04-04 11:22:00 +05:30
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
2022-08-27 11:52:29 +05:30
import Tracking from '~/tracking';
2022-04-04 11:22:00 +05:30
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '../lib/utils/constants';
2022-06-21 17:19:12 +05:30
import { ENTER_KEY } from '../lib/utils/keys';
2021-11-18 22:05:49 +05:30
import axios from '../lib/utils/axios_utils';
2020-11-24 15:15:51 +05:30
import {
convertToTitleCase,
humanize,
slugify,
convertUnicodeToAscii,
} from '../lib/utils/text_utility';
2023-03-04 22:38:38 +05:30
import { checkRules } from './project_name_rules';
2018-03-27 19:54:05 +05:30
2017-09-10 17:25:29 +05:30
let hasUserDefinedProjectPath = false;
2020-03-13 15:44:24 +05:30
let hasUserDefinedProjectName = false;
2021-11-18 22:05:49 +05:30
const invalidInputClass = 'gl-field-error-outline';
2022-06-21 17:19:12 +05:30
const invalidDropdownClass = 'gl-inset-border-1-red-400!';
2021-11-18 22:05:49 +05:30
2022-04-04 11:22:00 +05:30
const cancelSource = axios.CancelToken.source();
const endpoint = `${gon.relative_url_root}/import/url/validate`;
let importCredentialsValidationPromise = null;
2021-11-18 22:05:49 +05:30
const validateImportCredentials = (url, user, password) => {
2022-04-04 11:22:00 +05:30
cancelSource.cancel();
importCredentialsValidationPromise = axios
.post(endpoint, { url, user, password }, { cancelToken: cancelSource.cancel() })
2021-11-18 22:05:49 +05:30
.then(({ data }) => data)
2022-04-04 11:22:00 +05:30
.catch((thrown) =>
axios.isCancel(thrown)
? {
cancelled: true,
}
: {
// intentionally reporting success in case of validation error
// we do not want to block users from trying import in case of validation exception
success: true,
},
);
return importCredentialsValidationPromise;
2021-11-18 22:05:49 +05:30
};
2020-03-13 15:44:24 +05:30
2022-07-23 23:45:48 +05:30
const onProjectNameChangeJq = ($projectNameInput, $projectPathInput) => {
2020-11-24 15:15:51 +05:30
const slug = slugify(convertUnicodeToAscii($projectNameInput.val()));
2020-03-13 15:44:24 +05:30
$projectPathInput.val(slug);
};
2022-07-23 23:45:48 +05:30
const onProjectNameChange = ($projectNameInput, $projectPathInput) => {
const slug = slugify(convertUnicodeToAscii($projectNameInput.value));
// eslint-disable-next-line no-param-reassign
$projectPathInput.value = slug;
};
const onProjectPathChangeJq = ($projectNameInput, $projectPathInput, hasExistingProjectName) => {
2020-03-13 15:44:24 +05:30
const slug = $projectPathInput.val();
if (!hasExistingProjectName) {
$projectNameInput.val(convertToTitleCase(humanize(slug, '[-_]')));
}
};
2022-07-23 23:45:48 +05:30
const onProjectPathChange = ($projectNameInput, $projectPathInput, hasExistingProjectName) => {
const slug = $projectPathInput.value;
if (!hasExistingProjectName) {
// eslint-disable-next-line no-param-reassign
$projectNameInput.value = convertToTitleCase(humanize(slug, '[-_]'));
}
};
2022-06-21 17:19:12 +05:30
const selectedNamespaceId = () => document.querySelector('[name="project[selected_namespace_id]"]');
const dropdownButton = () => document.querySelector('.js-group-namespace-dropdown > button');
const namespaceButton = () => document.querySelector('.js-group-namespace-button');
const namespaceError = () => document.querySelector('.js-group-namespace-error');
const validateGroupNamespaceDropdown = (e) => {
if (selectedNamespaceId() && !selectedNamespaceId().attributes.value) {
document.querySelector('input[data-qa-selector="project_name"]').reportValidity();
e.preventDefault();
dropdownButton().classList.add(invalidDropdownClass);
namespaceButton().classList.add(invalidDropdownClass);
namespaceError().classList.remove('gl-display-none');
} else {
dropdownButton().classList.remove(invalidDropdownClass);
namespaceButton().classList.remove(invalidDropdownClass);
namespaceError().classList.add('gl-display-none');
}
};
2023-03-04 22:38:38 +05:30
const checkProjectName = (projectNameInput) => {
const msg = checkRules(projectNameInput.value);
const projectNameError = document.querySelector('#project_name_error');
if (!projectNameError) return;
if (msg) {
projectNameError.innerText = msg;
projectNameError.classList.remove('hidden');
} else {
projectNameError.classList.add('hidden');
}
};
2020-03-13 15:44:24 +05:30
const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => {
2021-12-11 22:18:48 +05:30
const specialRepo = document.querySelector('.js-user-readme-repo');
2022-07-23 23:45:48 +05:30
const projectNameInputListener = () => {
2020-03-13 15:44:24 +05:30
onProjectNameChange($projectNameInput, $projectPathInput);
2023-03-04 22:38:38 +05:30
checkProjectName($projectNameInput);
2022-07-23 23:45:48 +05:30
hasUserDefinedProjectName = $projectNameInput.value.trim().length > 0;
hasUserDefinedProjectPath = $projectPathInput.value.trim().length > 0;
};
2020-03-13 15:44:24 +05:30
2022-07-23 23:45:48 +05:30
$projectNameInput.removeEventListener('keyup', projectNameInputListener);
$projectNameInput.addEventListener('keyup', projectNameInputListener);
$projectNameInput.removeEventListener('change', projectNameInputListener);
$projectNameInput.addEventListener('change', projectNameInputListener);
const projectPathInputListener = () => {
2020-03-13 15:44:24 +05:30
onProjectPathChange($projectNameInput, $projectPathInput, hasUserDefinedProjectName);
2022-07-23 23:45:48 +05:30
hasUserDefinedProjectPath = $projectPathInput.value.trim().length > 0;
2021-12-11 22:18:48 +05:30
specialRepo.classList.toggle(
'gl-display-none',
2022-07-23 23:45:48 +05:30
$projectPathInput.value !== $projectPathInput.dataset.username,
2021-12-11 22:18:48 +05:30
);
2022-07-23 23:45:48 +05:30
};
2022-08-27 11:52:29 +05:30
const projectPathValueListener = () => {
// eslint-disable-next-line no-param-reassign
$projectPathInput.oldInputValue = $projectPathInput.value;
};
const projectPathTrackListener = () => {
if ($projectPathInput.oldInputValue === $projectPathInput.value) {
// no change made to the input
return;
}
const trackEvent = 'user_input_path_slug';
const trackCategory = undefined; // will be default set in event method
Tracking.event(trackCategory, trackEvent, {
label: 'new_project_form',
});
};
2022-07-23 23:45:48 +05:30
$projectPathInput.removeEventListener('keyup', projectPathInputListener);
$projectPathInput.addEventListener('keyup', projectPathInputListener);
2022-08-27 11:52:29 +05:30
$projectPathInput.removeEventListener('focus', projectPathValueListener);
$projectPathInput.addEventListener('focus', projectPathValueListener);
$projectPathInput.removeEventListener('blur', projectPathTrackListener);
$projectPathInput.addEventListener('blur', projectPathTrackListener);
2022-07-23 23:45:48 +05:30
$projectPathInput.removeEventListener('change', projectPathInputListener);
$projectPathInput.addEventListener('change', projectPathInputListener);
2022-06-21 17:19:12 +05:30
document.querySelector('.js-create-project-button').addEventListener('click', (e) => {
validateGroupNamespaceDropdown(e);
});
2020-03-13 15:44:24 +05:30
};
2017-09-10 17:25:29 +05:30
2021-03-08 18:12:59 +05:30
const deriveProjectPathFromUrl = ($projectImportUrl) => {
2020-03-13 15:44:24 +05:30
const $currentProjectName = $projectImportUrl
2022-07-23 23:45:48 +05:30
.closest('.toggle-import-form')
.querySelector('#project_name');
2018-12-13 13:39:08 +05:30
const $currentProjectPath = $projectImportUrl
2022-07-23 23:45:48 +05:30
.closest('.toggle-import-form')
.querySelector('#project_path');
2020-03-13 15:44:24 +05:30
2022-04-04 11:22:00 +05:30
if (hasUserDefinedProjectPath || $currentProjectPath.length === 0) {
2017-09-10 17:25:29 +05:30
return;
}
2022-07-23 23:45:48 +05:30
let importUrl = $projectImportUrl.value.trim();
2017-09-10 17:25:29 +05:30
if (importUrl.length === 0) {
return;
}
/*
\/?: remove trailing slash
(\.git\/?)?: remove trailing .git (with optional trailing slash)
(\?.*)?: remove query string
(#.*)?: remove fragment identifier
*/
importUrl = importUrl.replace(/\/?(\.git\/?)?(\?.*)?(#.*)?$/, '');
// extract everything after the last slash
const pathMatch = /\/([^/]+)$/.exec(importUrl);
if (pathMatch) {
2022-07-23 23:45:48 +05:30
// eslint-disable-next-line no-unused-vars
const [_, matchingString] = pathMatch;
$currentProjectPath.value = matchingString;
2020-03-13 15:44:24 +05:30
onProjectPathChange($currentProjectName, $currentProjectPath, false);
2017-09-10 17:25:29 +05:30
}
};
2021-11-11 11:23:49 +05:30
const bindHowToImport = () => {
2022-04-04 11:22:00 +05:30
const importLinks = document.querySelectorAll('.js-how-to-import-link');
importLinks.forEach((link) => {
const { modalTitle: title, modalMessage: modalHtmlMessage } = link.dataset;
link.addEventListener('click', (e) => {
e.preventDefault();
confirmAction('', {
modalHtmlMessage,
title,
hideCancel: true,
});
});
});
2021-11-11 11:23:49 +05:30
};
2017-09-10 17:25:29 +05:30
const bindEvents = () => {
const $newProjectForm = $('#new_project');
2021-11-18 22:05:49 +05:30
const $projectImportUrlUser = $('#project_import_url_user');
const $projectImportUrlPassword = $('#project_import_url_password');
const $projectImportUrlError = $('.js-import-url-error');
2022-04-04 11:22:00 +05:30
const $projectImportForm = $('form.js-project-import');
2018-03-17 18:26:18 +05:30
const $useTemplateBtn = $('.template-button > input');
const $changeTemplateBtn = $('.change-template');
2022-07-23 23:45:48 +05:30
const $projectImportUrl = document.querySelector('#project_import_url');
const $projectPath = document.querySelector('.tab-pane.active #project_path');
const $projectFieldsForm = document.querySelector('.project-fields-form');
const $selectedIcon = document.querySelector('.selected-icon');
const $selectedTemplateText = document.querySelector('.selected-template');
const $projectName = document.querySelector('.tab-pane.active #project_name');
const $projectTemplateButtons = document.querySelectorAll('.project-templates-buttons');
2017-09-10 17:25:29 +05:30
2022-04-04 11:22:00 +05:30
if ($newProjectForm.length !== 1 && $projectImportForm.length !== 1) {
2017-09-10 17:25:29 +05:30
return;
}
2021-11-11 11:23:49 +05:30
bindHowToImport();
2017-09-10 17:25:29 +05:30
2022-05-07 20:08:51 +05:30
$('.btn_import_gitlab_project').on('click contextmenu', () => {
2022-07-23 23:45:48 +05:30
const importGitlabProjectBtn = document.querySelector('.btn_import_gitlab_project');
const projectNamespaceId = document.querySelector('#project_namespace_id');
const { href: importHref } = importGitlabProjectBtn.dataset;
const newHref = `${importHref}?namespace_id=${projectNamespaceId.value}&name=${$projectName.value}&path=${$projectPath.value}`;
importGitlabProjectBtn.setAttribute('href', newHref);
2017-09-10 17:25:29 +05:30
});
2022-07-23 23:45:48 +05:30
const clearChildren = (el) => {
while (el.firstChild) el.removeChild(el.firstChild);
};
2018-03-17 18:26:18 +05:30
function chooseTemplate() {
2022-07-23 23:45:48 +05:30
$projectTemplateButtons.forEach((ptb) => ptb.classList.add('hidden'));
$projectFieldsForm.classList.add('selected');
2022-06-21 17:19:12 +05:30
2022-07-23 23:45:48 +05:30
clearChildren($selectedIcon);
2022-06-21 17:19:12 +05:30
2022-07-23 23:45:48 +05:30
const $selectedTemplate = this;
$selectedTemplate.checked = true;
2020-04-22 19:07:51 +05:30
2022-07-23 23:45:48 +05:30
const { value } = $selectedTemplate;
2021-02-22 17:27:13 +05:30
const selectedTemplate = DEFAULT_PROJECT_TEMPLATES[value];
2022-07-23 23:45:48 +05:30
$selectedTemplateText.textContent = selectedTemplate.text;
const clone = document.querySelector(selectedTemplate.icon).cloneNode(true);
clone.classList.add('d-block');
$selectedIcon.append(clone);
const $activeTabProjectName = document.querySelector('.tab-pane.active #project_name');
const $activeTabProjectPath = document.querySelector('.tab-pane.active #project_path');
2018-11-20 20:47:30 +05:30
$activeTabProjectName.focus();
2020-03-13 15:44:24 +05:30
setProjectNamePathHandlers($activeTabProjectName, $activeTabProjectPath);
2018-03-17 18:26:18 +05:30
}
2022-06-21 17:19:12 +05:30
function toggleActiveClassOnLabel(event) {
const $label = $(event.target).parent();
$label.toggleClass('active');
}
function chooseTemplateOnEnter(event) {
if (event.code === ENTER_KEY) {
chooseTemplate.call(this);
}
}
$useTemplateBtn.on('click', chooseTemplate);
$useTemplateBtn.on('focus focusout', toggleActiveClassOnLabel);
$useTemplateBtn.on('keypress', chooseTemplateOnEnter);
2018-03-17 18:26:18 +05:30
$changeTemplateBtn.on('click', () => {
2022-07-23 23:45:48 +05:30
$projectTemplateButtons.forEach((ptb) => ptb.classList.remove('hidden'));
$projectFieldsForm.classList.remove('selected');
2018-03-17 18:26:18 +05:30
$useTemplateBtn.prop('checked', false);
});
2017-09-10 17:25:29 +05:30
$newProjectForm.on('submit', () => {
$projectPath.val($projectPath.val().trim());
});
2022-04-04 11:22:00 +05:30
const updateUrlPathWarningVisibility = async () => {
const { success: isUrlValid, cancelled } = await validateImportCredentials(
2022-07-23 23:45:48 +05:30
$projectImportUrl.value,
2021-11-18 22:05:49 +05:30
$projectImportUrlUser.val(),
$projectImportUrlPassword.val(),
);
2022-04-04 11:22:00 +05:30
if (cancelled) {
return;
}
2022-07-23 23:45:48 +05:30
$projectImportUrl.classList.toggle(invalidInputClass, !isUrlValid);
2021-11-18 22:05:49 +05:30
$projectImportUrlError.toggleClass('hide', isUrlValid);
2022-04-04 11:22:00 +05:30
};
const debouncedUpdateUrlPathWarningVisibility = debounce(
updateUrlPathWarningVisibility,
DEFAULT_DEBOUNCE_AND_THROTTLE_MS,
);
2021-09-30 23:02:18 +05:30
let isProjectImportUrlDirty = false;
2022-07-23 23:45:48 +05:30
if ($projectImportUrl) {
$projectImportUrl.addEventListener('blur', () => {
isProjectImportUrlDirty = true;
debouncedUpdateUrlPathWarningVisibility();
});
$projectImportUrl.addEventListener('keyup', () => {
deriveProjectPathFromUrl($projectImportUrl);
});
}
2021-11-18 22:05:49 +05:30
[$projectImportUrl, $projectImportUrlUser, $projectImportUrlPassword].forEach(($f) => {
2022-07-23 23:45:48 +05:30
if (!$f) return false;
if ($f.on) {
return $f.on('input', () => {
if (isProjectImportUrlDirty) {
debouncedUpdateUrlPathWarningVisibility();
}
});
}
return $f.addEventListener('input', () => {
2021-11-18 22:05:49 +05:30
if (isProjectImportUrlDirty) {
2022-04-04 11:22:00 +05:30
debouncedUpdateUrlPathWarningVisibility();
2021-11-18 22:05:49 +05:30
}
});
});
2022-04-04 11:22:00 +05:30
$projectImportForm.on('submit', async (e) => {
e.preventDefault();
if (importCredentialsValidationPromise === null) {
// we didn't validate credentials yet
debouncedUpdateUrlPathWarningVisibility.cancel();
updateUrlPathWarningVisibility();
}
const submitBtn = $projectImportForm.find('input[type="submit"]');
submitBtn.disable();
await importCredentialsValidationPromise;
submitBtn.enable();
2021-11-18 22:05:49 +05:30
const $invalidFields = $projectImportForm.find(`.${invalidInputClass}`);
if ($invalidFields.length > 0) {
$invalidFields[0].focus();
2022-04-04 11:22:00 +05:30
} else {
// calling .submit() on HTMLFormElement does not trigger 'submit' event
// We are using this behavior to bypass this handler and avoid infinite loop
$projectImportForm[0].submit();
2021-09-30 23:02:18 +05:30
}
});
2018-11-20 20:47:30 +05:30
2019-07-31 22:56:46 +05:30
$('.js-import-git-toggle-button').on('click', () => {
2020-03-13 15:44:24 +05:30
setProjectNamePathHandlers(
2022-07-23 23:45:48 +05:30
document.querySelector('.tab-pane.active #project_name'),
document.querySelector('.tab-pane.active #project_path'),
2020-03-13 15:44:24 +05:30
);
2019-07-31 22:56:46 +05:30
});
2020-03-13 15:44:24 +05:30
setProjectNamePathHandlers($projectName, $projectPath);
2017-09-10 17:25:29 +05:30
};
export default {
bindEvents,
2022-08-13 15:12:31 +05:30
validateGroupNamespaceDropdown,
2017-09-10 17:25:29 +05:30
deriveProjectPathFromUrl,
2018-11-20 20:47:30 +05:30
onProjectNameChange,
2020-03-13 15:44:24 +05:30
onProjectPathChange,
2022-07-23 23:45:48 +05:30
onProjectNameChangeJq,
onProjectPathChangeJq,
2017-09-10 17:25:29 +05:30
};
2021-11-11 11:23:49 +05:30
export { bindHowToImport };