Read about our upcoming Code of Conduct on this issue

noteable_discussion.vue 10 KB
Newer Older
1
<script>
Fatih Acet's avatar
Fatih Acet committed
2
import { mapActions, mapGetters } from 'vuex';
3
import { GlTooltipDirective } from '@gitlab/ui';
4
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
5
import { s__, __ } from '~/locale';
6
import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave';
Fatih Acet's avatar
Fatih Acet committed
7
import icon from '~/vue_shared/components/icon.vue';
8
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
Fatih Acet's avatar
Fatih Acet committed
9
10
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
11
import diffDiscussionHeader from './diff_discussion_header.vue';
Fatih Acet's avatar
Fatih Acet committed
12
13
14
15
16
import noteSignedOutWidget from './note_signed_out_widget.vue';
import noteForm from './note_form.vue';
import diffWithNote from './diff_with_note.vue';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
17
import discussionNavigation from '../mixins/discussion_navigation';
18
import eventHub from '../event_hub';
19
import DiscussionNotes from './discussion_notes.vue';
20
import DiscussionActions from './discussion_actions.vue';
21

Fatih Acet's avatar
Fatih Acet committed
22
export default {
Felipe Artur's avatar
Felipe Artur committed
23
  name: 'NoteableDiscussion',
Fatih Acet's avatar
Fatih Acet committed
24
  components: {
Fatih Acet's avatar
Fatih Acet committed
25
    icon,
Fatih Acet's avatar
Fatih Acet committed
26
    userAvatarLink,
27
    diffDiscussionHeader,
Fatih Acet's avatar
Fatih Acet committed
28
29
    noteSignedOutWidget,
    noteForm,
30
    DraftNote: () => import('ee_component/batch_comments/components/draft_note.vue'),
31
    TimelineEntryItem,
32
    DiscussionNotes,
33
    DiscussionActions,
Fatih Acet's avatar
Fatih Acet committed
34
35
  },
  directives: {
36
    GlTooltip: GlTooltipDirective,
Fatih Acet's avatar
Fatih Acet committed
37
  },
38
  mixins: [noteable, resolvable, discussionNavigation, diffLineNoteFormMixin],
Fatih Acet's avatar
Fatih Acet committed
39
  props: {
Felipe Artur's avatar
Felipe Artur committed
40
    discussion: {
Fatih Acet's avatar
Fatih Acet committed
41
42
43
      type: Object,
      required: true,
    },
44
45
46
47
48
    line: {
      type: Object,
      required: false,
      default: null,
    },
Felipe Artur's avatar
Felipe Artur committed
49
50
51
52
53
54
55
56
57
58
    renderDiffFile: {
      type: Boolean,
      required: false,
      default: true,
    },
    alwaysExpanded: {
      type: Boolean,
      required: false,
      default: false,
    },
59
60
61
62
63
    discussionsByDiffOrder: {
      type: Boolean,
      required: false,
      default: false,
    },
64
65
66
67
68
    helpPagePath: {
      type: String,
      required: false,
      default: '',
    },
Fatih Acet's avatar
Fatih Acet committed
69
70
71
72
73
74
75
76
77
78
  },
  data() {
    return {
      isReplying: false,
      isResolving: false,
      resolveAsThread: true,
    };
  },
  computed: {
    ...mapGetters([
79
      'convertedDisscussionIds',
Fatih Acet's avatar
Fatih Acet committed
80
      'getNoteableData',
81
      'userCanReply',
82
      'nextUnresolvedDiscussionId',
83
84
      'unresolvedDiscussionsCount',
      'hasUnresolvedDiscussions',
85
      'showJumpToNextDiscussion',
86
      'getUserData',
87
      'getDiscussion',
Fatih Acet's avatar
Fatih Acet committed
88
    ]),
89
90
91
    currentUser() {
      return this.getUserData;
    },
92
93
94
    autosaveKey() {
      return getDiscussionReplyKey(this.firstNote.noteable_type, this.discussion.id);
    },
Fatih Acet's avatar
Fatih Acet committed
95
96
97
    newNotePath() {
      return this.getNoteableData.create_note_path;
    },
98
    firstNote() {
Fatih Acet's avatar
Fatih Acet committed
99
100
      return this.discussion.notes.slice(0, 1)[0];
    },
101
    shouldShowJumpToNextDiscussion() {
102
      return this.showJumpToNextDiscussion(this.discussionsByDiffOrder ? 'diff' : 'discussion');
103
    },
Felipe Artur's avatar
Felipe Artur committed
104
    shouldRenderDiffs() {
105
      return this.discussion.diff_discussion && this.renderDiffFile;
Fatih Acet's avatar
Fatih Acet committed
106
    },
Fatih Acet's avatar
Fatih Acet committed
107
    shouldGroupReplies() {
108
      return !this.shouldRenderDiffs;
Fatih Acet's avatar
Fatih Acet committed
109
    },
Fatih Acet's avatar
Fatih Acet committed
110
    wrapperComponent() {
Felipe Artur's avatar
Felipe Artur committed
111
112
113
114
      return this.shouldRenderDiffs ? diffWithNote : 'div';
    },
    wrapperComponentProps() {
      if (this.shouldRenderDiffs) {
Phil Hughes's avatar
Phil Hughes committed
115
        return { discussion: this.discussion };
Felipe Artur's avatar
Felipe Artur committed
116
117
118
      }

      return {};
Fatih Acet's avatar
Fatih Acet committed
119
    },
120
121
122
123
124
    isExpanded() {
      return this.discussion.expanded || this.alwaysExpanded;
    },
    shouldHideDiscussionBody() {
      return this.shouldRenderDiffs && !this.isExpanded;
Fatih Acet's avatar
Fatih Acet committed
125
    },
126
    diffLine() {
127
128
129
130
      if (this.line) {
        return this.line;
      }

131
132
133
134
      if (this.discussion.diff_discussion && this.discussion.truncated_diff_lines) {
        return this.discussion.truncated_diff_lines.slice(-1)[0];
      }

135
      return null;
136
    },
137
    resolveWithIssuePath() {
138
      return !this.discussionResolved ? this.discussion.resolve_with_issue_path : '';
139
    },
Fatih Acet's avatar
Fatih Acet committed
140
  },
141
142
143
144
145
146
  created() {
    eventHub.$on('startReplying', this.onStartReplying);
  },
  beforeDestroy() {
    eventHub.$off('startReplying', this.onStartReplying);
  },
Fatih Acet's avatar
Fatih Acet committed
147
148
149
150
151
  methods: {
    ...mapActions([
      'saveNote',
      'removePlaceholderNotes',
      'toggleResolveNote',
Felipe Artur's avatar
Felipe Artur committed
152
      'expandDiscussion',
153
      'removeConvertedDiscussion',
Fatih Acet's avatar
Fatih Acet committed
154
155
156
157
    ]),
    showReplyForm() {
      this.isReplying = true;
    },
158
159
160
161
    cancelReplyForm(shouldConfirm, isDirty) {
      if (shouldConfirm && isDirty) {
        const msg = s__('Notes|Are you sure you want to cancel creating this comment?');

Fatih Acet's avatar
Fatih Acet committed
162
        // eslint-disable-next-line no-alert
163
        if (!window.confirm(msg)) {
Fatih Acet's avatar
Fatih Acet committed
164
          return;
165
        }
Fatih Acet's avatar
Fatih Acet committed
166
      }
167

168
169
170
171
      if (this.convertedDisscussionIds.includes(this.discussion.id)) {
        this.removeConvertedDiscussion(this.discussion.id);
      }

172
      this.isReplying = false;
173
      clearDraft(this.autosaveKey);
Fatih Acet's avatar
Fatih Acet committed
174
175
    },
    saveReply(noteText, form, callback) {
176
177
178
179
180
      if (!noteText) {
        this.cancelReplyForm();
        callback();
        return;
      }
Felipe Artur's avatar
Felipe Artur committed
181
182
183
184
185
186
      const postData = {
        in_reply_to_discussion_id: this.discussion.reply_id,
        target_type: this.getNoteableData.targetType,
        note: { note: noteText },
      };

187
188
189
190
      if (this.convertedDisscussionIds.includes(this.discussion.id)) {
        postData.return_discussion = true;
      }

Felipe Artur's avatar
Felipe Artur committed
191
192
193
194
      if (this.discussion.for_commit) {
        postData.note_project_id = this.discussion.project_id;
      }

Fatih Acet's avatar
Fatih Acet committed
195
196
197
      const replyData = {
        endpoint: this.newNotePath,
        flashContainer: this.$el,
Felipe Artur's avatar
Felipe Artur committed
198
        data: postData,
Fatih Acet's avatar
Fatih Acet committed
199
      };
200

Felipe Artur's avatar
Felipe Artur committed
201
      this.isReplying = false;
Fatih Acet's avatar
Fatih Acet committed
202
203
      this.saveNote(replyData)
        .then(() => {
204
          clearDraft(this.autosaveKey);
Fatih Acet's avatar
Fatih Acet committed
205
206
207
208
209
210
          callback();
        })
        .catch(err => {
          this.removePlaceholderNotes();
          this.isReplying = true;
          this.$nextTick(() => {
211
212
213
            const msg = __(
              'Your comment could not be submitted! Please check your network connection and try again.',
            );
Fatih Acet's avatar
Fatih Acet committed
214
215
216
            Flash(msg, 'alert', this.$el);
            this.$refs.noteForm.note = noteText;
            callback(err);
217
          });
Fatih Acet's avatar
Fatih Acet committed
218
219
        });
    },
Felipe Artur's avatar
Felipe Artur committed
220
    jumpToNextDiscussion() {
Tim Zallmann's avatar
Tim Zallmann committed
221
222
223
224
      const nextId = this.nextUnresolvedDiscussionId(
        this.discussion.id,
        this.discussionsByDiffOrder,
      );
225
      const nextDiscussion = this.getDiscussion(nextId);
226

227
      this.jumpToDiscussion(nextDiscussion);
228
    },
Tim Zallmann's avatar
Tim Zallmann committed
229
    deleteNoteHandler(note) {
230
      this.$emit('noteDeleted', this.discussion, note);
Tim Zallmann's avatar
Tim Zallmann committed
231
    },
232
233
234
235
236
    onStartReplying(discussionId) {
      if (this.discussion.id === discussionId) {
        this.showReplyForm();
      }
    },
Fatih Acet's avatar
Fatih Acet committed
237
238
  },
};
239
240
241
</script>

