diff options
Diffstat (limited to 'app/assets/javascripts/snippets/components')
3 files changed, 219 insertions, 8 deletions
diff --git a/app/assets/javascripts/snippets/components/edit.vue b/app/assets/javascripts/snippets/components/edit.vue new file mode 100644 index 00000000000..2185b1d67e4 --- /dev/null +++ b/app/assets/javascripts/snippets/components/edit.vue @@ -0,0 +1,211 @@ +<script> +import { GlButton, GlLoadingIcon } from '@gitlab/ui'; + +import Flash from '~/flash'; +import { __, sprintf } from '~/locale'; +import axios from '~/lib/utils/axios_utils'; +import TitleField from '~/vue_shared/components/form/title.vue'; +import { getBaseURL, joinPaths, redirectTo } from '~/lib/utils/url_utility'; +import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue'; + +import UpdateSnippetMutation from '../mutations/updateSnippet.mutation.graphql'; +import CreateSnippetMutation from '../mutations/createSnippet.mutation.graphql'; +import { getSnippetMixin } from '../mixins/snippets'; +import { SNIPPET_VISIBILITY_PRIVATE } from '../constants'; +import SnippetBlobEdit from './snippet_blob_edit.vue'; +import SnippetVisibilityEdit from './snippet_visibility_edit.vue'; +import SnippetDescriptionEdit from './snippet_description_edit.vue'; + +export default { + components: { + SnippetDescriptionEdit, + SnippetVisibilityEdit, + SnippetBlobEdit, + TitleField, + FormFooterActions, + GlButton, + GlLoadingIcon, + }, + mixins: [getSnippetMixin], + 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 { + blob: {}, + fileName: '', + content: '', + isContentLoading: true, + isUpdating: false, + newSnippet: false, + }; + }, + computed: { + updatePrevented() { + return this.snippet.title === '' || this.content === '' || this.isUpdating; + }, + isProjectSnippet() { + return Boolean(this.projectPath); + }, + apiData() { + return { + id: this.snippet.id, + title: this.snippet.title, + description: this.snippet.description, + visibilityLevel: this.snippet.visibilityLevel, + fileName: this.fileName, + content: this.content, + }; + }, + saveButtonLabel() { + if (this.newSnippet) { + return __('Create snippet'); + } + return this.isUpdating ? __('Saving') : __('Save changes'); + }, + cancelButtonHref() { + return this.projectPath ? `/${this.projectPath}/snippets` : `/snippets`; + }, + titleFieldId() { + return `${this.isProjectSnippet ? 'project' : 'personal'}_snippet_title`; + }, + descriptionFieldId() { + return `${this.isProjectSnippet ? 'project' : 'personal'}_snippet_description`; + }, + }, + methods: { + updateFileName(newName) { + this.fileName = newName; + }, + flashAPIFailure(err) { + Flash(sprintf(__("Can't update snippet: %{err}"), { err })); + }, + onNewSnippetFetched() { + this.newSnippet = true; + this.snippet = this.$options.newSnippetSchema; + this.blob = this.snippet.blob; + this.isContentLoading = false; + }, + onExistingSnippetFetched() { + this.newSnippet = false; + const { blob } = this.snippet; + this.blob = blob; + this.fileName = blob.name; + const baseUrl = getBaseURL(); + const url = joinPaths(baseUrl, blob.rawPath); + + axios + .get(url) + .then(res => { + this.content = res.data; + this.isContentLoading = false; + }) + .catch(e => this.flashAPIFailure(e)); + }, + onSnippetFetch(snippetRes) { + if (snippetRes.data.snippets.edges.length === 0) { + this.onNewSnippetFetched(); + } else { + this.onExistingSnippetFetched(); + } + }, + handleFormSubmit() { + this.isUpdating = true; + this.$apollo + .mutate({ + mutation: this.newSnippet ? CreateSnippetMutation : UpdateSnippetMutation, + variables: { + input: { + ...this.apiData, + projectPath: this.newSnippet ? this.projectPath : undefined, + }, + }, + }) + .then(({ data }) => { + const baseObj = this.newSnippet ? data?.createSnippet : data?.updateSnippet; + + const errors = baseObj?.errors; + if (errors.length) { + this.flashAPIFailure(errors[0]); + } + redirectTo(baseObj.snippet.webUrl); + }) + .catch(e => { + this.isUpdating = false; + this.flashAPIFailure(e); + }); + }, + }, + newSnippetSchema: { + title: '', + description: '', + visibilityLevel: SNIPPET_VISIBILITY_PRIVATE, + blob: {}, + }, +}; +</script> +<template> + <form + class="snippet-form js-requires-input js-quick-submit common-note-form" + :data-snippet-type="isProjectSnippet ? 'project' : 'personal'" + > + <gl-loading-icon + v-if="isLoading" + :label="__('Loading snippet')" + size="lg" + class="loading-animation prepend-top-20 append-bottom-20" + /> + <template v-else> + <title-field :id="titleFieldId" v-model="snippet.title" required :autofocus="true" /> + <snippet-description-edit + :id="descriptionFieldId" + v-model="snippet.description" + :markdown-preview-path="markdownPreviewPath" + :markdown-docs-path="markdownDocsPath" + /> + <snippet-blob-edit + v-model="content" + :file-name="fileName" + :is-loading="isContentLoading" + @name-change="updateFileName" + /> + <snippet-visibility-edit + v-model="snippet.visibilityLevel" + :help-link="visibilityHelpLink" + :is-project-snippet="isProjectSnippet" + /> + <form-footer-actions> + <template #prepend> + <gl-button + type="submit" + category="primary" + variant="success" + :disabled="updatePrevented" + @click="handleFormSubmit" + >{{ saveButtonLabel }}</gl-button + > + </template> + <template #append> + <gl-button :href="cancelButtonHref">{{ __('Cancel') }}</gl-button> + </template> + </form-footer-actions> + </template> + </form> +</template> diff --git a/app/assets/javascripts/snippets/components/snippet_description_edit.vue b/app/assets/javascripts/snippets/components/snippet_description_edit.vue index 68810f8ab3f..6f3a86be8d7 100644 --- a/app/assets/javascripts/snippets/components/snippet_description_edit.vue +++ b/app/assets/javascripts/snippets/components/snippet_description_edit.vue @@ -50,7 +50,6 @@ export default { :markdown-docs-path="markdownDocsPath" > <textarea - id="snippet-description" slot="textarea" class="note-textarea js-gfm-input js-autosize markdown-area qa-description-textarea" @@ -59,6 +58,7 @@ export default { :value="value" :aria-label="__('Description')" :placeholder="__('Write a comment or drag your files hereā¦')" + v-bind="$attrs" @input="$emit('input', $event.target.value)" > </textarea> diff --git a/app/assets/javascripts/snippets/components/snippet_header.vue b/app/assets/javascripts/snippets/components/snippet_header.vue index 79b191cb25a..30a23b51bc4 100644 --- a/app/assets/javascripts/snippets/components/snippet_header.vue +++ b/app/assets/javascripts/snippets/components/snippet_header.vue @@ -9,7 +9,7 @@ import { GlLoadingIcon, GlDropdown, GlDropdownItem, - GlNewButton, + GlButton, } from '@gitlab/ui'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; @@ -28,7 +28,7 @@ export default { GlDropdown, GlDropdownItem, TimeAgoTooltip, - GlNewButton, + GlButton, }, apollo: { canCreateSnippet: { @@ -186,7 +186,7 @@ export default { <div class="detail-page-header-actions"> <div class="d-none d-sm-flex"> <template v-for="(action, index) in personalSnippetActions"> - <gl-new-button + <gl-button v-if="action.condition" :key="index" :disabled="action.disabled" @@ -197,7 +197,7 @@ export default { @click="action.click ? action.click() : undefined" > {{ action.text }} - </gl-new-button> + </gl-button> </template> </div> <div class="d-block d-sm-none dropdown"> @@ -227,8 +227,8 @@ export default { </gl-sprintf> <template #modal-footer> - <gl-new-button @click="closeDeleteModal">{{ __('Cancel') }}</gl-new-button> - <gl-new-button + <gl-button @click="closeDeleteModal">{{ __('Cancel') }}</gl-button> + <gl-button variant="danger" category="primary" :disabled="isDeleting" @@ -237,7 +237,7 @@ export default { > <gl-loading-icon v-if="isDeleting" inline /> {{ __('Delete snippet') }} - </gl-new-button> + </gl-button> </template> </gl-modal> </div> |