summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/work_items/components/work_item_comment_form.vue
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/work_items/components/work_item_comment_form.vue')
-rw-r--r--app/assets/javascripts/work_items/components/work_item_comment_form.vue228
1 files changed, 228 insertions, 0 deletions
diff --git a/app/assets/javascripts/work_items/components/work_item_comment_form.vue b/app/assets/javascripts/work_items/components/work_item_comment_form.vue
new file mode 100644
index 00000000000..65042f1431d
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/work_item_comment_form.vue
@@ -0,0 +1,228 @@
+<script>
+import { GlAvatar, GlButton } from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { getDraft, clearDraft, updateDraft } from '~/lib/utils/autosave';
+import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
+import { __, s__ } from '~/locale';
+import Tracking from '~/tracking';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
+import { getWorkItemQuery, getWorkItemNotesQuery } from '../utils';
+import createNoteMutation from '../graphql/create_work_item_note.mutation.graphql';
+import { i18n, TRACKING_CATEGORY_SHOW } from '../constants';
+import WorkItemNoteSignedOut from './work_item_note_signed_out.vue';
+import WorkItemCommentLocked from './work_item_comment_locked.vue';
+
+export default {
+ constantOptions: {
+ markdownDocsPath: helpPagePath('user/markdown'),
+ avatarUrl: window.gon.current_user_avatar_url,
+ },
+ components: {
+ GlAvatar,
+ GlButton,
+ MarkdownEditor,
+ WorkItemNoteSignedOut,
+ WorkItemCommentLocked,
+ },
+ mixins: [glFeatureFlagMixin(), Tracking.mixin()],
+ props: {
+ workItemId: {
+ type: String,
+ required: true,
+ },
+ fullPath: {
+ type: String,
+ required: true,
+ },
+ fetchByIid: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ queryVariables: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ workItem: {},
+ isEditing: false,
+ isSubmitting: false,
+ isSubmittingWithKeydown: false,
+ commentText: '',
+ };
+ },
+ apollo: {
+ workItem: {
+ query() {
+ return getWorkItemQuery(this.fetchByIid);
+ },
+ variables() {
+ return this.queryVariables;
+ },
+ update(data) {
+ return this.fetchByIid ? data.workspace.workItems.nodes[0] : data.workItem;
+ },
+ skip() {
+ return !this.queryVariables.id && !this.queryVariables.iid;
+ },
+ error() {
+ this.$emit('error', i18n.fetchError);
+ },
+ },
+ },
+ computed: {
+ signedIn() {
+ return Boolean(window.gon.current_user_id);
+ },
+ autosaveKey() {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ return `${this.workItemId}-comment`;
+ },
+ canEdit() {
+ // maybe this should use `NotePermissions.updateNote`, but if
+ // we don't have any notes yet, that permission isn't on WorkItem
+ return Boolean(this.workItem?.userPermissions?.updateWorkItem);
+ },
+ tracking() {
+ return {
+ category: TRACKING_CATEGORY_SHOW,
+ label: 'item_comment',
+ property: `type_${this.workItemType}`,
+ };
+ },
+ workItemType() {
+ return this.workItem?.workItemType?.name;
+ },
+ markdownPreviewPath() {
+ return `${gon.relative_url_root || ''}/${this.fullPath}/preview_markdown?target_type=${
+ this.workItemType
+ }`;
+ },
+ isProjectArchived() {
+ return this.workItem?.project?.archived;
+ },
+ },
+ methods: {
+ startEditing() {
+ this.isEditing = true;
+ this.commentText = getDraft(this.autosaveKey) || '';
+ },
+ async cancelEditing() {
+ if (this.commentText) {
+ const msg = s__('WorkItem|Are you sure you want to cancel editing?');
+
+ const confirmed = await confirmAction(msg, {
+ primaryBtnText: __('Discard changes'),
+ cancelBtnText: __('Continue editing'),
+ });
+
+ if (!confirmed) {
+ return;
+ }
+ }
+
+ this.isEditing = false;
+ clearDraft(this.autosaveKey);
+ },
+ async updateWorkItem(event = {}) {
+ const { key } = event;
+
+ if (key) {
+ this.isSubmittingWithKeydown = true;
+ }
+
+ this.isSubmitting = true;
+
+ try {
+ this.track('add_work_item_comment');
+
+ const {
+ data: { createNote },
+ } = await this.$apollo.mutate({
+ mutation: createNoteMutation,
+ variables: {
+ input: {
+ noteableId: this.workItem.id,
+ body: this.commentText,
+ },
+ },
+ });
+
+ if (createNote.errors?.length) {
+ throw new Error(createNote.errors[0]);
+ }
+
+ const client = this.$apollo.provider.defaultClient;
+ client.refetchQueries({
+ include: [getWorkItemNotesQuery(this.fetchByIid)],
+ });
+
+ this.isEditing = false;
+ clearDraft(this.autosaveKey);
+ } catch (error) {
+ this.$emit('error', error.message);
+ Sentry.captureException(error);
+ }
+
+ this.isSubmitting = false;
+ },
+ setCommentText(newText) {
+ this.commentText = newText;
+ updateDraft(this.autosaveKey, this.commentText);
+ },
+ },
+};
+</script>
+
+<template>
+ <li class="timeline-entry">
+ <work-item-note-signed-out v-if="!signedIn" />
+ <work-item-comment-locked
+ v-else-if="!canEdit"
+ :work-item-type="workItemType"
+ :is-project-archived="isProjectArchived"
+ />
+ <div v-else class="gl-display-flex gl-align-items-flex-start gl-flex-wrap-nowrap">
+ <gl-avatar :src="$options.constantOptions.avatarUrl" :size="32" class="gl-mr-3" />
+ <form v-if="isEditing" class="common-note-form gfm-form js-main-target-form gl-flex-grow-1">
+ <markdown-editor
+ class="gl-mb-3"
+ :value="commentText"
+ :render-markdown-path="markdownPreviewPath"
+ :markdown-docs-path="$options.constantOptions.markdownDocsPath"
+ :form-field-aria-label="__('Add a comment')"
+ :form-field-placeholder="__('Write a comment or drag your files hereā€¦')"
+ form-field-id="work-item-add-comment"
+ form-field-name="work-item-add-comment"
+ enable-autocomplete
+ autofocus
+ use-bottom-toolbar
+ @input="setCommentText"
+ @keydown.meta.enter="updateWorkItem"
+ @keydown.ctrl.enter="updateWorkItem"
+ @keydown.esc="cancelEditing"
+ />
+ <gl-button
+ category="primary"
+ variant="confirm"
+ :loading="isSubmitting"
+ @click="updateWorkItem"
+ >{{ __('Comment') }}
+ </gl-button>
+ <gl-button category="tertiary" class="gl-ml-3" @click="cancelEditing"
+ >{{ __('Cancel') }}
+ </gl-button>
+ </form>
+ <gl-button
+ v-else
+ class="gl-flex-grow-1 gl-justify-content-start! gl-text-secondary!"
+ @click="startEditing"
+ >{{ __('Add a comment') }}</gl-button
+ >
+ </div>
+ </li>
+</template>