<template>
242
  <timeline-entry-item class="note note-discussion">
243
    <div class="timeline-content">
244
245
246
247
248
      <div
        :data-discussion-id="discussion.id"
        class="discussion js-discussion-container"
        data-qa-selector="discussion_content"
      >
249
        <diff-discussion-header v-if="shouldRenderDiffs" :discussion="discussion" />
250
        <div v-if="!shouldHideDiscussionBody" class="discussion-body">
251
252
253
254
255
          <component
            :is="wrapperComponent"
            v-bind="wrapperComponentProps"
            class="card discussion-wrapper"
          >
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
            <discussion-notes
              :discussion="discussion"
              :diff-line="diffLine"
              :help-page-path="helpPagePath"
              :is-expanded="isExpanded"
              :line="line"
              :should-group-replies="shouldGroupReplies"
              @startReplying="showReplyForm"
              @deleteNote="deleteNoteHandler"
            >
              <slot slot="avatar-badge" name="avatar-badge"></slot>
              <template #footer="{ showReplies }">
                <draft-note
                  v-if="showDraft(discussion.reply_id)"
                  :key="`draft_${discussion.id}`"
                  :draft="draftForDiscussion(discussion.reply_id)"
                />
                <div
                  v-else-if="showReplies"
                  :class="{ 'is-replying': isReplying }"
