gitlab-org--gitlab-foss/app/assets/javascripts/issuable/issuable_form.js

209 lines
6.3 KiB
JavaScript

import $ from 'jquery';
import Pikaday from 'pikaday';
import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
import Autosave from '~/autosave';
import { parsePikadayDate, pikadayToString } from '~/lib/utils/datetime_utility';
import { queryToObject, objectToQuery } from '~/lib/utils/url_utility';
import UsersSelect from '~/users_select';
import ZenMode from '~/zen_mode';
const MR_SOURCE_BRANCH = 'merge_request[source_branch]';
const MR_TARGET_BRANCH = 'merge_request[target_branch]';
const DATA_ISSUES_NEW_PATH = 'data-new-issue-path';
function organizeQuery(obj, isFallbackKey = false) {
if (!obj[MR_SOURCE_BRANCH] && !obj[MR_TARGET_BRANCH]) {
return obj;
}
if (isFallbackKey) {
return {
[MR_SOURCE_BRANCH]: obj[MR_SOURCE_BRANCH],
};
}
return {
[MR_SOURCE_BRANCH]: obj[MR_SOURCE_BRANCH],
[MR_TARGET_BRANCH]: obj[MR_TARGET_BRANCH],
};
}
function format(searchTerm, isFallbackKey = false) {
const queryObject = queryToObject(searchTerm, { legacySpacesDecode: true });
const organizeQueryObject = organizeQuery(queryObject, isFallbackKey);
const formattedQuery = objectToQuery(organizeQueryObject);
return formattedQuery;
}
function getSearchTerm(newIssuePath) {
const { search, pathname } = document.location;
return newIssuePath === pathname ? '' : format(search);
}
function getFallbackKey() {
const searchTerm = format(document.location.search, true);
return ['autosave', document.location.pathname, searchTerm].join('/');
}
export default class IssuableForm {
static addAutosave(map, id, $input, searchTerm, fallbackKey) {
if ($input.length) {
map.set(
id,
new Autosave($input, [document.location.pathname, searchTerm, id], `${fallbackKey}=${id}`),
);
}
}
constructor(form) {
if (form.length === 0) {
return;
}
this.form = form;
this.toggleWip = this.toggleWip.bind(this);
this.renderWipExplanation = this.renderWipExplanation.bind(this);
this.resetAutosave = this.resetAutosave.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
// prettier-ignore
this.draftRegex = new RegExp(
'^\\s*(' + // Line start, then any amount of leading whitespace
'\\[draft\\]\\s*' + // [Draft] and any following whitespace
'|draft:\\s*' + // Draft: and any following whitespace
'|\\(draft\\)\\s*' + // (Draft) and any following whitespace
')+' + // At least one repeated match of the preceding parenthetical
'\\s*', // Any amount of trailing whitespace
'i', // Match any case(s)
);
this.gfmAutoComplete = new GfmAutoComplete(
gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources,
).setup();
this.usersSelect = new UsersSelect();
this.reviewersSelect = new UsersSelect(undefined, '.js-reviewer-search');
this.zenMode = new ZenMode();
this.searchTerm = getSearchTerm(form[0].getAttribute(DATA_ISSUES_NEW_PATH));
this.fallbackKey = getFallbackKey();
this.titleField = this.form.find('input[name*="[title]"]');
this.descriptionField = this.form.find('textarea[name*="[description]"]');
if (!(this.titleField.length && this.descriptionField.length)) {
return;
}
this.autosaves = this.initAutosave();
this.form.on('submit', this.handleSubmit);
this.form.on('click', '.btn-cancel, .js-reset-autosave', this.resetAutosave);
this.form.find('.js-unwrap-on-load').unwrap();
this.initWip();
const $issuableDueDate = $('#issuable-due-date');
if ($issuableDueDate.length) {
const calendar = new Pikaday({
field: $issuableDueDate.get(0),
theme: 'gitlab-theme animate-picker',
format: 'yyyy-mm-dd',
container: $issuableDueDate.parent().get(0),
parse: (dateString) => parsePikadayDate(dateString),
toString: (date) => pikadayToString(date),
onSelect: (dateText) => {
$issuableDueDate.val(calendar.toString(dateText));
if (this.autosaves.has('due_date')) this.autosaves.get('due_date').save();
},
firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate($issuableDueDate.val()));
}
}
initAutosave() {
const autosaveMap = new Map();
IssuableForm.addAutosave(
autosaveMap,
'title',
this.form.find('input[name*="[title]"]'),
this.searchTerm,
this.fallbackKey,
);
IssuableForm.addAutosave(
autosaveMap,
'description',
this.form.find('textarea[name*="[description]"]'),
this.searchTerm,
this.fallbackKey,
);
IssuableForm.addAutosave(
autosaveMap,
'confidential',
this.form.find('input:checkbox[name*="[confidential]"]'),
this.searchTerm,
this.fallbackKey,
);
IssuableForm.addAutosave(
autosaveMap,
'due_date',
this.form.find('input[name*="[due_date]"]'),
this.searchTerm,
this.fallbackKey,
);
return autosaveMap;
}
handleSubmit() {
return this.resetAutosave();
}
resetAutosave() {
this.autosaves.forEach((autosaveItem) => {
autosaveItem?.reset();
});
}
initWip() {
this.$wipExplanation = this.form.find('.js-wip-explanation');
this.$noWipExplanation = this.form.find('.js-no-wip-explanation');
if (!(this.$wipExplanation.length && this.$noWipExplanation.length)) {
return undefined;
}
this.form.on('click', '.js-toggle-wip', this.toggleWip);
this.titleField.on('keyup blur', this.renderWipExplanation);
return this.renderWipExplanation();
}
workInProgress() {
return this.draftRegex.test(this.titleField.val());
}
renderWipExplanation() {
if (this.workInProgress()) {
// These strings are not "translatable" (the code is hard-coded to look for them)
this.$wipExplanation.find('code')[0].textContent =
'Draft'; /* eslint-disable-line @gitlab/require-i18n-strings */
this.$wipExplanation.show();
return this.$noWipExplanation.hide();
}
this.$wipExplanation.hide();
return this.$noWipExplanation.show();
}
toggleWip(event) {
event.preventDefault();
if (this.workInProgress()) {
this.removeWip();
} else {
this.addWip();
}
return this.renderWipExplanation();
}
removeWip() {
return this.titleField.val(this.titleField.val().replace(this.draftRegex, ''));
}
addWip() {
this.titleField.val(`Draft: ${this.titleField.val()}`);
}
}