debian-mirror-gitlab/app/assets/javascripts/snippets/components/edit.vue

294 lines
8.8 KiB
Vue
Raw Normal View History

2020-04-22 19:07:51 +05:30
<script>
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
2021-03-11 19:13:27 +05:30
import eventHub from '~/blob/components/eventhub';
2020-10-24 23:57:45 +05:30
import { deprecatedCreateFlash as Flash } from '~/flash';
2020-11-24 15:15:51 +05:30
import { redirectTo, joinPaths } from '~/lib/utils/url_utility';
2021-03-11 19:13:27 +05:30
import { __, sprintf } from '~/locale';
2021-01-03 14:25:43 +05:30
import {
SNIPPET_MARK_EDIT_APP_START,
SNIPPET_MEASURE_BLOBS_CONTENT,
2021-01-29 00:20:46 +05:30
} from '~/performance/constants';
import { performanceMarkAndMeasure } from '~/performance/utils';
2021-03-11 19:13:27 +05:30
import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
import TitleField from '~/vue_shared/components/form/title.vue';
2020-04-22 19:07:51 +05:30
2021-01-29 00:20:46 +05:30
import { SNIPPET_CREATE_MUTATION_ERROR, SNIPPET_UPDATE_MUTATION_ERROR } from '../constants';
2021-03-11 19:13:27 +05:30
import { getSnippetMixin } from '../mixins/snippets';
import CreateSnippetMutation from '../mutations/createSnippet.mutation.graphql';
import UpdateSnippetMutation from '../mutations/updateSnippet.mutation.graphql';
2021-01-03 14:25:43 +05:30
import { markBlobPerformance } from '../utils/blob';
2021-03-11 19:13:27 +05:30
import { getErrorMessage } from '../utils/error';
2020-11-24 15:15:51 +05:30
2020-10-24 23:57:45 +05:30
import SnippetBlobActionsEdit from './snippet_blob_actions_edit.vue';
2020-04-22 19:07:51 +05:30
import SnippetDescriptionEdit from './snippet_description_edit.vue';
2021-03-11 19:13:27 +05:30
import SnippetVisibilityEdit from './snippet_visibility_edit.vue';
2020-04-22 19:07:51 +05:30
2021-01-03 14:25:43 +05:30
eventHub.$on(SNIPPET_MEASURE_BLOBS_CONTENT, markBlobPerformance);
2020-04-22 19:07:51 +05:30
export default {
components: {
SnippetDescriptionEdit,
SnippetVisibilityEdit,
2020-10-24 23:57:45 +05:30
SnippetBlobActionsEdit,
2020-04-22 19:07:51 +05:30
TitleField,
FormFooterActions,
2021-03-11 19:13:27 +05:30
CaptchaModal: () => import('~/captcha/captcha_modal.vue'),
2020-04-22 19:07:51 +05:30
GlButton,
GlLoadingIcon,
},
mixins: [getSnippetMixin],
2021-01-29 00:20:46 +05:30
inject: ['selectedLevel'],
2020-04-22 19:07:51 +05:30
props: {
markdownPreviewPath: {
type: String,
required: true,
},
markdownDocsPath: {
type: String,
required: true,
},
visibilityHelpLink: {
type: String,
default: '',
required: false,
},
projectPath: {
type: String,
default: '',
required: false,
},
},
data() {
return {
isUpdating: false,
2020-10-24 23:57:45 +05:30
actions: [],
2021-01-29 00:20:46 +05:30
snippet: {
title: '',
description: '',
visibilityLevel: this.selectedLevel,
},
2021-03-11 19:13:27 +05:30
captchaResponse: '',
needsCaptchaResponse: false,
captchaSiteKey: '',
spamLogId: '',
2020-04-22 19:07:51 +05:30
};
},
computed: {
2020-10-24 23:57:45 +05:30
hasBlobChanges() {
return this.actions.length > 0;
2020-07-28 23:09:34 +05:30
},
2021-03-11 19:13:27 +05:30
hasNoChanges() {
return (
this.actions.every(
(action) => !action.content && !action.filePath && !action.previousPath,
) &&
!this.snippet.title &&
!this.snippet.description
);
},
2020-10-24 23:57:45 +05:30
hasValidBlobs() {
2021-03-08 18:12:59 +05:30
return this.actions.every((x) => x.content);
2020-07-28 23:09:34 +05:30
},
2020-04-22 19:07:51 +05:30
updatePrevented() {
2020-10-24 23:57:45 +05:30
return this.snippet.title === '' || !this.hasValidBlobs || this.isUpdating;
2020-04-22 19:07:51 +05:30
},
isProjectSnippet() {
return Boolean(this.projectPath);
},
apiData() {
return {
id: this.snippet.id,
title: this.snippet.title,
description: this.snippet.description,
visibilityLevel: this.snippet.visibilityLevel,
2020-10-24 23:57:45 +05:30
blobActions: this.actions,
2021-03-11 19:13:27 +05:30
...(this.spamLogId && { spamLogId: this.spamLogId }),
...(this.captchaResponse && { captchaResponse: this.captchaResponse }),
2020-04-22 19:07:51 +05:30
};
},
saveButtonLabel() {
if (this.newSnippet) {
return __('Create snippet');
}
return this.isUpdating ? __('Saving') : __('Save changes');
},
cancelButtonHref() {
2020-05-24 23:13:21 +05:30
if (this.newSnippet) {
2020-11-24 15:15:51 +05:30
return joinPaths('/', gon.relative_url_root, this.projectPath, '-/snippets');
2020-05-24 23:13:21 +05:30
}
return this.snippet.webUrl;
2020-04-22 19:07:51 +05:30
},
},
2020-10-24 23:57:45 +05:30
beforeCreate() {
2021-01-03 14:25:43 +05:30
performanceMarkAndMeasure({ mark: SNIPPET_MARK_EDIT_APP_START });
2020-10-24 23:57:45 +05:30
},
2020-07-28 23:09:34 +05:30
created() {
window.addEventListener('beforeunload', this.onBeforeUnload);
},
destroyed() {
window.removeEventListener('beforeunload', this.onBeforeUnload);
},
2020-04-22 19:07:51 +05:30
methods: {
2020-07-28 23:09:34 +05:30
onBeforeUnload(e = {}) {
const returnValue = __('Are you sure you want to lose unsaved changes?');
2021-03-11 19:13:27 +05:30
if (!this.hasBlobChanges || this.hasNoChanges || this.isUpdating) return undefined;
2020-07-28 23:09:34 +05:30
Object.assign(e, { returnValue });
return returnValue;
},
2020-04-22 19:07:51 +05:30
flashAPIFailure(err) {
2020-06-23 00:09:42 +05:30
const defaultErrorMsg = this.newSnippet
? SNIPPET_CREATE_MUTATION_ERROR
: SNIPPET_UPDATE_MUTATION_ERROR;
Flash(sprintf(defaultErrorMsg, { err }));
this.isUpdating = false;
2020-04-22 19:07:51 +05:30
},
2020-06-23 00:09:42 +05:30
getAttachedFiles() {
const fileInputs = Array.from(this.$el.querySelectorAll('[name="files[]"]'));
2021-03-08 18:12:59 +05:30
return fileInputs.map((node) => node.value);
2020-06-23 00:09:42 +05:30
},
createMutation() {
return {
mutation: CreateSnippetMutation,
variables: {
input: {
...this.apiData,
uploadedFiles: this.getAttachedFiles(),
projectPath: this.projectPath,
},
},
};
},
updateMutation() {
return {
mutation: UpdateSnippetMutation,
variables: {
input: this.apiData,
},
};
},
2020-04-22 19:07:51 +05:30
handleFormSubmit() {
this.isUpdating = true;
this.$apollo
2020-06-23 00:09:42 +05:30
.mutate(this.newSnippet ? this.createMutation() : this.updateMutation())
2020-04-22 19:07:51 +05:30
.then(({ data }) => {
const baseObj = this.newSnippet ? data?.createSnippet : data?.updateSnippet;
2021-03-11 19:13:27 +05:30
if (baseObj.needsCaptchaResponse) {
// If we need a captcha response, start process for receiving captcha response.
// We will resubmit after the response is obtained.
this.requestCaptchaResponse(baseObj.captchaSiteKey, baseObj.spamLogId);
return;
}
2020-04-22 19:07:51 +05:30
const errors = baseObj?.errors;
if (errors.length) {
this.flashAPIFailure(errors[0]);
2020-06-23 00:09:42 +05:30
} else {
redirectTo(baseObj.snippet.webUrl);
2020-04-22 19:07:51 +05:30
}
})
2021-03-08 18:12:59 +05:30
.catch((e) => {
2021-03-11 19:13:27 +05:30
// eslint-disable-next-line no-console
console.error('[gitlab] unexpected error while updating snippet', e);
this.flashAPIFailure(getErrorMessage(e));
2020-04-22 19:07:51 +05:30
});
},
2020-10-24 23:57:45 +05:30
updateActions(actions) {
this.actions = actions;
},
2021-03-11 19:13:27 +05:30
/**
* Start process for getting captcha response from user
*
* @param captchaSiteKey Stored in data and used to display the captcha.
* @param spamLogId Stored in data and included when the form is re-submitted.
*/
requestCaptchaResponse(captchaSiteKey, spamLogId) {
this.captchaSiteKey = captchaSiteKey;
this.spamLogId = spamLogId;
this.needsCaptchaResponse = true;
},
/**
* Handle the captcha response from the user
*
* @param captchaResponse The captchaResponse value emitted from the modal.
*/
receivedCaptchaResponse(captchaResponse) {
this.needsCaptchaResponse = false;
this.captchaResponse = captchaResponse;
if (this.captchaResponse) {
// If the user solved the captcha resubmit the form.
this.handleFormSubmit();
} else {
// If the user didn't solve the captcha (e.g. they just closed the modal),
// finish the update and allow them to continue editing or manually resubmit the form.
this.isUpdating = false;
}
},
2020-04-22 19:07:51 +05:30
},
};
</script>
<template>
<form
2021-01-29 00:20:46 +05:30
class="snippet-form js-quick-submit common-note-form"
2020-04-22 19:07:51 +05:30
:data-snippet-type="isProjectSnippet ? 'project' : 'personal'"
2020-06-23 00:09:42 +05:30
data-testid="snippet-edit-form"
@submit.prevent="handleFormSubmit"
2020-04-22 19:07:51 +05:30
>
<gl-loading-icon
v-if="isLoading"
:label="__('Loading snippet')"
size="lg"
2020-11-24 15:15:51 +05:30
class="loading-animation prepend-top-20 gl-mb-6"
2020-04-22 19:07:51 +05:30
/>
<template v-else>
2021-03-11 19:13:27 +05:30
<captcha-modal
:captcha-site-key="captchaSiteKey"
:needs-captcha-response="needsCaptchaResponse"
@receivedCaptchaResponse="receivedCaptchaResponse"
/>
2020-05-24 23:13:21 +05:30
<title-field
2021-01-03 14:25:43 +05:30
id="snippet-title"
2020-05-24 23:13:21 +05:30
v-model="snippet.title"
2020-06-23 00:09:42 +05:30
data-qa-selector="snippet_title_field"
2020-05-24 23:13:21 +05:30
required
:autofocus="true"
/>
2020-04-22 19:07:51 +05:30
<snippet-description-edit
v-model="snippet.description"
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
/>
2020-10-24 23:57:45 +05:30
<snippet-blob-actions-edit :init-blobs="blobs" @actions="updateActions" />
2020-07-28 23:09:34 +05:30
2020-04-22 19:07:51 +05:30
<snippet-visibility-edit
v-model="snippet.visibilityLevel"
:help-link="visibilityHelpLink"
:is-project-snippet="isProjectSnippet"
/>
<form-footer-actions>
<template #prepend>
<gl-button
category="primary"
2020-06-23 00:09:42 +05:30
type="submit"
2020-04-22 19:07:51 +05:30
variant="success"
:disabled="updatePrevented"
2020-05-24 23:13:21 +05:30
data-qa-selector="submit_button"
2020-06-23 00:09:42 +05:30
data-testid="snippet-submit-btn"
2020-04-22 19:07:51 +05:30
>{{ saveButtonLabel }}</gl-button
>
</template>
<template #append>
2020-06-23 00:09:42 +05:30
<gl-button type="cancel" data-testid="snippet-cancel-btn" :href="cancelButtonHref">{{
2020-05-24 23:13:21 +05:30
__('Cancel')
}}</gl-button>
2020-04-22 19:07:51 +05:30
</template>
</form-footer-actions>
</template>
</form>
</template>