noteable_note.vue 7.75 KB
Newer Older
1
<script>
Fatih Acet's avatar
Fatih Acet committed
2 3 4
import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex';
import { escape } from 'underscore';
5 6
import { truncateSha } from '~/lib/utils/text_utility';
import { s__, sprintf } from '~/locale';
7
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
Fatih Acet's avatar
Fatih Acet committed
8 9 10 11 12 13 14 15
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue';
import noteActions from './note_actions.vue';
import noteBody from './note_body.vue';
import eventHub from '../event_hub';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
16

Fatih Acet's avatar
Fatih Acet committed
17
export default {
Felipe Artur's avatar
Felipe Artur committed
18
  name: 'NoteableNote',
Fatih Acet's avatar
Fatih Acet committed
19 20 21 22 23
  components: {
    userAvatarLink,
    noteHeader,
    noteActions,
    noteBody,
24
    TimelineEntryItem,
Fatih Acet's avatar
Fatih Acet committed
25 26 27 28 29 30
  },
  mixins: [noteable, resolvable],
  props: {
    note: {
      type: Object,
      required: true,
Filipa Lacerda's avatar
Filipa Lacerda committed
31
    },
32 33 34 35 36 37 38 39 40 41
    line: {
      type: Object,
      required: false,
      default: null,
    },
    helpPagePath: {
      type: String,
      required: false,
      default: '',
    },
42 43 44 45 46
    commit: {
      type: Object,
      required: false,
      default: () => null,
    },
Fatih Acet's avatar
Fatih Acet committed
47 48 49 50 51 52 53 54 55 56
  },
  data() {
    return {
      isEditing: false,
      isDeleting: false,
      isRequesting: false,
      isResolving: false,
    };
  },
  computed: {
Felipe Artur's avatar
Felipe Artur committed
57
    ...mapGetters(['targetNoteHash', 'getNoteableData', 'getUserData']),
Fatih Acet's avatar
Fatih Acet committed
58 59
    author() {
      return this.note.author;
60
    },
Fatih Acet's avatar
Fatih Acet committed
61
    classNameBindings() {
62
      return {
Felipe Artur's avatar
Felipe Artur committed
63
        [`note-row-${this.note.id}`]: true,
Fatih Acet's avatar
Fatih Acet committed
64 65 66
        'is-editing': this.isEditing && !this.isRequesting,
        'is-requesting being-posted': this.isRequesting,
        'disabled-content': this.isDeleting,
Felipe Artur's avatar
Felipe Artur committed
67
        target: this.isTarget,
68
        'is-editable': this.note.current_user.can_edit,
69 70
      };
    },
Felipe Artur's avatar
Felipe Artur committed
71 72 73
    canResolve() {
      return this.note.resolvable && !!this.getUserData.id;
    },
Fatih Acet's avatar
Fatih Acet committed
74
    canReportAsAbuse() {
75
      return !!this.note.report_abuse_path && this.author.id !== this.getUserData.id;
76
    },
Fatih Acet's avatar
Fatih Acet committed
77 78
    noteAnchorId() {
      return `note_${this.note.id}`;
Filipa Lacerda's avatar
Filipa Lacerda committed
79
    },
Felipe Artur's avatar
Felipe Artur committed
80 81 82
    isTarget() {
      return this.targetNoteHash === this.noteAnchorId;
    },
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
    actionText() {
      if (this.commit) {
        const { id, url } = this.commit;
        const linkStart = `<a class="commit-sha monospace" href="${escape(url)}">`;
        const linkEnd = '</a>';
        return sprintf(
          s__('MergeRequests|commented on commit %{linkStart}%{commitId}%{linkEnd}'),
          {
            commitId: truncateSha(id),
            linkStart,
            linkEnd,
          },
          false,
        );
      }

      return '<span class="d-none d-sm-inline">&middot;</span>';
    },
Fatih Acet's avatar
Fatih Acet committed
101
  },
Filipa Lacerda's avatar
Filipa Lacerda committed
102

Fatih Acet's avatar
Fatih Acet committed
103 104 105
  created() {
    eventHub.$on('enterEditMode', ({ noteId }) => {
      if (noteId === this.note.id) {
106
        this.isEditing = true;
Fatih Acet's avatar
Fatih Acet committed
107 108 109 110
        this.scrollToNoteIfNeeded($(this.$el));
      }
    });
  },
111

Felipe Artur's avatar
Felipe Artur committed
112 113 114 115 116 117
  mounted() {
    if (this.isTarget) {
      this.scrollToNoteIfNeeded($(this.$el));
    }
  },

Fatih Acet's avatar
Fatih Acet committed
118
  methods: {
Felipe Artur's avatar
Felipe Artur committed
119
    ...mapActions(['deleteNote', 'updateNote', 'toggleResolveNote', 'scrollToNoteIfNeeded']),
Fatih Acet's avatar
Fatih Acet committed
120 121
    editHandler() {
      this.isEditing = true;
122
      this.$emit('handleEdit');
Fatih Acet's avatar
Fatih Acet committed
123 124
    },
    deleteHandler() {
125
      const typeOfComment = this.note.isDraft ? 'pending comment' : 'comment';
Fatih Acet's avatar
Fatih Acet committed
126
      // eslint-disable-next-line no-alert
127
      if (window.confirm(`Are you sure you want to delete this ${typeOfComment}?`)) {
Fatih Acet's avatar
Fatih Acet committed
128
        this.isDeleting = true;
Tim Zallmann's avatar
Tim Zallmann committed
129
        this.$emit('handleDeleteNote', this.note);
130

131 132
        if (this.note.isDraft) return;

Fatih Acet's avatar
Fatih Acet committed
133
        this.deleteNote(this.note)
134
          .then(() => {
Fatih Acet's avatar
Fatih Acet committed
135
            this.isDeleting = false;
136
          })
137
          .catch(() => {
Felipe Artur's avatar
Felipe Artur committed
138
            Flash('Something went wrong while deleting your note. Please try again.');
Fatih Acet's avatar
Fatih Acet committed
139
            this.isDeleting = false;
140
          });
Fatih Acet's avatar
Fatih Acet committed
141 142
      }
    },
143 144 145 146 147 148 149 150
    updateSuccess() {
      this.isEditing = false;
      this.isRequesting = false;
      this.oldContent = null;
      $(this.$refs.noteBody.$el).renderGFM();
      this.$refs.noteBody.resetAutoSave();
      this.$emit('updateSuccess');
    },
Fatih Acet's avatar
Fatih Acet committed
151
    formUpdateHandler(noteText, parentElement, callback) {
152 153 154 155 156
      this.$emit('handleUpdateNote', {
        note: this.note,
        noteText,
        callback: () => this.updateSuccess(),
      });
Fatih Acet's avatar
Fatih Acet committed
157 158 159
      const data = {
        endpoint: this.note.path,
        note: {
Felipe Artur's avatar
Felipe Artur committed
160
          target_type: this.getNoteableData.targetType,
Fatih Acet's avatar
Fatih Acet committed
161 162 163 164 165 166 167 168 169 170
          target_id: this.note.noteable_id,
          note: { note: noteText },
        },
      };
      this.isRequesting = true;
      this.oldContent = this.note.note_html;
      this.note.note_html = escape(noteText);

      this.updateNote(data)
        .then(() => {
171
          this.updateSuccess();
Fatih Acet's avatar
Fatih Acet committed
172 173 174 175 176 177
          callback();
        })
        .catch(() => {
          this.isRequesting = false;
          this.isEditing = true;
          this.$nextTick(() => {
Felipe Artur's avatar
Felipe Artur committed
178
            const msg = 'Something went wrong while editing your comment. Please try again.';
Fatih Acet's avatar
Fatih Acet committed
179 180 181 182 183 184 185 186 187
            Flash(msg, 'alert', this.$el);
            this.recoverNoteContent(noteText);
            callback();
          });
        });
    },
    formCancelHandler(shouldConfirm, isDirty) {
      if (shouldConfirm && isDirty) {
        // eslint-disable-next-line no-alert
Felipe Artur's avatar
Felipe Artur committed
188
        if (!window.confirm('Are you sure you want to cancel editing this comment?')) return;
Fatih Acet's avatar
Fatih Acet committed
189 190 191 192 193 194 195
      }
      this.$refs.noteBody.resetAutoSave();
      if (this.oldContent) {
        this.note.note_html = this.oldContent;
        this.oldContent = null;
      }
      this.isEditing = false;
196
      this.$emit('cancelForm');
Fatih Acet's avatar
Fatih Acet committed
197 198 199 200 201
    },
    recoverNoteContent(noteText) {
      // we need to do this to prevent noteForm inconsistent content warning
      // this is something we intentionally do so we need to recover the content
      this.note.note = noteText;
Felipe Artur's avatar
Felipe Artur committed
202
      this.$refs.noteBody.note.note = noteText;
203
    },
Fatih Acet's avatar
Fatih Acet committed
204 205
  },
};
206 207 208
</script>