276
                  class="discussion-reply-holder clearfix"
277
                >
278
                  <user-avatar-link
279
                    v-if="!isReplying && userCanReply"
280
281
282
283
284
285
                    :link-href="currentUser.path"
                    :img-src="currentUser.avatar_url"
                    :img-alt="currentUser.name"
                    :img-size="40"
                    class="d-none d-sm-block"
                  />
286
287
288
289
290
291
292
293
294
295
                  <discussion-actions
                    v-if="!isReplying && userCanReply"
                    :discussion="discussion"
                    :is-resolving="isResolving"
                    :resolve-button-title="resolveButtonTitle"
                    :resolve-with-issue-path="resolveWithIssuePath"
                    :should-show-jump-to-next-discussion="shouldShowJumpToNextDiscussion"
                    @showReplyForm="showReplyForm"
                    @resolve="resolveHandler"
                    @jumpToNextDiscussion="jumpToNextDiscussion"
296
                  />
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
                  <div v-if="isReplying" class="avatar-note-form-holder">
                    <user-avatar-link
                      v-if="currentUser"
                      :link-href="currentUser.path"
                      :img-src="currentUser.avatar_url"
                      :img-alt="currentUser.name"
                      :img-size="40"
                      class="d-none d-sm-block"
                    />
                    <note-form
                      ref="noteForm"
                      :discussion="discussion"
                      :is-editing="false"
                      :line="diffLine"
                      save-button-title="Comment"
                      :autosave-key="autosaveKey"
                      @handleFormUpdateAddToReview="addReplyToReview"
                      @handleFormUpdate="saveReply"
                      @cancelForm="cancelReplyForm"
                    />
                  </div>
318
319
320
321
                  <note-signed-out-widget v-if="!userCanReply" />
                </div>
              </template>
            </discussion-notes>
322
          </component>
323
324
325
        </div>
      </div>
    </div>
326
  </timeline-entry-item>
327
</template>