<template>
209
  <timeline-entry-item
210
    :id="noteAnchorId"
211
    :class="classNameBindings"
212
    :data-award-url="note.toggle_award_path"
Felipe Artur's avatar
Felipe Artur committed
213
    :data-note-id="note.id"
214
    class="note note-wrapper"
Felipe Artur's avatar
Felipe Artur committed
215
  >
216 217 218 219 220 221 222
    <div v-once class="timeline-icon">
      <user-avatar-link
        :link-href="author.path"
        :img-src="author.avatar_url"
        :img-alt="author.name"
        :img-size="40"
      >
223
        <slot slot="avatar-badge" name="avatar-badge"></slot>
224 225 226 227
      </user-avatar-link>
    </div>
    <div class="timeline-content">
      <div class="note-header">
228 229 230 231 232
        <note-header
          v-once
          :author="author"
          :created-at="note.created_at"
          :note-id="note.id"
233 234 235
        >
          <span v-html="actionText"></span>
        </note-header>
236 237 238 239 240
        <note-actions
          :author-id="author.id"
          :note-id="note.id"
          :note-url="note.noteable_note_url"
          :access-level="note.human_access"
241
          :can-edit="note.current_user.can_edit"
242 243 244 245 246 247 248 249 250 251 252 253
          :can-award-emoji="note.current_user.can_award_emoji"
          :can-delete="note.current_user.can_edit"
          :can-report-as-abuse="canReportAsAbuse"
          :can-resolve="note.current_user.can_resolve"
          :report-abuse-path="note.report_abuse_path"
          :resolvable="note.resolvable"
          :is-resolved="note.resolved"
          :is-resolving="isResolving"
          :resolved-by="note.resolved_by"
          @handleEdit="editHandler"
          @handleDelete="deleteHandler"
          @handleResolve="resolveHandler"
Filipa Lacerda's avatar
Filipa Lacerda committed
254
        />
255
      </div>
256 257 258 259 260 261 262 263 264 265 266 267 268
      <div class="timeline-discussion-body">
        <slot name="discussion-resolved-text"></slot>
        <note-body
          ref="noteBody"
          :note="note"
          :line="line"
          :can-edit="note.current_user.can_edit"
          :is-editing="isEditing"
          :help-page-path="helpPagePath"
          @handleFormUpdate="formUpdateHandler"
          @cancelForm="formCancelHandler"
        />
      </div>
269
    </div>
270
  </timeline-entry-item>
271
</template>